Supporting timezones is a pain in the ass, but it’s a necessary evil for some sites and there are two working solutions as I see it.
Observe:The first one involves a fair amount of setup including one gem and two plugins, and the second one is too slow.
Err’s gonna give you a third option: zoned
We take the best of both worlds and mash them together into a plugin that consists of about 10 lines of ruby and the same amount of javascript.
We’ve been using it on Chowhound for the past month with positive results, even through the dreaded DST change. Here’s how it works:
The Javascript
var Timezone = { set : function() { var date = new Date(); date.setTime(date.getTime() + (1000*24*60*60*1000)); var expires = "; expires=" + date.toGMTString(); document.cookie = "timezone=" + (-date.getTimezoneOffset() * 60) + expires + "; path=/"; } }
This sets a timezone cookie with the value returned from the essential date.getTimezoneOffset() function. This is important because you need the client’s offset, ruby can’t help you here.
The Ruby
module Zoned mattr_reader :server_offset @@server_offset = Time.now.gmtoff def zoned(date) return date unless timezone = controller.send(:cookies)[:timezone] date - (server_offset - timezone.to_i) end alias :z :zoned endAll this does:
- Returns the date untouched if the cookie isn’t set
- Determines the server’s GMT offset
- Takes the difference of the server’s offset with the client’s offset and the date passed
The second bullet point is unnecessary, but an added bonus for anyone with a server not running UTC which is a requirement for almost all other timezone solutions.
How we’re using it
Login/Signup:<% form_for @login, { :html => { :onsubmit => "Timezone.set()" } %>Logout:
cookies.delete(:timezone)Views:
<%= z(post.created_at).to_s(:long) %>
Final thoughts
There are some issues you have to be cool with if you use this plugin (or come up with a solution):
- None of the dates will convert if the client’s javascript is disabled
- When the date is converted, it will still think it’s in the original timezone
- If DST happens or they change their computer’s timezone, they will need to log out and log back in to update their timezone cookie
- If their computer’s timezone is set incorrectly, so will the updated time
In the case of something major like Daylight Saving Time, making sure their timezone cookie has been reset is as easy as changing the salt value used in our cookies to invalidate all of the existing ones, thus forcing them to log back in.
Per usual, install via:
./script/plugin install svn://errtheblog.com/svn/plugins/zoned
Overtime
You can convert everyone’s timezone (not just those logged in) via:
window.onload = Timezone.set
On the first page load it will show the server’s timezone, but on any subsequent loads the time will convert correctly.
Another idea is just to make an Ajax.Request in Timezone.set call an action that will save the user’s offset in the database so you don’t have to rely on a cookie.
Think of this plugin more as the building block that’ll cater to whatever suits your site best. Let us know how you’re using it.
Are you aware of Jamis Buck’s TzTime plugin?
Interesting take on the timezone thang. Thought of using window.onload to handle resetting of the cookie on DST changes?
http://mikewest.org/archive/showing-perfect-time-unobtrusively
John and Ian, those solutions are the two I mentioned at the beginning of my post.
Tim, that’s a much better practice than the one I suggested, thanks.
Shouldn’t the timezone offset be taken from the date before setting it to 1000 days in the future? It’s very likely that the future date will not have the same GMT offset as today (1000 days from now is December 16 – which does not have DST).
Awesome catch Evan, I’ve updated the plugin to eliminate that bug.
Nice!
Great plugin!
For some reason though I can’t get this plugin to work on my machine. =(
The javascript works. The code inside the module works on its own. But when I use it, it doesn’t do any conversion from the view.
Is it because I’m still running ruby 1.8.2? (I’m running the latest version of rails)
I like this approach – but I’d also like to show the user what time zone the displayed times are in (both GMT offset and timezone abbreviation); I’m not convinced that everyone will be expecting to see times correctly changed to their locale.
A possible solution would be to set another cookie containing Javascript’s opinion of their timezone—something like:
(I’m not convinced if that always works, but seems ok for me based on a few timezone changes)
And then add a helper method that displays the result – something like “GMT+0100 (BST)” or “GMT-0400 (EDT)”.
Adding as a helper means one can display the timezone separately – for example, in the header of a column of times – rather than always next to the time in question.
Any other ideas or better solutions?
I’ve played a bit with what NeilS suggest in ActiveSupport for JavaScript and only found how inconsistent browsers are. Firefox even behaves different between Mac and Win :-/
Both IE (6 and 7) and Firefox/Win suffer from not showing the timezone name, instead showing something like GMT+0300 (heh, more amusing even, one shows +0300 and the other +03:00). So parsing that kinda sucks.
When I sort out a bit of stuff at work I’ll get back to AS4JS. Next version will have a lot more date mojo, including as much tz management as I can get done.
I tried the url for the plugin: http://require.errtheblog.com/plugins/browser/zoned and it’s down.. I think I read elsewhere you might be moving to GitHub though?
Chime in.