Bridge Globalize and TZInfo
The Globalize plugin integrates Internationalization (i18n) and Localization (L10n) by default with very little effort.
However, its trivial to bridge the Globalize::Country model with the TZInfo gem and tzinfo_timezone plugin and have DRY timezones indexed by country, with daylight savings time covered.
TZInfo gem
An OS independent (no zoneinfo files required) library that provides daylight savings aware conversions.
Install with:
gem install tzinfo
tzinfo_timezone plugin
A replacement for Rail's bundled TimeZone class, released by Jamis Buck.Note the depracated
TimeZone#unadjust and TimeZone#adjust needs to be replaced with TimeZone#localto_utc and TimeZone#utcto_local respectively.
Install with:
ruby script/plugin install tzinfo_timezone
The hacks
In a perfect world this could be wrapped in a plugin, but implementation is fairly application specific, thus the following is just to illustrate the concept.Adapt accordingly.
Make Globalize::Country Timezone aware
Wrab this in a library required by your environment.rb
Globalize::Country.class_eval do
#Add associations specific to your application
has_many :accounts, :class_name => 'Account'
has_many :addresses, :class_name => 'Address'
def timezones
TZInfo::Country.get( self.code ).zone_names
end
end
Which should yield the following from the console:
>> Globalize::Country.find_by_code('PT').timezones
=> ["Europe/Lisbon", "Atlantic/Azores", "Atlantic/Madeira"]
Adding a Timezone to your Account ( User etc. )
Application specific models:
class Account < Activerecord::Base
belongs_to :country, :class_name => 'Globalize::Country'
has_one :setting, :dependent => :destroy
delegate :tz, :to => :setting
end
class Setting < ActiveRecord::Base
belongs_to :account
composed_of :tz, :class_name => 'TZInfo::Timezone',
:mapping => %w(timezone time_zone)
end
The user interface
Controller action:
class Admin::SettingController < Admin::BaseController
def edit
#@account instance variable stubbed - set in inherited before_filter
@setting = @account.setting
%w(account setting).collect{|o| instance_variable_get( "@#{o}".to_sym ).send( :attributes=, params[o.to_sym]) }
if request.post? && [@setting, @account].reject(&:save).empty?
#stubbed for clarity
end
end
end
Helper methods:
def country_to_currency_map
map ||= {}
Globalize::Country.find(:all).each{|c| map.store(c.id, c.currency_code) }
map.to_json
end
def country_to_timezone_map
map ||= {}
Globalize::Country.find(:all).each{|c| map.store(c.id, c.timezones) }
map.to_json
end
The view:
#begin form_for block ( customer form builder, ignore hash options )
<% fields_for 'account', @account do |a| %>
<%= a.select( 'country_id', Globalize::Country.find(:all).collect{|c| ["#{c.code} - #{c.english_name}", c.id]}, { :label => 'Country'.t, :required => true, :description => "( currency code #{@account.country.currency_code} )" } ) %>
<% end %>
<%= s.select( 'timezone', @account.country.timezones.collect{ |t| [t,t] }, { :label => 'Time Zone', :required => true } ) %>
<%= javascript_tag("new Country( #{country_to_currency_map()},
#{country_to_timezone_map()} );") %>
#other fields here and end form_for block
Javascript for good measure:
var Country = Class.create();
Country.prototype = {
initialize: function( locale_data, tz_data ){
this.locale_data = $H(locale_data);
this.tz_data = $H(tz_data);
$('account_country_id').onchange = this.update.bindAsEventListener(this);
},
update: function(){
this.update_currency();
this.update_timezones();
},
update_currency: function(){
$('account_country_id_description').update( '( currency code ' + this.currency_for_country() + ' )' );
new Effect.Highlight( 'account_country_id_description', { duration: 0.8 })
},
currency_for_country: function(){
var country_hash = this.locale_data.detect(function(i,index){ if( i.key == $F('account_country_id') ){ return true } })
return country_hash.value
},
update_timezones: function(){
var template = '';
$('setting_timezone').replace( template.gsub('#{options}', this.build_time_zones_for_country() ) );
new Effect.Highlight( 'setting_timezone', {duration: 0.8} )
},
build_time_zones_for_country: function(){
var country_hash = this.tz_data.detect(function(i,index){ if( i.key == $F('account_country_id') ){ return true } });
var options = '';
country_hash.value.each(function(i,index){ options += '' })
return options
}
}
The result:


About this entry
You’re currently reading “Bridge Globalize and TZInfo,” an entry on Rails, Ruby & Prototype ramblings
- Published:
- October 13th 12:56 AM
- Updated:
- October 13th 05:25 AM
- Sections:
- Globalize
1 comment
Jump to comment form | comments rss [?]