Posted 6 months ago. Visible to the public. Repeats.

Working with or without time zones in Rails applications

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:

Copy
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.


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)
Copy
>> 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
Copy
>> 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

Copy
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.

Copy
>> 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
Copy
>> 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

Copy
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.

Copy
>> 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
Copy
>> 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

Copy
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.

Copy
>> 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
Copy
>> Time.now => 2017-06-12 10:00:00 +0200 >> Time.current => Mon, 12 Jun 2017 10:00:00 CEST +02:00

makandra has been working exclusively with Ruby on Rails since 2007. Our laser focus on a single technology has made us a leader in this space.

Author of this card:

Avatar
Arne Hartherz
Last edit:
6 months ago
by Besprechungs-PC
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Arne Hartherz to makandropedia