I’ve been doing the Weekly
Challenges. The
latest
involved mildly abstruse string formatting. (Note that this ends
today.)
Task 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)
Obviously the Right way to do this is with an actual date parser, but
many languages don't have that. And the nearly as right way is regular
expressions, though I did it without in some languages just for fun.
Typst has no sprintf-like formatting, so I write that first:
#let fmn(a, n) = {
let ap = str(a)
while ap.len() < n {
ap = "0" + ap
}
ap
}
#let formatdate(a) = {
Match the input against the pattern.
let dmy = a.match(regex("([0-9]+)[a-z]+ ([A-Z][a-z]{2}) ([0-9]+)")).captures
A literal map to look up the month name.
dmy.at(1) = (
("Jan" , 1),
("Feb" , 2),
("Mar" , 3),
("Apr" , 4),
("May" , 5),
("Jun" , 6),
("Jul" , 7),
("Aug" , 8),
("Sep" , 9),
("Oct" , 10),
("Nov" , 11),
("Dec" , 12),
).to-dict().at(dmy.at(1))
Then convert to integer as needed, format and join.
(
fmn(int(dmy.at(2)), 4),
fmn(dmy.at(1), 2),
fmn(int(dmy.at(0)), 2)
).join("-")
}
Raku makes this much more difficult by assuming any space in a regexp
is not significant. PostScript has no pattern matching at all beyond
the individual character…
Task 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:
-
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
The traditional Perl way of inserting commas in a number is to reverse
it first. But this didn't feel like a regexp-flavoured problem to me.
Raku:
sub formatphonenumber($a) {
Break the string into a list of valid characters.
(my $p = $a) ~~ s:g/\D+//;
my @pure = $p.comb;
Counter for remaining characters.
my $left = @pure.elems;
my @out = [];
my $i = 0;
If it's long enough, iterate:
if ($left > 4) {
for @pure -> $c {
@out.push($c);
$i++;
$left--;
If we've hit the end of a group of three, put in a divider.
if ($i % 3 == 0) {
@out.push('-');
And if what remains is short enough, drop out.
if ($left <= 4) {
last;
}
}
}
}
At this point we have two, three or four digits left. The four-digit
case is the only one in which the answer isn't "just dump all the
remaining digits". For that, snag the next two digits and add a
separator, leaving the last two to be caught by the default handler.
if ($left == 4) {
@out.push(@pure[$i]);
@out.push(@pure[$i + 1]);
@out.push('-');
$i += 2;
}
Dump the remaining digits.
for $i .. @pure.end -> $p {
@out.push(@pure[$p]);
}
@out.join('');
}
Full code on
github.