adding timezone to your rails app
Courtenay : February 23rd, 2007
This is a nasty job, no doubt about it. But, a necessary one, particularly since I set the
server time to UTC and all my applications' timestamps are now weirdly off-kilter. Unfortunately
the seminal article on this topic
got lost in the tubes so I'm documenting my experiences. Much of this was stolen from Jamis' announcement
of tztime
Install all the gems and supporting libraries. You may have some of these already.
# gem install tzinfo
TZInfo provides the various conversions and cross-platform magic. Now the plugin for your rails app,
$ script/plugin install tzinfo_timezone
which will give you some conversion helpers and such. Finally, the new Jamis Buck wizardry
$ script/plugin install tztime
which now should replace Time in your app, so it'll do a lot of conversion to and from UTC for you. This plugin is useful if you're calling Time in your application. Anywhere you called Time.now you want to call TzTime.now. We'll also be using it as a singleton store to keep the current request's timezone.
Now, you're ready for the pain. Set your server's timezone to UTC. Depending on OS and arch, this may be as simple as one of these methods
# cp /usr/share/zoneinfo/UCT /etc/localtime
or
# export ENV['TZ']
or
# tzselect
This is going to screw a lot of things up if you aren't already on UTC and have other apps running. Maybe. Or maybe your OS can handle it seamlessly (hopefully). Caveat hackor.
Open up environment.rb and uncomment this line (requires a restart)
config.active_record.default_timezone = :utc
Open up application_helper.rb and write yourself a handy conversion method.
def tz(time_at)
TzTime.zone.utc_to_local(time_at.utc)
end
Slick. We then need to configure the application to set the timezone.
class ApplicationController < ActionController::Base
before_filter :login_required
around_filter :set_timezone
private
def set_timezone
TzTime.zone = current_user.tz
yield
TzTime.reset!
end
end
You'll notice current_user has a tz field. Where does this come from? Add a string column to your the user model, called time_zone, and have it default to "UTC" or whatever your preferred default timezone is. FYI, this step depends largely on your application. You might have sites, stores or users as the top-level, so it may not be current_user but @site here.
Once you've added the time_zone column to the model, we need to have it parse the string representation of timezone info a proper zone.
class User < ActiveRecord::Base
composed_of :tz, :class_name => 'TZInfo::Timezone', :mapping => %w( time_zone time_zone )
end
This creates the @user.tz method. You'll want people to edit their timezone no doubt, so users/_form.rhtml gets a new field,
<label>Default timezone</label>
<%= time_zone_select 'user', 'time_zone', TZInfo::Timezone.all.sort, :model => TZInfo::Timezone %>
There are some custom timezone selectors out there, but this should get you started.
Finally, search all occurances of date fields in your application, something like
<%= created_on %>
<%= updated_at %>
and make them use the tz helper
<%=tz user.created_at %>
And that's it!
(photo by *eclaire on flickr.)
9 Responses to “adding timezone to your rails app”
Sorry, comments are closed for this article.
February 23rd, 2007 at 05:07 AM
Hi this is a nice post very usefull !
Where can I find the custom timezone selectors you are talking about.
I found the default list pretty long and would like a shorter liste like : GMT +1 (Paris, Madrid, Monaco…)
Thanks
February 23rd, 2007 at 06:57 AM
We use javascript to determine the time zone automatically from their system settings by calling
new Date().getTimezoneOffset(). Then we store this value in a cookie so that we can pass it back to the rails app on the server, which stays in our local timezone but formats times appropriately for the user’s settings.Obviously, the tradeoff is that your users must have javascript enabled and they must have their computer’s timezone settings correct. For our target audience, these are reasonable assumptions.
February 23rd, 2007 at 02:57 PM
I’d kill for this took hook into toformatteds instead of replacing all my time references in my views.
March 3rd, 2007 at 10:11 AM
Ok, I’m a newbie to RoR, so please bear with me…
In my app controller, my current_user is defined with:
So before the user logs in, there is no current user, and therefore, the system is unable to get a current time zone. I could include a conditional that defaults to a specific zone, but wondering if there is a best practices strategy for dealing with this?
March 3rd, 2007 at 02:14 PM
Eric, you can try to replace:
TzTime.zone = current_user.tz
with:
TzTime.zone = currentuser.tz if loggedin?
And add:
def logged_in? current_user != :false end
Meaning that time zone will not be set if no user is logged in the system.
March 7th, 2007 at 01:51 PM
great article, here’s the archived version of the Lunchroom article: http://web.archive.org/web/20060425190845/http://lunchroom.lunchboxsoftware.com/pages/tzinfo_rails
You’ve pretty much covered it but there it is for posterity.
March 9th, 2007 at 07:24 AM
Thank you for the clarification. Is there any way to adopt this solution if you can’t change the local machine time to utc for some reason.
March 9th, 2007 at 10:25 AM
Why, for the same hour of the day, the conversion change from winter to summer DST. I don’t get it. Is it a bug or is UTC different from GMT?
March 9th, 2007 at 10:27 AM
Here is my example again: