Rails 2.1 Time Zone Support: An Overview
A Portuguese translation of this article can be found here.
This will be the first of several posts I’ll create about the new time zone features in the upcoming Rails 2.1 release. In this post, I’ll give an overview of the new features, by walking through the setup of a new app.
I’ll start with a fresh Rails 2.1 app created via the rails command. In 2.1, time zone support will be turned on by default in environment.rb, via the config.time_zone option:
config.time_zone = ‘UTC’
– this will be set to UTC as the default, but you’ll most likely want to set this to a time zone appropriate for your locale. The new rake tasks time:zones:all, time:zones:us, and time:zones:local have been added to help one find appropriate time zone names. time:zones:local will make an educated guess based on the system local time, so that’s a good place to start:
* UTC -06:00 *
Central America
Central Time (US & Canada)
Guadalajara
Mexico City
Monterrey
Saskatchewan
For this example, I’ll set config.time_zone to US Central Time:
config.time_zone = ‘Central Time (US & Canada)‘
Next, I’ll create a simple scaffold for a Task model with an alert_at datetime attribute:
$ rake db:migrate
$ script/server
I’ll go to the new task form, and create a new task:

The show action displays the date and time I entered, followed by the UTC offset:

… for this example, the UTC offset is -0500, which is the offset for US Central Time during daylight savings.
To show how this time is stored in the database, I’ll go to script/console, and check #alert_at_before_type_cast:
=> #< Task … >
>> t.alert_at
=> Sun, 06 Apr 2008 10:30:00 CDT -05:00
>> t.alert_at_before_type_cast
=> "2008-04-06 15:30:00"
The database is storing the UTC representation of our time: 15:30 UTC is simultaneous with 10:30 CDT (Central Daylight Time.) The difference between the two times is the UTC offset (-5 hours, in this case.)
Next, I’ll edit the task and change the month to January:

Notice the offset parameter is -0600 now — that’s because the alert_at date is no longer in daylight savings time.

script/console will confirm that the database received the correct UTC representation:
=> #< Task … >
>> t.alert_at
=> Sun, 06 Jan 2008 10:30:00 CST -06:00
>> t.alert_at_before_type_cast
=> "2008-01-06 16:30:00"
The database time is now 16:30 instead of 15:30, because the UTC offset is -6 hours now.
User-specific time zones
What I’ve set up so far will work fine for an app where all of its users are in one time zone. If the app eventually needs to support users in different time zones, that’s easy enough to add.
First, I’ll create a user scaffold, with a string attribute to store the user’s time zone
$ rake db:migrate
I’ll change the user create and edit forms to use the time zone select instead of a text field:
<%= f.time_zone_select :time_zone, TimeZone.us_zones %>
The new user form now looks like this — I’ll use it to create a couple users with different time zones:

For demo purposes, I’ll add a simple login_from_querystring before_filter to the application controller:
before_filter :login_from_querystring
def login_from_querystring
@current_user = User.find_by_name(params[:user])
end
I’ll then add a set_time_zone before_filter, which will set Time.zone to the current logged-in user’s time zone:
before_filter :set_time_zone
def set_time_zone
Time.zone = @current_user.time_zone if @current_user
end
I’ll add a header in the layout to show who’s logged in, their time zone, and the current time in their zone:
Current user: <%= @current_user.name if @current_user %>
Current time zone: <%= Time.zone.name %>
Current time: <%= Time.zone.now.inspect %>
<hr />
Finally, I’ll change the tasks index view to use the #inspect representation of alert at, to reveal additional detail:
<%=h task.alert_at.inspect %>
Now, if I log in as one of the users I created, I’ll see the task I created before, with the alert_at time adjusted to the current user’s time zone:

… notice the time displayed for the task is 11:30 EST — that’s simultaneous with 10:30 CST.
For a user in US Mountain Time, the task displays as 9:30 MST:

Without a logged in user, the time zone defaults to the zone set in config.time_zone:

Methods for creating times in the current Time.zone
So far, we’ve been relying on ActiveRecord to automatically convert model attributes to the user’s local time. For cases where you need to create new time instances in the user’s local zone, the methods Time.zone.local(), Time.zone.parse() and Time.zone.at() are available, as well as Time.zone.now:
=> "Hawaii"
>> Time.zone.now
=> Wed, 09 Apr 2008 15:48:18 HST -10:00
>> Time.zone.local(2008, 4, 9, 15, 48, 18)
=> Wed, 09 Apr 2008 15:48:18 HST -10:00
>> Time.zone.parse(‘2008-04-09 15:48:18‘)
=> Wed, 09 Apr 2008 15:48:18 HST -10:00
>> Time.zone.at(1207792098)
=> Wed, 09 Apr 2008 15:48:18 HST -10:00
Time and DateTime #in_time_zone will convert any instance to the zone stored in Time.zone:
=> "Alaska"
>> t = Time.utc(2000)
=> Sat Jan 01 00:00:00 UTC 2000
>> t.in_time_zone
=> Fri, 31 Dec 1999 15:00:00 AKST -09:00
…or, to any zone or zone identifier (i.e., name, integer or Duration) passed in as an argument:
=> Fri, 31 Dec 1999 14:00:00 HST -10:00
>> t.in_time_zone(-6.hours)
=> Fri, 31 Dec 1999 18:00:00 CST -06:00
Tips Upgrading Existing Apps
- the new time zone features assume that the database is storing times in UTC, so if you’ve currently storing times in the database in a zone other than UTC, you’ll need to migrate existing data to UTC
- if the tzinfo_timezone plugin is installed, you’ll need to remove it, given that it overrides the TimeZone class in ActiveSupport
- the TZInfo gem is no longer required, given that it’s now bundled in ActiveSupport. However, if you do have a recent version of this gem installed, Rails will favor the gem over the bundled version.
- The bundled TZInfo is a slimmed-down version of the gem, so if you’re interacting with the TZInfo API directly, you should have the gem installed
- If you *don’t* wish to use the new time zone features — the new features shouldn’t interfere with your existing code, as long as you don’t declare config.time_zone in environment.rb
More To Come
In future posts, I’ll try to cover more of the under-the-hood stuff, but hopefully this post will help to get folks up to speed.
If you find time zones, UTC offsets, and daylight savings time confusing, you might want to check out these Time Zone Visualizations, which might just confuse you even more…
Related Posts:
Making Rails time zone aware attributes and Chronic play well together
Two fixes to ActiveSupport::TimeWithZone
38 Comments