posted by bmishkin  03/11/2009

Rails 2.2 has straightforward, plugin-free support for timezones and localization. Woot to rails core, goodbye to tzinfo.

But what if you need to use a variable using a MySQL::time column – like lunch_time that describes a time of day? Lets use the example of an ActiveRecord class Schedule with a column lunch_time. A function like
1
2
3
def missed_lunch?(sched)
  Time.zone.now > sched.lunch_time + 30.minutes                  
end

doesn’t work. Why? MySQL::time columns are 24-hour naked times (like 13:30:04), with no zone, date, or AM/PM within MySQL. When loaded by AR the column is converted into a Ruby::Time object in UTC time on 1/1/2000, since Ruby has no native time_of_day-like class. 9:15 (in MySQL::time) becomes Sat Jan 01 9:15:00 UTC 2000 ( in ruby Time).

Better would be a relevant time of day that we could compare to ruby Time objects (like Time.zone.now). It’s best to tackle this problem at the model level, so the rest of our code can deal with time_of_day consistently. We extend ruby Time with a method to convert unruly MySQL::time columns into local-zone, ‘today’ times:
1
2
3
4
5
class Time
  def from_mysql_time
    return Time.zone.now.change(:hour => self.hour, :min => self.min, :sec => self.sec)  
  end
end
Then overload the column attribute in our AR model to always return from_mysql_time for our column:
1
2
3
4
5
6
7
class Schedule < ActiveRecord::Base
#  lunch_time                   time
(...)
  def lunch_time
    read_attribute(:lunch_time).from_mysql_time
  end
end
This gives us a MySQL::time column that is useful for comparisons. Example:
>> Time.zone.now
=> Wed, 11 Mar 2009 11:46:51 EDT -04:00
>> my_sched.lunch_time
=> Wed, 11 Mar 2009 12:30:00 EDT -04:00
>> missed_lunch?(my_sched)
false

This solution works consistenly for all timezones. If your app uses localization, you can relax knowing your time_of_day comparisons will be done correctly in any Time.zone

Leave a Comment