Convenience Methods for Time

Our system of timekeeping (the whole damn thing, from 60 second minutes through to leap years) is a pet peeve of mine. If only the metric people could have done for time what they did for everything else!

Ruby has very robust support for date and time manipulation, but for reasons I don’t understand, it is broken up across multiple classes (chiefly Date, Time, and DateTime). So far, this is the only fundamentally broken thing I have found in the standard library.

Time stores date and time information, and does arithmetic in seconds. Date and DateTime are primarily about parsing and manipulating dates, and do arithmetic in days and months. Date and DateTime are very robust at parsing and arithmetic (smart enough to roll April 31 to May 1 and so on), while Time is fussy (Time will raise if you try to create April 31). Date and DateTime are implemented in Ruby, and are especially inefficient at string parsing. Time is implemented in C, and parses date strings very quickly, but not as well effectively as Date and DateTime.

In a better world, we would get the benefits of Time, Date, and DateTime all rolled into one class. Everything would be in Time, and Time would be smart enough to fall back to Date and DateTime as needed.

Since this is Ruby, we could do exactly this, easily and transparently in our code. Let’s start with something a little more modest. Here are some convenience methods for time management in Ruby:

require 'date'

module TimeRounding
def day_start
Time.local year, month, day
end

def day_end
day_offset(1) - 1
end

def day_offset offset
date = Date.new(year, month, day) + offset
Time.local date.year, date.month, date.day, hour, min, sec
end

def month_start
Time.local year, month, 1
end

def month_end
month_offset(1) - 1

end

def month_offset offset
date = Date.new(year, month, day) >> offset
Time.local date.year, date.month, date.day, hour, min, sec
end
end

class Time
include TimeRounding
end

And the usage:

irb(main):034:0> Time.now
=> Wed May 09 18:48:11 GMT 2007
irb(main):035:0> Time.now.day_offset 3
=> Sat May 12 18:48:14 GMT 2007
irb(main):036:0> Time.now.month_offs
et 3
=> Thu Aug 09 18:48:22 GMT 2007
irb(main):037:0> Time.now.day_offset 14
=> Wed May 23 18:48:31 GMT 2007
irb(main):038:0> Time.now.day_offset -20
=> Thu Apr 19 18:48:41 GMT 2007