Formatting Time Zones and Datetime in Rails 3

Mark Berry March 22, 2012

Here is my current approach for formatting time zone and date/time values. I’m using Rails 3.1.3, SimpleForm 2.0.1, and Twitter Bootstrap 2.0.1. I had some trouble finding examples of how to do this so maybe this will help someone.

Time Zone

config/locales/datetime.yml

en:
  time_zone:
    priority: US|Hawaii|Alaska|Arizona|Indiana

app/views/_user_fields.html.erb

<%= f.input :time_zone, :required => true,
            :priority => /#{t('time_zone.priority')}/,
            :input_html => { :class => "input-xlarge" } %>

The trick here is using string interpolation inside a regular expression to provide the priority list.

DateTime Formats

config/locales/datetime.yml

en:
  time:

    # quote value so brackets not interpreted as YAML list:
    never_msg: '[never]'
    format_labels:
      mdyslash12: mm/dd/yyyy - hh:mm am (12-hour)
      mdyslash24: mm/dd/yyyy - hh:mm (24-hour)
    formats:
      mdyslash12: ! '%m/%d/%Y %I:%M%p'
      mdyslash24: ! '%m/%d/%Y %H:%M'

Here I’ve created two possible custom datetime formats and a “never_msg”.

app/views/_user_fields.html.erb

<%= f.input :datetime_format, :collection => t('time.format_labels'), 
    :required => true,
    :value_method => :first, :label_method => :last,
    :input_html => { :class => "input-xlarge" } %>

Here the trick is that t('time.format_labels') returns a hash of keys and values. And each element of that hash is returned as 2-item array. So for the select list values, we want the hash key, i.e. the :first item in the array, and for the select list labels, we want the hash value, i.e. the :last item in the array.

Another alternative would be to actually display the current time in localized form in the select list:

<%= f.input :datetime_format, :collection => t('time.format_labels'), 
    :required => true,
    :label_method => lambda { |i| I18n.localize DateTime.now.in_time_zone
                                             (current_user.time_zone),
                                  :format => i.first },
    :input_html => { :class => "input-xlarge" } %>

The :format option of the I18n.localize method tells it what format to use for displaying the date/time. (See guide.) :format expects a symbol. Here we take advantage of the hash keys already being symbols, so we just retrieve they key with .first). By the way, current_user comes from the Devise authentication plugin.

Finally, to just display date/time values (as opposed to putting them in a select list in a form), I wrote a little helper:

app/helpers/application_helper.rb

# Localize a datetime value with a standard nil message 
# and a default format
def l_dt(datetime, nilmsg = I18n.t('time.never_msg'), 
    format = @datetime_format)
  datetime ? I18n.l(datetime, :format => format) : nilmsg
end

Then to display a localized date/time, I just call that helper like this:

<%= l_dt @user.current_sign_in_at %>

I18n.localize returns an error if you pass it a nil string. The l_dt helper takes care of that by using a “never_msg” string, also retrieved (and optionally translated) from the locale file.


Leave a Reply





*