Format, Format
with Raku

by Arne Sommer

Format, Format with Raku

[371] Published 15. November 2025.

This is my response to The Weekly Challenge #347.

Challenge #347.1: Format Date

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 set below.

@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"
File: format-date
#! /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.

File: format-date-try
#! /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"


Challenge #347.2: Format Phone Number

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:

  1. Removing all spaces and dashes
  2. Grouping digits into blocks of length 3 from left to right
  3. 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
  4. Joining all blocks with dashes
Example 1:
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"
File: format-phone-number
#! /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.