The TzInfo gem and plugins: tzinfo_timezone and tztime provide a bunch of timezone-related nifty things that intend to make your life easier by providing extra helper stuff on top of the standard Rails TimeZone class.
Personal preferences
However, I personally hate the dodgy-looking "Country/City" style of timezone list that the plugin provides. I much prefer "GMT+10:00 Sydney" provided with Ruby's TimeZone code. Especially when dealing with clients that can be expected to understand what a timezone is.
I figured I could take davantage of the niceties of TzTime/TzInfo for converting, displaying and updating activerecord etc... but use the standard Rails time_zone_select helpers on the user-preference form, and just convert the users chosen TimeZone string into a TzinfoTimezone before applying it.
But when I tried it, I suddenly started getting something along the lines of:
TZInfo::InvalidTimezoneIdentifier: no such file to load -- tzinfo/definitions/Tijuana
One big problem
Delving into the code, it seems that neither the TzInfo::Timezone class nor the tzinfo_timezone plugin's TzinfoTimezone class actually provide a method to convert from the standard Ruby TimeZone string and back... even though the plugin seems to do this internally (to generate the new list of timezones for the drop-down list).
This seems a bit of an oversight as the plugin already contains a mapping (conveniently named MAPPING) between the two.
The monkeypatch
The required code change is pretty small if done inside of tzinfo_timezone. The TzinfoTimezone class' new method tries to fetch out the appropriate timezone object, returning nil if not found. A one-line change fixes it so that after trying with the given timezone, it checks if it's in the given mapping before trying again thus:
# replace: def new(name) self[name] end # with: def new(name) self[name] || self[MAPPING[name]] end
The form drop-down
Adding the field to a user is fairly simple - assuming the string column is named "time_zone" - I use the pretty standard:
<%= time_zone_select 'user', 'time_zone', TimeZone.all, {:include_blank => true} -%>
The Controller changes:
According to the tutorial on using TzTime, we put an around_filter in the controller to prep TzInfo with the logged-in user's chosen timezone (or a default if they have none, or there's nobody logged in). Becuase the plguin doesn't directly update the TzInfo::Timezone class, we have to alter that to directly call our updated method.
# old way to set up default timezone around_filter :set_timezone def set_timezone # pull pref from the user - if they've supplied it if logged_in? && !current_user.time_zone.nil? TzTime.zone = TZInfo::Timezone.new(current_user.time_zone) else # otherwise use the environment's default = UTC TzTime.zone = TZInfo::Timezone.new(ENV['TZ']) end yield TzTime.reset! end # new way to set up default timezone around_filter :set_timezone def set_timezone # pull pref from the user - if they've supplied it if logged_in? && !current_user.time_zone.nil? TzTime.zone = TzinfoTimezone.new(current_user.time_zone) else # otherwise use the environment's default = UTC TzTime.zone = TZInfo::Timezone.new(ENV['TZ']) end yield TzTime.reset! end
A validation for good measure
just to add to the pile, a validation to make sure the user's chosen timezone actually exists...:
validates_each :time_zone do |model, attr, val| model.errors.add(attr, "is not a valid timezone") unless val.blank? || TimeZone.all.any? {|tz| tz.name == val } end
No comments:
Post a Comment