Saari Development

This blog is intended to be a log of my (Ali Rizvi's) professional ramblings as a software development engineer. I intend to add logs of my experience with different technologies, software, tech books/articles and related stuff from time to time. My intention is to have an archive for my personal use and public benefit.

Wednesday, January 02, 2008

Ruby : Time Math Interview Problem With Bug Fixed (still writing test first)

While discussing my friend Arsalan's C# (seemlessly compiled on my linux machine using mcs)
solution and testing it out I found a bug in my own code that. The problem was when adding
more than 12 hours (> 720 minutes) it was not doing the right thing. The code can still be
refactored for cleaner solution but it is too late at night to do that now. Also, my wife
gave me another idea to convert the time to minutes before adding which I will try out later.

1 # Without using any built in date or time functions, write a function or method
2 # that accepts two mandatory arguments. The first argument is a string of the
3 # format "[H]H:MM {AM|PM}" and the second argument is an integer. Assume the
4 # integer is the number of minutes to add to the string. The return value of
5 # the function should be a string of the same format as the first argument.
6 # For example AddMinutes("9:13 AM", 10) would return "9:23 AM". The exercise
7 # isn't meant to be too hard. I just want to see how you code. Feel free to
8 # do it procedurally or in an object oriented way, whichever you prefer. Use
9 # any language you want. Write production quality code.
10 # Question Source: http://blist.com/blog/
11
12 # the following solution was developed using TDD
13
14 require 'test/unit'
15
16 class TestTimeCalc < Test::Unit::TestCase
17
18 def setup
19 @time = "9:13 AM"
20 end
21
22 def test_new_time_cal
23 assert_not_nil(TimeCalc.new)
24 end
25
26 def test_add_minute_zero
27 assert_equal(@time, TimeCalc.add_minutes(@time, 0))
28 end
29
30 def test_add_minute_ten
31 assert_equal("9:23 AM", TimeCalc.add_minutes(@time, 10))
32 end
33
34 def test_add_minute_thirteen
35 assert_equal("9:26 AM", TimeCalc.add_minutes(@time, 13))
36 end
37
38 def test_add_hour
39 assert_equal("10:13 AM", TimeCalc.add_minutes(@time, 60))
40 end
41
42 def test_add_two_hours_fifteen_minutes
43 assert_equal("11:28 AM", TimeCalc.add_minutes(@time, 135))
44 end
45
46 def test_add_past_meridiem
47 # 785 minutes = 13 hours and 5 minutes
48 assert_equal("10:18 PM", TimeCalc.add_minutes(@time, 785))
49 end
50
51 def test_alpha_hour_min_format_throws_exception
52 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD AM", 10) }
53 end
54
55 def test_bad_meridiem_throws_exception
56 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD TM", 10) }
57 end
58
59 def test_hr_greater_than_twelve
60 assert_raise(ArgumentError) { TimeCalc.add_minutes("13:00 PM", 10) }
61 end
62
63 def test_min_greater_than_fifty_nine
64 assert_raise(ArgumentError) { TimeCalc.add_minutes("12:60 PM", 10) }
65 end
66
67 def test_add_lot_of_minutes
68 # 1000 minutes = 16 hours and 40 minutes
69 assert_equal("1:53 AM", TimeCalc.add_minutes(@time, 1000))
70 end
71
72 def test_add_up_to_noon
73 # 9:13 AM plus 2 hr 47 min (107)
74 assert_equal("12:00 PM", TimeCalc.add_minutes(@time, 167))
75 end
76
77 def test_add_whole_lot_of_minutes
78 # 9999 minutes = 166 hours and 39 minutes
79 assert_equal("7:52 AM", TimeCalc.add_minutes(@time, 9999))
80 end
81
82 end # end class TestTimeCalc
83
84 class TimeCalc
85
86 def self.add_minutes(time, minutes)
87 (hour, min, meridiem) = parse_time_string(time)
88
89 hour_increment = (min + minutes)/60
90 min_increment = (min + minutes)%60 - min
91
92 while (hour + hour_increment > 12)
93 meridiem = (meridiem == 'AM' ? 'PM' : 'AM')
94 hour_increment -= 12
95 end
96
97 hour += hour_increment
98 # special case for 12th hour
99 meridiem = (meridiem == 'AM' ? 'PM' : 'AM') if hour == 12
100 min += min_increment
101
102 hour.to_s + ":" + sprintf('%02d', min) + " " + meridiem
103 end
104
105 private
106
107 def self.parse_time_string(time)
108 raise ArgumentError unless (matches = time.match(/(\d{1,2}):(\d{1,2})\s+(\w{2})/))
109 matches = time.match(/^(\d{1,2}):(\d{1,2})\s+([A|P]M)$/)
110 hour = matches[1].to_i
111 min = matches[2].to_i
112 meridiem = matches[3]
113 raise ArgumentError unless (hour <= 12)
114 raise ArgumentError unless (min < 60)
115 return [hour, min, meridiem]
116 end
117 end # end class TimeCalc
118
119 if __FILE__ == $0
120 puts TimeCalc.add_minutes(ARGV[0], ARGV[1].to_i)
121 end
122

# vim colorscheme = delek

2 Comments:

Blogger Arsalan said...

If you haven't looked at it yet, try my updated code at http://softwareworks.wordpress.com

My design converts time to minutes before performing any arithmetic.

9:23 AM  
Blogger suresh said...

interesting blog. It would be great if you can provide more details about it. Thanks you


Ruby Development

1:20 AM  

Post a Comment

<< Home