Rails supports time zones, but there are several pitfalls. Most importantly because Time.now
and Time.current
are completely different things and code from gems might use one or the other.
Especially configuring an application that cares only about one time zone is a bit tricky.
The following was tested on Rails 5.1 but should apply to Rails 4.2 as well.
Using only local time
Your life will be easier if your application does not need to support time zones. Disable them like this:
config.time_zone = 'Berlin' # Your local time zone
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_attributes = false
Note that we disable ActiveRecord's time zone logic while still configuring a time zone.
This seems odd at first, but is required for Time.current
to work properly.
If possible, put your server into local time zone, since Ruby's Time
will use that. In the example above, it should be in the Berlin zone.
Using time zones
If your application should support time zones, the default ActiveRecord configuration will respect a config.time_zone
. If you do not configure a default time zone, all times will be UTC.
You need to use Time#in_time_zone
or ActiveSupport::TimeWithZone#in_time_zone
on all time objects to convert them to a user's current time zone.
Configuration Examples
Read on for examples on different configurations and what will happen and/or go wrong.
Without any time zone configuration
This happens:
- ActiveRecord writes UTC to the database
- ActiveRecord time attributes are ActiveSupport::TimeWithZone objects
But:
- ActiveRecord will not convert time attributes. They will be UTC and you need to explicitly convert them.
-
Time.current
will be UTC -
Time.now
will be your local time (or the local time of your server)
>> Project.create!
>> Project.connection.select_one('SELECT created_at FROM projects')
=> {"created_at"=>"2017-06-12 08:00:00.000000"}
>> Project.last.created_at
=> Mon, 12 Jun 2017 08:00:00 UTC +00:00
>> Time.now
=> 2017-06-12 10:00:00 +0200
>> Time.current
=> Mon, 12 Jun 2017 08:00:00 UTC +00:00
With a configured (default) time zone
config.time_zone = 'Berlin'
This happens:
- ActiveRecord writes UTC to the database
- ActiveRecord converts time attributes back to the configured default time zone
- ActiveRecord time attributes are
ActiveSupport::TimeWithZone
objects -
Time.current
will be in the configured default time zone -
Time.now
will be your local time (or the local time of your server)
While this might be okay even for the "we don't need time zone" use case, UTC timestamps in the database are harder to read for humans.
>> Project.create!
>> Project.connection.select_one('SELECT created_at FROM projects')
=> {"created_at"=>"2017-06-12 08:00:00.000000"}
>> Project.last.created_at
=> Mon, 12 Jun 2017 10:00:00 CEST +02:00
>> Time.now
=> 2017-06-12 10:00:00 +0200
>> Time.current
=> Mon, 12 Jun 2017 10:00:00 CEST +02:00
When ActiveRecord's time zone logic is disabled, and no time zone is configured
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_attributes = false
This happens:
- ActiveRecord writes local time to the database
- ActiveRecord time attributes will be read correctly in local time
- ActiveRecord time attributes are
Time
objects
But:
-
Time.current
will be UTC
While ActiveRecord seems to be working alright, using Time.current
is incorrect in this case and would cause problems.
>> Project.create!
>> Project.connection.select_one('SELECT created_at FROM projects')
=> {"created_at"=>"2017-06-12 10:00:00.000000"}
>> Project.last.created_at
=> 2017-06-12 10:00:00 +0200
>> Time.now
=> 2017-06-12 10:00:00 +0200
>> Time.current
=> Mon, 12 Jun 2017 08:00:00 UTC +00:00
When ActiveRecord's time zone logic is disabled, and a local time zone is configured
config.time_zone = 'Berlin'
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_attributes = false
This happens:
- ActiveRecord writes local time to the database
- ActiveRecord time attributes will be read correctly in local time
- ActiveRecord time attributes are
Time
objects -
Time.current
will be in the configured (local) time zone
As described above, this is our suggested configuration for an application that needs to support only one time zone.
Note that you can not distinguish times around a DST switch from summer time to winter time, as any time between 2 and 3 AM could be either!
>> Project.create!
>> Project.connection.select_one('SELECT created_at FROM projects')
=> {"created_at"=>"2017-06-12 10:00:00.000000"}
>> Project.last.created_at
=> 2017-06-12 10:00:00 +0200
>> Time.now
=> 2017-06-12 10:00:00 +0200
>> Time.current
=> Mon, 12 Jun 2017 10:00:00 CEST +02:00