The Weekly Challenge 347

Date Parsing & Phone Number Cleanup!

Original Challenge Link

Task 1: Format Date

"From Ordinal Text to ISO Date!"

The first task asks us to convert dates like 23rd Oct 2025 into ISO format 2025-10-23. The input contains day with ordinal suffix, a 3-letter month, and a 4-digit year.

The Strategy: Parse with regex into day/suffix/month/year parts, validate each field, confirm ordinal suffix correctness (including 11th/12th/13th exceptions), map month names to numbers, then format as YYYY-MM-DD.
Perl Implementation
sub format_date ($input) {
    state $check = compile(Str);
    ($input) = $check->($input);
    $input =~ s/^\s+|\s+$//g;

    my ( $day, $suffix, $month_name, $year ) =
      $input =~ /\A(\d{1,2})(st|nd|rd|th)\s+([A-Za-z]{3})\s+(\d{4})\z/
      or die 'Invalid date format';

    my $day_num = $day + 0;
    die 'Day out of range'       unless 1 <= $day_num && $day_num <= 31;
    die 'Invalid ordinal suffix' unless _suffix_for($day_num) eq $suffix;

    my $month_num = $MONTH_INDEX{$month_name} // die 'Unknown month abbreviation';
    my $year_num = $year + 0;
    die 'Year out of range' unless 1900 <= $year_num && $year_num <= 2100;

    return sprintf '%04d-%02d-%02d', $year_num, $month_num, $day_num;
}
Python Implementation
def format_date(text: str) -> str:
    """Convert strings like '10th Nov 2025' to '2025-11-10'."""
    match = DATE_PATTERN.match(text.strip())
    if not match:
        raise ValueError("Invalid date format")

    day = int(match.group("day"))
    suffix = match.group("suffix")
    month_name = match.group("month")
    year = int(match.group("year"))

    if not 1 <= day <= 31:
        raise ValueError("Day out of range")
    if _suffix_for(day) != suffix:
        raise ValueError("Invalid ordinal suffix")

    month = MONTHS.get(month_name)
    if month is None:
        raise ValueError("Unknown month abbreviation")
    if not 1900 <= year <= 2100:
        raise ValueError("Year out of range")

    return f"{year:04d}-{month:02d}-{day:02d}"

Task 2: Format Phone Number

"Dash It Right: Normalize Phone Number Blocks!"

The second task normalizes a phone number containing digits, spaces, and dashes. After stripping separators, the digits are regrouped into blocks of 3 from the left, except the last 4 digits must become two blocks of 2 and 2.

The Strategy: Remove spaces and dashes, then consume 3-digit chunks while more than 4 digits remain. For the final remainder: if length is 4, split into 2+2; otherwise keep as one block. Join blocks with dashes.
Perl Implementation
sub format_phone_number ($input) {
    state $check = compile(Str);
    ($input) = $check->($input);
    die 'Phone number requires at least two digits' unless $input =~ /\d.*\d/;
    die 'Invalid characters in phone number' if $input =~ /[^0-9\s-]/;

    my $digits = $input =~ s/[\s-]//gr;
    my $length = length $digits;
    die 'Phone number requires at least two digits' if $length < 2;

    my @blocks;
    my $pos = 0;
    while ( $length - $pos > 4 ) {
        push @blocks, substr $digits, $pos, 3;
        $pos += 3;
    }

    my $remaining = substr $digits, $pos;
    if ( length $remaining == 4 ) {
        push @blocks, substr( $remaining, 0, 2 ), substr( $remaining, 2, 2 );
    } elsif ( length $remaining ) {
        push @blocks, $remaining;
    }

    return join '-', @blocks;
}
Python Implementation
def format_phone_number(value: str) -> str:
    """Normalize phone number into blocks separated by dashes."""
    if not re.fullmatch(r"[0-9\s-]+", value):
        raise ValueError("Invalid characters in phone number")

    digits = re.sub(r"[\s-]", "", value)
    if len(digits) < 2:
        raise ValueError("Phone number requires at least two digits")

    blocks = []
    i = 0
    while len(digits) - i > 4:
        blocks.append(digits[i:i+3])
        i += 3

    tail = digits[i:]
    if len(tail) == 4:
        blocks.extend([tail[:2], tail[2:]])
    elif tail:
        blocks.append(tail)

    return "-".join(blocks)