2
\$\begingroup\$

This challenge is inspired by this other.

The challenge

Write a program or a function in any programming language that given as input a string representing a time in English (see below, for further details) it outputs or prints the equivalent in the 24 hours "digital" format HH:MM.

Shortest code in bytes wins, standard loopholes are forbidden.

Input format

Your program should be able to handle all this kind of inputs:

one minute past 'HOUR'
'MINUTES' minutes past 'HOUR'
quarter past 'HOUR'
half past 'HOUR'
one minute to 'HOUR'
'MINUTES' minutes to 'HOUR'
quarter to 'HOUR'
'HOUR'
'HOUR12' 'MINUTES' AM
'HOUR12' 'MINUTES' PM

where: MINUTES is a number from 1 to 59 written in English with hyphen; HOUR is the name of an hour in one of the formats:

midnight
noon
midday
'HOUR24' o'clock
'HOUR12' o'clock AM
'HOUR12' o'clock PM

HOUR12 is a number from 1 to 12 written in English with hyphen; and HOUR24 is a number from 1 to 23 written in English with hyphen.

Some examples

midnight                                  ->   00:00
midday                                    ->   12:00
twelve o'clock                            ->   12:00
quarter to midnight                       ->   23:45
twenty-two minutes past eight o'clock PM  ->   20:22
thirty-four minutes to fifteen o'clock    ->   14:26
six o'clock AM                            ->   06:00
one minute to twenty-one o'clock          ->   20:59
seven thirteen AM                         ->   07:13
nine fourteen PM                          ->   21:14

Good luck!

\$\endgroup\$
5
  • \$\begingroup\$ So is 'MINUTES' minutes past 'HOUR24' o'clock a valid time? \$\endgroup\$
    – Blue
    Feb 6, 2016 at 15:06
  • \$\begingroup\$ @muddyfish Yes, it is. \$\endgroup\$
    – Bob
    Feb 6, 2016 at 15:08
  • 2
    \$\begingroup\$ Should HOUR24 be a number from 0 to 23? \$\endgroup\$ Feb 6, 2016 at 15:21
  • 1
    \$\begingroup\$ @ETHproductions No, from 1 to 23. "zero o'clock" doesn't sound well. \$\endgroup\$
    – Bob
    Feb 6, 2016 at 16:41
  • \$\begingroup\$ @Bob neither does "24 o-clock" or "23-oclock" \$\endgroup\$
    – cat
    Jul 7, 2016 at 13:30

1 Answer 1

2
\$\begingroup\$

Ruby, 653 597 Bytes

P=/ past | to /;T=%w{q twen thirty fourty fifty sixty el twe thi fourte fifte sixt sevent eighte ninet o tw th fo fi si s e n t}.zip([15,*(2..6).map{|i|i*10},*11..19,*1..10]).to_h;D={'middnight'=>0,'midday'=>12,'noon'=>12};n=->s{s.split('-').reduce(0){|p,v|(Array(T.find{|k,_|v.start_with? k}).last||0)+p}};i=->s{m=0;s=s.gsub(/^\s+|\s+$/,'');c=s.split(P);(c.reverse! if s.match(P));h=D[c[0]]||n.(c[0].gsub(/ o\'clock (A|P)m/,''));h+=(s.end_with?('PM') ? 12 : 0);m=n.(c[1]||c[0].split(' ')[1]) if c[1]||c[0].match(/^\w+ (?!o'clock)/);(m=60-m;h=(h-1)%24)if s.match(/ to /);(sprintf '%02d:%02d',h,m)}

Ungolfed:

P=/ past | to /
# configuration for translating english words to numbers
T=%w{q twen thirty fourty fifty sixty el twe thi fourte fifte sixt sevent eighte ninet o tw th fo fi si s e n t}.zip([15,*(2..6).map{|i|i*10},*11..19,*1..10]).to_h
D={'middnight'=>0,'midday'=>12,'noon'=>12}

# proc for translating english word groups to a number
n=-> s {
  s.split('-').reduce(0) { |p,v|
    (Array(T.find {|k,_| v.start_with? k}).last || 0) + p
  }
}

i=-> s {
  m=0
  s=s.gsub(/^\s+|\s+$/,'')
  c=s.split(P)
  (c.reverse! if s.match(P))

  # set the hour
  h=D[c[0]]||n.(c[0].gsub(/ o\'clock (A|P)m/,''))
  h+=(s.end_with?('PM') ? 12 : 0)

  # set the minute
  m=n.(c[1]||c[0].split(' ')[1]) if c[1]||c[0].match(/^\w+ (?!o'clock)/)

  # adjust for 'past'
  (m=60-m;h=(h-1)%24)if s.match(/ to /)

  sprintf '%02d:%02d',h,m
}

Verification script used:

examples = <<EXAMPLES
midnight                                  ->   00:00
midday                                    ->   12:00
twelve o'clock                            ->   12:00
quarter to midnight                       ->   23:45
twenty-two minutes past eight o'clock PM  ->   20:22
thirty-four minutes to fifteen o'clock    ->   14:26
six o'clock AM                            ->   06:00
one minute to twenty-one o'clock          ->   20:59
seven thirteen AM                         ->   07:13
nine fourteen PM                          ->   21:14
EXAMPLES

examples.split("\n").each do |example|
  text, expected = example.split(/\s+->\s+/)
  r = i.(text)
  raise "Unexpected result for \"#{text}\". Got \"#{r}\", expected \"#{expected}\"" unless r == expected
  print "."
end

Thank you to Kevin Lau for 47 bytes of optimization

\$\endgroup\$
2
  • \$\begingroup\$ [15,*(2..6).map{|i|i*10},*11..19,*1..10] is a much more concise representation of the numbers you are using to construct your hash. Also, there's a lot of whitespace you can remove between brackets, and using Ruby interpolation within regex patterns allows you to replace v.start_with? k with v=~/^#{k}/ \$\endgroup\$
    – Value Ink
    Jul 6, 2016 at 0:43
  • \$\begingroup\$ I should have thought of the ranges and splats, thanks! Also removed some unnecessary whitespace. \$\endgroup\$ Jul 6, 2016 at 4:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.