This is my response to The Weekly Challenge #347.
10th Nov 20252025-11-10
@DAYS = ("1st", "2nd", "3rd", ....., "30th", "31st")
@MONTHS = ("Jan", "Feb", "Mar", ....., "Nov", "Dec")
@YEARS = (1900..2100)
Example 1:
Input: $str = "1st Jan 2025"
Output: "2025-01-01"
Example 2:
Input: $str = "22nd Feb 2025"
Output: "2025-02-22"
Example 3:
Input: $str = "15th Apr 2025"
Output: "2025-04-15"
Example 4:
Input: $str = "23rd Oct 2025"
Output: "2025-10-23"
Example 5:
Input: $str = "31st Dec 2025"
Output: "2025-12-31"
#! /usr/bin/env raku
unit sub MAIN ($str where 13 >= $str.chars >= 12); # [1]
my %months = ( # [2]
Jan => 1, Feb => 2, Mar => 3, Apr => 4,
May => 5, Jun => 6, Jul => 7, Aug => 8,
Sep => 9, Oct => 10, Nov => 11, Dec => 12 );
my %days = ( # [3]
'1st' => 1, '2nd' => 2, '3rd' => 3, '4th' => 4, '5th' => 5,
'6th' => 6, '7th' => 7, '8th' => 8, '9th' => 9, '10th' => 10,
'11st' => 11, '12nd' => 12, '13rd' => 13, '14th' => 14, '15th' => 15,
'16th' => 16, '17th' => 17, '18th' => 18, '19th' => 19, '20th' => 20,
'21st' => 21, '22nd' => 22, '23rd' => 23, '24th' => 24, '25th' => 25,
'26th' => 26, '27th' => 37, '28th' => 28, '29th' => 29, '30th' => 30,
'31st' => 31);
my ($day, $month, $year) = $str.split(/\s/); # [4]
if $year.Int && 1900 <= $year <= 2100 # [5]
&& %months{$month} # [5a]
&& %days{$day} # [5b]
{
say $year # [6]
~ "-" ~ %months{$month}.fmt('%02d') # [6a]
~ "-" ~ %days{$day}.fmt('%02d'); # [6b]
}
[1] A string with length 12 or 13.
[2] The legal input month string, as a hash lookup.
[3] The legal input day strings, also as a hash lookup.
[4] Split the input on single spaces.
[5] Do we have a legal year, month [5a] and day [5b]?
[6] If so, print the year, month [6a] and day [6b].
The month and days are prefixed with zero if the size is 1, courtesy
of fmt, the method version of sprintf. Note the manually
added hyphens.
See docs.raku.org/routine/fmt for more information about fmt.
Running it:
$ ./format-date "1st Jan 2025"
2025-01-01
$ ./format-date "22nd Feb 2025"
2025-02-22
$ ./format-date "15th Apr 2025"
2025-04-15
$ ./format-date "23rd Oct 2025"
2025-10-23
$ ./format-date "31st Dec 2025"
2025-12-31
Looking good.
No verbose mode, but here are som more examples:
$ ./format-date "1th Feb 2025"
$ ./format-date "31rd Dec 2025"
$ ./format-date "31st Dec 1025"
$ ./format-date "31st Feb 2025"
2025-02-31
The first two have illegal input, and do not produce any output. The
third one shows that the program allows 31 days in any month. Verifying
the legality of the date is easy; just delegate the task to a
Date object.
#! /usr/bin/env raku
unit sub MAIN ($str where 13 >= $str.chars >= 12);
my %months = (
Jan => 1, Feb => 2, Mar => 3, Apr => 4,
May => 5, Jun => 6, Jul => 7, Aug => 8,
Sep => 9, Oct => 10, Nov => 11, Dec => 12 );
my %days = (
'1st' => 1, '2nd' => 2, '3rd' => 3, '4th' => 4, '5th' => 5,
'6th' => 6, '7th' => 7, '8th' => 8, '9th' => 9, '10th' => 10,
'11st' => 11, '12nd' => 12, '13rd' => 13, '14th' => 14, '15th' => 15,
'16th' => 16, '17th' => 17, '18th' => 18, '19th' => 19, '20th' => 20,
'21st' => 21, '22nd' => 22, '23rd' => 23, '24th' => 24, '25th' => 25,
'26th' => 26, '27th' => 37, '28th' => 28, '29th' => 29, '30th' => 30,
'31st' => 31);
my ($day, $month, $year) = $str.split(/\s/);
if $year.Int && 1900 <= $year <= 2100 && %months{$month} && %days{$day}
{
my $date = try Date.new($year, %months{$month}, %days{$day}); # [1]
say $date if $date; # [2]
}
[1]
Create a Date object, wrapped in try so that the
program does not crash on (stringification of) illegal dates in [2].
See
docs.raku.org/type/Date
for information about the Date class.
See docs.raku.org/routine/try for more information about try.
[2] Let the Date object print the date for us, as it adds hyphens
and zeros as needed.
Running it on some edge cases:
$ ./format-date-try "28th Feb 2025"
2025-02-28
$ ./format-date-try "29th Feb 2025"
Input: $phone = "1-23-45-6"
Output: "123-456"
Example 2:
Input: $phone = "1234"
Output: "12-34"
Example 3:
Input: $phone = "12 345-6789"
Output: "123-456-789"
Example 4:
Input: $phone = "123 4567"
Output: "123-45-67"
Example 5:
Input: $phone = "123 456-78"
Output: "123-456-78"
#! /usr/bin/env raku
unit sub MAIN ($phone where $phone ~~ /^ <[0 .. 9 \s -]>+ $/, # [1]
:v($verbose));
my $parts := gather # [2]
{
my $current = ""; # [3]
my @todo = $phone.comb.grep: * eq any(0..9); # [4]
say ": Digits { @todo.join }" if $verbose;
while @todo # [5]
{
$current ~= @todo.shift; # [6]
if $current.chars == 3 || (@todo.elems == 2 && $current.chars == 2) # [7]
{
take $current; # [7a]
$current = ""; # [7b]
}
}
take $current if $current; # [8]
}
say $parts.join("-"); # [9]
[1] Ensure digits, dashes and spaces only.
[2] This task is suitable for gather/take,
where we use gather to collect the parts (the digit blocks).
See my Raku Gather, I Take article or docs.raku.org/language/control#gather/take for more information about gather/take.
[3] The current digit block, initially empty.
[4] Digits to iterate over, as an array. We ditch the dashes.
[5] More digits to process?
[6] Add it to the current digit block.
[7] Do we either have a three digit block, or a two digit
block and only two digits left to go? If so, return (with take)
the block [7a] and reset it [7b].
[8] Return the very last block, if any.
[9] Print the blocks with dashes beetween them.
Running it:
$ ./format-phone-number "1-23-45-6"
123-456
$ ./format-phone-number "1234"
12-34
$ ./format-phone-number "12 345-6789"
123-456-789
$ ./format-phone-number "123 4567"
123-45-67
$ ./format-phone-number "123 456-78"
123-456-78
Looking good.
With verbose mode:
$ ./format-phone-number -v "1-23-45-6"
: Digits 123456
123-456
$ ./format-phone-number -v "1234"
: Digits 1234
12-34
$ ./format-phone-number -v "12 345-6789"
: Digits 123456789
123-456-789
$ ./format-phone-number -v "123 4567"
: Digits 1234567
123-45-67
$ ./format-phone-number -v "123 456-78"
: Digits 12345678
123-456-78
And that's it.