The Bear's Den

Enter at your own risk

Phone Dating

Task 1: Format Date

Submitted by: Mohammad Sajid Anwar


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"

Solution

There is no requirement to reject an input date in a form other than specified. So the input date may simply be fed into DateTime::Format::ParseDate->parse_datetime(), which is capable of parsing many more formats. Specifying an output formatter completes the solution. The sub returns a DateTime object that will yield the requested format when stringified.

use strict;
use warnings;
use DateTime::Format::Strptime;
use DateTime::Format::DateParse;

sub format_date {
    state $fmt = DateTime::Format::Strptime->new(pattern => '%F');
    DateTime::Format::DateParse->parse_datetime(shift)->set_formatter($fmt);
}

See the full solution to task 1.

Task 2: Format Phone Number

Submitted by: Mohammad Sajid Anwar


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"

Solution

In a first step all non-digits are removed.

Basically, the grouping can be found by matching the previous result against a pattern of the form:

/^(...)*(..)*$/

Some things need to be considered:

To address all these issues, the expression can be modified to something like:

/(?:(...)(?!.$)(?{say $+}))*(?:(..)(?{say $+}))*/

Straight behind the (?!.$) assertion, the preceding capture group is never subject to backtracking and thus provides a safe point to gather the current match. Similarly, after the greedily repeated first sub-expression, the second sub-expression will always match the remaining string. This also means that there is no need to anchor the expression on either side.

Finally the gathered matched groups are joined with dashes.

use strict;
use warnings;
use List::Gather;

sub format_phone_number {
    join '-', gather {
        shift =~ tr/0-9//cdr =~
            /(?:(...)(?!.$)(?{take $+}))*(?:(..)(?{take $+}))*/;
    }
}

See the full solution to task 2.