This article outlines some basic guidelines to follow when working with timezones in Ruby on Rails applications. Ruby on Rails has great support for timezones, but getting it working correctly can be tricky. Following the techniques below should save you some headache.
- Application Settings – use the default UTC settings
- Set each request to the logged on users timezone
- Display times and dates using the I18n view helpers
- Override user timezone when displaying time aware entity
- Override user timezone when saving time aware entity
- Filter data based on the users timezone
Is it ‘time zone’, ‘timezone’ or ‘time-zone’? Both styles end up being used in rails code base
config.time_zone
andconfig.active_record.default_timezone
. I use timezone in this article and time_zone in the sample code base. Read more on stackexchange.
Sample code can be found at: https://github.com/house9/sample_timez
The sample includes user profiles with timezone settings, an event model, and a work schedule model. It comes with a small set of sample data as well. To set it up locally, follow the README instructions.
Application Settings
Rails uses the following defaults for a new application
- Application assumes it is running in UTC
- Database assumes it is storing data in UTC
I recommend you keep these defaults!
in rails console (default settings)
1 2 3 4 5 |
|
Both can be overriden in your application configuration file (config/application.rb) – don’t do it!
1 2 3 |
|
Set the timezone for each request
How you determine the ‘current users’ timezone will differ from application to application. In the sample code we are storing the value on the user model in a time_zone field. The sample application uses the techniques from this RailsCast episode. Alternatively you may want to set it based on the client’s browser setting.
Use an around_filter
or combo of before_filter
and after_filter
to set each request’s timezone in the ApplicationController
. If we don’t have a current user it will default to UTC.
1 2 3 4 5 6 7 8 9 10 |
|
Display times and dates using the I18n view helpers
The I18n
helpers are timezone aware. Aside from rendering the datetimes in the logged on users locale format, they will convert the stored UTC times to the current threads timezone.
- http://guides.rubyonrails.org/i18n.html#adding-date-time-formats
- https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale
On the homepage of the sample application there are numerous examples, also check out the config/locales/en.yml
file.
Basic example:
1
|
|
Beware: I18n.localize does not handle nil values. You will need to guard against nils for nullable columns.
Override the display of dates
In the sample application we have an Event model and a WorkSchedule model. Each model has its own timezone. Events are based on where the event will actually occur. In this case displaying the dates in the users timezone makes no sense at all, so we must override the display. There are a few techniques that can be employed:
- in_time_zone method (see work schedule in the sample application)
- Time.use_zone blocks (see events in the sample application)
in_time_zone
example: WorkSchedule overrides the start_at and end_at model attributes, therefore no special handling is needed in the views (of course you still need to use I18n.localize).
1 2 3 4 5 6 7 8 9 |
|
Time.use_zone
example: The Event model does not override start_at or end_at attributes, but uses Time.use_zone blocks in the views.
<!-- displayed times in the events timezone -->
<% Time.use_zone(@event.time_zone) do %>
<%= l(@event.start_at) %>
<%= l(@event.end_at) %>
<% end %>
<!-- now back to the logged on users timezone -->
<p>
Created at <%= l(@event.created_at) %>
Updated at <%= l(@event.updated_at) %>
</p>
There is odd behavior with Time.use_zone when doing in-memory sorting in ruby (sort_by
). See the HomeController
in the sample application. Ordered attributes do not convert in the use_zone block. I am not sure if this a bug or by design.
Override timezone when saving data
Sometimes you want to override saving data. Let’s say my profile is set up with ‘Pacific’ timezone, but I am creating an event that will occur in New York (‘Eastern’ timezone). I do not want rails to convert the times to ‘Pacific’. We can reset the current threads timezone to the events timezone before the create and update actions execute.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Alternatively this could be done using Time.use_zone
blocks in the create and update actions.
Filter data based on the users timezone
Rails will handle most of this automatically if you don’t use raw sql. For example:
1
|
|
If you are filtering datetime data by the day be careful, the below queries are from the sample application logged in as a user with (GMT+06:00) Astana
timezone
1 2 3 4 5 6 |
|
the proper way
1 2 3 4 5 6 |
|
This is not an issue when filtering on date columns (only datetime).
1 2 3 4 |
|
In some situations you might need to query for data based on a specific timezone
1 2 3 4 5 6 7 8 |
|
Conclusion
I hope you found this article helpful. All feedback is welcome – I am sure I have left out some key information or just plain got it wrong.
Additional Reading: http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails
ETC… (links and resources)
1
|
|
1 2 |
|
- API: DateTime
- API: time_zone_select
- Stackoverflow: Timezone confusion
- Stackoverflow: More timezone confusion
- Postgresql: AT TIME ZONE
- datetime_select preselects wrong times upon edit
1 2 3 4 |
|