Timezone, one of the developer's plagues
Timezones, along with charset and multi-browser support, are a pain in the ass to deal with.
With rails, since 2.1, timezones are supported by default via the config.time_zone
option :
# config/environment.rb
config.time_zone = ‘UTC’
You can configure it to something else like :
# config/environment.rb
config.time_zone = 'Paris'
Or in the controller, per user basis :
# controllers/application.rb
before_filter :set_time_zone
def set_time_zone
Time.zone = @current_user.time_zone if @current_user
end
With all this, rails and active record handle timezones for you. But be very careful when building SQL queries by hand.
ActiveRecord converts dates to UTC timezone before saving them to the database, and convert them back to the current timezone when you load the record from the database. And this can lead to something strange when doing queries like:
User.all(:conditions => ["created_at > ?", Date.today.at_beginning_of_day])
ActiveRecord converts date, but only when saving or loading records. But you can deal with it if you don't forget timezones:
User.all(:conditions => ["created_at > ?", Time.zone.now.at_beginning_of_day])
Something strange is that Rails adds timezone support to the Time class, but not to the date calculations:
>> Time.zone = 'Paris'
>> Date.today.to_s(:db)
=> "2010-02-05"
>> Time.now.to_s(:db)
=> "2010-02-05 00:07:49" # The same date everywhere
>> Time.zone.today.to_s(:db)
=> "2010-02-05"
>> Time.zone.now
=> Fri, 05 Feb 2010 00:06:13 CET +01:00 # All is right here
Let's make some calculations :
>> Time.zone.today.at_beginning_of_day.to_s(:db)
=> "2010-02-05 00:00:00" # Seems to be ok, but
>> Time.zone.now.at_beginning_of_day.to_s(:db)
=> "2010-02-04 23:00:00" # Woot ! the database is still yesterday !
Something you can do if you absolutely need to use Time.today (something stolen from Barry Hess):
class ::Date
def beginning_of_day_in_zone
Time.zone.parse(self.to_s)
end
alias_method :at_beginning_of_day_in_zone, :beginning_of_day_in_zone
alias_method :midnight_in_zone, :beginning_of_day_in_zone
alias_method :at_midnight_in_zone, :beginning_of_day_in_zone
def end_of_day_in_zone
Time.zone.parse((self+1).to_s) – 1
end
end