Perl Weekly Challenge 100: Fun Time

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.

Examples

Example 1

Input: 05:15 pm or 05:15pm
Output: 17:15

Example 2

Input: 19:15
Output: 07:15 pm or 07:15pm

Overview

We have to do three things:

  • Parse the input and extract the interesting parts (hours, minutes, am/pm/none).
  • Calculate the result. Note that the minutes part stays as is, this doesn’t change whether the time is using am/pm or a 24 hour clock.
  • Print the resulting time.

For the am/pm marker, note that if the input uses am or pm, then the output doesn’t. If the input doesn’t have am or pm (that is, it uses a 24 hour clock), then the output will have either am or pm. In particular, it will have am in the output if the input hour is less than 12, else it will have pm.

For the hours, it’s important to realize that a time with am or pm doesn’t use the 0 hour — instead it uses 12. So, beside adding/subtracting 12 (if going from/to pm time), we have to compensate for this. We do this as follows:

  • We start off with taking the hour modulo 12. The effect of this is that if the start time uses am or pm, and the hour is 12, the hour now is 0. If the start time is in 24 hour format, we now have hours in range 011.
  • If the start time used pm, we add 12 to the hour.
  • If the start time was in 24 hour format, and the hour is 0 (after the modulo 12), set the hour to 12.

Solutions

For each of the languages, we assume the input is on standard input, one time per line. Output is written to standard output, one time per line.

Perl

We will use a substitution regular expression to modify the time, with an executable replacement part:

    say s {^\s* ([0-9]+) : ([0-9]+) \s* ([pa]?)m? \s*\n}
          {sprintf "%02d:%02d%s",
              $3 ? ($1 % 12) + (lc ($3) eq "a" ? 0 : 12)
                 : ($1 % 12) || 12,
              $2,
              $3 ? ""
                 : $1 >= 12 ? "pm" : "am"}xeir

From the input, we parse the hours (the first ([0-9]+)), the minutes (the second ([0-9]+)) and the am/pm setting (if any) (([pa]?)m?). The result is that $1 contains the hours; $2 contains the minutes, and $3 is either a (for an am time), p (for a pm time) or the empty string (for a time using 24 hours).

In the replacement part, we use sprintf to do formatting: hours and minutes as two digit numbers (0 padded) (%02s), followed by either am, pm, or none. The new hours and am/pm marker are calculated as explained in the overview section.

We’re using four regexp modifiers:

  • x means we can freely have white space in the pattern, which won’t be matched.
  • e means the replacement part is code which needs to be executed — the result is the replacement.
  • i means we will be doing the matching case insensitive (so we accept both am and AM, and pm and PM).
  • r means we don’t modify the original string, instead, we return the result. And this result is passed directly to sprintf.

Does this count as a one-liner? I say it does; the newlines are just there for readability.

Find the complete program on GitHub.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s