This post originated from an RSS feed registered with Ruby Buzz
by Eric Hodel.
Original Post: Timezones 1, Rails 0
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
For Trackmap I need to take the time a picture was taken and a user supplied time zone and convert that to a correctly offset time. Since Flickr won’t give me a time zone (cameras don’t record them) I ask the user. If they say Mountain time I need to create a time that is correct for the server’s clock (in my case Pacific Time).
Rails TimeZone
When I originally wrote the code back in November and December of 2005 I noticed that Rails provides the handy TimeZone class which worked great! Rails gave me handy offsets that I could use to adjust times correctly.
Then April rolled around, my clocks switched to daylight savings time, and Rails’ TimeZone class broke. TimeZone thought the offset for Pacific time was -8 hours instead of -7 hours giving inaccurate conversions. I filed a ticket, but it was closed WONTFIX (the breakage remains undocumented). I was, however, pointed at the TZInfo Timezone plugin. Since April I haven’t had much time to work on Trackmap, so I left it broken.
TZInfo Timezone Plugin
On Friday I switched over to the plugin and found it provides incorrect UTC offsets as a misfeature:
While the TZinfo Timezone plugin will convert times between known time zones, it doesn’t provide a way of interpreting a user-input time so you get a correctly offset time, making it only half of a fix for Rails’ TimeZone.
Interpreting Times
To handle user-input times you must use TZInfo::Timezone#period_for_utc or TZInfo::Timezone#period_for_local to retrieve the correct UTC offset. You can retrieve the TZInfo::Timezone object from TzinfoTimezone#tzinfo (which is all the utility the plugin provides).
Correct conversions require some work. I chose to do it this way:
Create a Time using the user-supplied time
Get the local TZInfo::TimezonePeriod for the timestamp
If a TimezonePeriod doesn’t exist for the given time, get the utc period
If the TimezonePeriod is ambiguous, choose standard time
Use TimezonePeriod#to_utc to adjust the Time to a UTC Time
A TimezonePeriod won’t exist during the standard time to daylight savings time switch, the user probably chose the wrong time zone. The TimezonePeriod is ambiguous during the daylight savings time to standard time switch since the user’s clock traverses the same time period twice. I chose to have strange behavior over failing. If the user notices and emails me I can say they probably fat-fingered something.
Rails Strikes Back
Having time interpretation finished I thought I was done. My database has timestamp with time zone columns so the database and Rails should just figure things out. Turns out Rails still can’t handle timestamp with time zone despite having a patch in the bug tracking database for three months.
Instead of applying the patch (which I’d have to do twice, once on my laptop and once online, and for every Rails upgrade until it gets checked in), I adapted it into a 75 line extension to the PostgreSQL connection adapter. Maybe I’ll make a plugin out of it while I’m waiting.
I’d like to think I was unlucky and the patch just got lost in the shuffle, but a look at the bug database makes me think that lost tickets happen frequently. One quarter of Rails’ defect tickets are open (1098 out of 4034) so I don’t see how anybody can figure out what tickets are invalid, valid, duplicate, fixed but not closed, or outright bogus. Furthermore, the amount of bugmail generated must be overwhelming.
The Rails core team should do what Mozilla did back in its early days and organize a bug day. Get a bunch of people on IRC to crawl through the open tickets to validate, cross-reference, prioritize and categorize bugs. (Mozilla even made it a weekly event with prizes!) Having a clean bug database will prevent useful patches from falling on the floor and rotting to the point that their authors must re-write them.