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.currentwill be UTC -
Time.nowwill 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::TimeWithZoneobjects -
Time.currentwill be in the configured default time zone -
Time.nowwill 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
Timeobjects
But:
-
Time.currentwill 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
Timeobjects -
Time.currentwill 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