TL;DR

Here we are with TASK #1 from the Perl Weekly Challenge #100. Enjoy!

The challenge

You are given a time (12 hour / 24 hour). Write a script to convert the given time from 12 hour format to 24 hour format and vice versa. Ideally we expect a one-liner.

The questions

One first question is about the one-liner. I mean, I have nothing against one-liners, but the real value of a one-liner for me is when I can easily remember it and type it on the spot, so:

  • either the one-liner is really short and easy to remember;
  • or it relies on a module that is normally widely available, which makes us go back to the previous bullet.

Now, in this case the only module that comes to my mind is DateTime, which is not in CORE, does no parsing and so it’s virtually not useful. Which in turn makes me wonder if I should know better about other modules readily available, or tools or…

OK enough, all of this just to say that my solution will not be ideal.

Another question is about the interpretation of the or in the examples. Does it mean that we can choose what best format suits to our programmer needs, i.e. with or without a space to separate am or pm? In this case, we will stick to Postel’s law:

Be conservative in what you do, be liberal in what you accept from others.

That is to say, we will stick to a single output format, but we will also accept both input formats.

Last… I guess it’s common knowledge in places where am and pm are a thing, but I had to explicitly search for whether 12:15 pm means when there’s sun in the Italian sky or not. Well, unless it’s cloudy, of course. And let me tell something: it makes no sense. Just like putting the month, then the day, then the year… 🤯

Anyway, we will do no input validation.

The solution

Enough ranting, let’s get to the code:

sub fun_time ($t) {
   my ($h, $m, $ampm) = $t =~ m{\A(\d\d):(\d\d)(?:\s*(am|pm))?\z}mxs;
   ($h, $ampm) =
      ($ampm//='') eq 'pm' ? ($h < 12 ? $h + 12 : 12, ''  )
      : $ampm eq 'am'      ? ($h < 12 ? $h      : 0 , ''  )
      : $h == 0            ? (12                    , ' am')
      : $h == 12           ? (12                    , ' pm')
      : $h > 12            ? ($h - 12               , ' pm')
      :                      ($h                    , ' am');
   return sprintf "%02d:%02d%s", $h % 24, $m, $ampm;
}

This is one of those cases where, in hindsight, I should have probably broken the function into two different ones, to cope with the two different possible directions of conversion. Whatever.

The first line does the parsing of the input: we always expect to get hours and minutes, and we’re not so sure about getting the day part indicator. This is why the latter part is within a block that is also optional (note the ? just after it). We also make sure to get rid of any spaces while at it.

Minutes are easy: they stay the same 🤓

For the others… it’s complicated, like always when there are times involved. And don’t get me started with dates and calendars…

Depending on the cases, there will be a transformation in the value for the hour and there will always be a transformation for the $ampm variable that tracks what to output for the am/pm part. There are a lot of sub-cases because after 11:59 am there comes 12:00 pm and we also have to revert this… oh my!

I’m curious to look at those clever one liners at this point!

The whole program, should you be curious about it:

#!/usr/bin/env perl
use 5.024;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';

sub fun_time ($t) {
   my ($h, $m, $ampm) = $t =~ m{\A(\d\d):(\d\d)(?:\s*(am|pm))?\z}mxs;
   ($h, $ampm) =
      ($ampm//='') eq 'pm' ? ($h < 12 ? $h + 12 : 12, ''  )
      : $ampm eq 'am'      ? ($h < 12 ? $h      : 0 , ''  )
      : $h == 0            ? (12                    , ' am')
      : $h == 12           ? (12                    , ' pm')
      : $h > 12            ? ($h - 12               , ' pm')
      :                      ($h                    , ' am');
   return sprintf "%02d:%02d%s", $h % 24, $m, $ampm;
}

say fun_time(shift || '05:15');

Wait! What time it?!? I’m late for school!