Weekly Challenge 347
Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. It’s a great way for us all to practice some coding.
Task 1: Format Date
Task
You are given a date in the form: 10th Nov 2025.
Write a script to format the given date in the form: 2025-11-10 using the sets below.
@DAYS = ("1st", "2nd", "3rd", ....., "30th", "31st")
@MONTHS = ("Jan", "Feb", "Mar", ....., "Nov", "Dec")
@YEARS = (1900..2100)
My solution
Both of these weeks tasks are pretty straight forward so don’t require too much explanation. For this task, I start by defined the lists (arrays in Perl) for DAYS, MONTHS and YEARS. As the years are actually a string, I use the map function to convert them to a string.
DAYS = [
"1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th",
"11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th",
"20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th",
"29th", "30th", "31st"
]
MONTHS = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"
]
YEARS = list(map(str, range(1900, 2101)))
I start the function by defining the three fields. Each list is the description of the field, the list to use, and the offset value.
def format_date(input_string: str) -> str:
fields = [
("day of month", DAYS, 1),
("month", MONTHS, 1),
("year", YEARS, 1900)
]
I then split the input on spaces and store this as input_list. I create an empty list called output_list. I also check that there are three items in the list.
input_list = input_string.split()
output_list = []
if len(input_list) != 3:
raise ValueError("Input must contain day, month, and year")
I then loop through each field, getting the index (position) of the part of index_list that we are looking at, and check it is in the list. If it isn’t, I’ll raise an error (e.g. “Invalid day of month: 23th”). The offset is applied and the value is added to the output_list list. I use an offset as ‘Jan’ is at position zero, but it is the 1st month (as written as a date).
for i in (range(3)):
name, values, offset = fields[i]
value = input_list[i]
if value not in values:
raise ValueError(f"Invalid {name}: {value}")
index = values.index(value) + offset
output_list.append(f"{index:02d}")
Finally, I print the date. I reverse the list and separate it with dashes.
return "-".join(reversed(output_list))
The Perl code follows the same logic.
Examples
$ ./ch-1.py "1st Jan 2025"
2025-01-01
$ ./ch-1.py "22nd Feb 2025"
2025-02-22
$ ./ch-1.py "15th Apr 2025"
2025-04-15
$ ./ch-1.py "23rd Oct 2025"
2025-10-23
$ ./ch-1.py "31st Dec 2025"
2025-12-31
Task 2: Format Phone Number
Task
You are given a phone number as a string containing digits, space and dash only.
Write a script to format the given phone number using the below rules:
- Removing all spaces and dashes
- Grouping digits into blocks of length 3 from left to right
- Handling the final digits (4 or fewer) specially:
- 2 digits: one block of length 2
- 3 digits: one block of length 3
- 4 digits: two blocks of length 2
- Joining all blocks with dashes
My solution
For this task, I start by using a regular expression removing anything that isn’t a digit from the input_string variable. I create an empty list called parts.
I use a while loop that runs if input_string is not empty. I then determine how many characters to remove from the front of the string. If the string is four characters long, I set this to 2. In other cases, I set it to the minimum of 3 or the length of the string. I remove the characters from the start of the string and add it to the parts list. The loop runs again until all characters are removed.
def format_phone(input_string: str) -> str:
# Strip all non-digit characters
input_string = re.sub(r'D', '', input_string)
parts = []
while input_string:
# Decide length of next part
l = 2 if len(input_string) == 4 else min(3, len(input_string))
parts.append(input_string[:l])
input_string = input_string[l:]
return '-'.join(parts)
The Perl solution follows the same logic. It uses the substr function which can both remove the leading characters and add to the parts list in a single call.
sub main ($input_string) {
# Strip all non-digit characters
$input_string =~ s/D//g;
my @parts = ();
while ($input_string) {
# Decide length of next part
my $l = length($input_string) == 4 ? 2 : min( 3, length($input_string) );
push @parts, substr( $input_string, 0, $l, "" );
}
say join( "-", @parts );
}
Examples
$ ./ch-2.py "1-23-45-6"
123-456
$ ./ch-2.py "1234"
12-34
$ ./ch-2.py "12 345-6789"
123-456-789
$ ./ch-2.py "123 4567"
123-45-67
$ ./ch-2.py "123 456-78"
123-456-78