-1

Write a method that sums up multiple 24-hour time values(h:m:s). Validate the time using regular expressions.

Input and output format : Eg: ("11:23:07","22:53:45","0:23:23","23:45:56") -> "2 day & 10:26:11" ("11:23:07") -> "11:23:07"

My code is failing with one test case i.e "24:01:10" "10:30:50"

Solution:

require 'time'
# Time_Sum
class Time
  KEY = /^(0\d|1\d|2[0-3]|\d):[0-5]\d?:[0-5]\d?$/
  def to_seconds(timestamp)
    timestamp.hour * 3600 + timestamp.min * 60 + timestamp.sec
  end

  def validate?(time_string)
    time_string.match(KEY)
  end

  def sum_time(*time)
    total_seconds = 0
    time.each do |time_item|
      timestamp = Time.parse(time_item) if validate?(time_item)
      total_seconds += to_seconds(timestamp) if validate?(time_item)
      'Invalid 24-hour time value'
    end
    display_results(total_seconds)
  end

  def display_results(total_seconds)
    sum_time_string = ''
    days = (total_seconds / (24 * 3600)).to_i
    sum_time_string = "#{days} day & " if days > 0
    sum_time_string + Time.at(total_seconds).utc.strftime('%H:%M:%S')
  end
end
reader = Time.new
if ARGV.empty?
  puts 'Please provide an input'
else
  p reader.sum_time(*ARGV)
end

Expected output : "Invalid 24-hour time value" Actual output : "10:30:50"

Also can someone please suggest better regex Key ?

EylM
  • 5,967
  • 2
  • 16
  • 28

1 Answers1

1

Firstly, I would recommend against polluting the core Ruby class Time. The needed methods could be housed in a custom class or possibly defined at the level of main (as I have done below).

Nor would I make use of the classes Time or DateTime. It's easier to just deal with strings. Begin by constructing a regular expression that will be used to determine if each time string is valid.

VALID_TIME = /
             \A        # match beginning of string
             (?:       # begin non-capture of group
               0?\d    # optionally match a zero followed by a digit
               |       # or
               1\d     # match 1 followed by a digit
               |       # or
               2[0-3]  # match 2 followed by a digit 0, 1, 2 or 3
             )         # end non-capture group
             (?:       # begin a non-capture group
               :       # match a colon
               [0-5]   # match a digit 0, 1, 2, 3, 4 or 5
               \d      # match a digit
             )         # end non-capture group
             {2}       # execute the previous non-capture group twice
             /x        # free-spacing regex-definition mode

Writing regular expressions in free-spacing mode has the advantage that it makes them self-documenting. This regular expression is conventionally written as follows:

/\A(?:0?\d|1\d|2[0-3])(?::[0-5]\d){2}/

Our main method might be total_times, to which is passed one or more strings representing twenty-four hour times.

def total_times(*time_strings)
  seconds    = tot_seconds(*time_strings)
  days, hrs  = seconds.divmod(24*3600)
  hrs,  mins = hrs.divmod(3600)
  mins, secs = mins.divmod(60)
  [days, hrs, mins, secs]
end

This first calls a method tot_seconds, having the same arguments. That method returns the sum of the number of seconds since midnight for each of the times it is passed. Once the total number of seconds is obtained, Integer#divmod is used repeatedly to compute the equivalent numbers of days, hour, minutes and seconds.

def tot_seconds(*time_strings)
  time_strings.sum { |time_str| time_str_to_seconds(time_str) }
end

This method passes each time string to the method time_str_to_seconds, which returns the number of seconds since midnight for that time string.

def time_str_to_seconds(time_str)
  raise ArgumentError, "'#{time_str}' is an invalid 24-hour time value" unless
    time_str.match?(VALID_TIME)
  [3600, 60, 1].zip(time_str.split(':')).
                reduce(0) { |tot,(n,s)| tot + n*s.to_i }
end

This method first checks the validity of the time string. If it is found to be invalid an exception is raised. This seemed like the best place to put the validity check. If the string is found to be valid it is split into strings representing hours, minutes and seconds, which are then converted to integers and combined to obtain number of seconds.

Suppose:

time_str = "9:35:08"

Then the calculations in time_str_to_seconds are as follows:

time_str.match?(VALID_TIME)
  #=> true
a = time_str.split(':')
  #=> ["9", "35", "08"] 
b = [3600, 60, 1].zip(a)
  #=> [[3600, "9"], [60, "35"], [1, "08"]]
b.reduce(0) { |tot,(n,s)| tot + n*s.to_i }
  #=> 3600*9 + 60*35 + 1*8 => 34508

See String#match? and Array#zip.

Let's now try this with an example.

arr = total_times "2:33:41", "23:46:08"
  #=> [1, 2, 19, 49]

We might use these results as follows:

puts "%d day & %d:%2d:%2d" % arr
"1 day & 2:19:49"

In some cases it might be preferable to write:

days, hrs, mins, secs = total_times "2:33:41", "23:46:08"

We can easily confirm these results.

tot_secs =  2*3600 + 33*60 + 41 +
           23*3600 + 46*60 +  8
  #=> 94789 
days, hrs  = tot_secs.divmod(24*3600)
  #=> [1, 8389] 
hrs, mins  = hrs.divmod(3600)
  #=> [2, 1189] 
mins, secs = mins.divmod(60)
  #=> [19, 49] 
[days, hrs, mins, secs] 
  #=> [1, 2, 19, 49] 

Two more examples:

total_times "11:23:07", "22:53:45", "0:23:23", "23:45:56"
  #=> [2, 10, 26, 11]
total_times "24:01:10", "10:30:50"
  #=> ArgumentError ('24:01:10' is an invalid 24-hour time value)

Note that it is not necessary to use a regular expression to determine the validity of a time string. One could write:

time_str = "9:35:08"
hr, min, sec = time_str.split(':').map(&:to_i)
  #=> [9, 35, 8] 
(0..23).cover?(hr) && (0..59).cover?(min) && (0..59).cover?(sec)
  #=> true
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100