Ruby on Rails
LocalizingDates

The database I’m working on stores dates in the native (C-locale) format. In the frontend however, I prefer the German date-format DD.MM.YYYY over the default YYYY-MM-DD.

After browsing the Postgres-lists, it looks like you need to be more adventurous than me to run the database under a different locale. So I decided to change the representation only in the RoR-view. Of course, both rendering the new format and receiving it as input should work.

After fighting with RoR and using seperate text-fields for the dates (I can’t stand the RoR-way of representing dates, cf. my comment in HowtoUseDateHelpers) and converting them explicitly back and forth, I decided to give Aggregations a try: This should help me set up a shadow object for each date-object in a row (I need three). Additionally, these new shadow-object (called value-object in the docs) can be annotated with Validations, thus fitting really neat into the RoR-framework.

I was a bit disappointed with the Aggregations-docs because it was a bit sparse on what I needed (really converting and not only storing in separate attributes) and didn’t tell me where to put the files.

Modifying the Model

We will be working with the Bill-model, which has an attribute specifying the date the bill was issued. We’ll use the German word for date, which is datum. The table contains a column of the same name. The shadow-class we’ll develop will be called DeDateString. Note the require statement used to bring the shadow-class into scope.

Validation will be done on the shadow-representation.


class Bills < <a href="http://wiki.rubyonrails.org/rails/pages/ActiveRecord" class="existingWikiWord">ActiveRecord</a>::Base
  require 'dedatestring'
  ...
  composed_of :datum, :class_name => "DeDateString" , 
                      :mapping=> %w(datum dateIn)

  validates_format_of :datum, :with=> /^(\d+)\.(\d+)\.(\d+)$/,
                              :message => "- wrong dateformat"
  ...

The Shadow-Class

The shadow-class needs to provide the following:

The DeDateString-class should reside in lib/dedatestring.rb:


class <span class="newWikiWord">DeDateString<a href="http://wiki.rubyonrails.org/rails/pages/DeDateString">?</a></span>

  attr_reader :dateIn

  def initialize(intDate)
    @dateIn = date_INTtoDE intDate.to_s
  end

  def to_s
    @dateIn
  end

  private
  # Convert internal date-representation to German.
  def date_INTtoDE(d)
    d.sub(/(\w+)-(\w+)-(\w+)/, '\\3.\\2.\\1')
  end

end

class String
  def dateIn
    self.sub(/(\w+).(\w+).(\w+)/, '\\3-\\2-\\1')
  end
end

[ Should I wrap this in a Module so that the explicit namespace for the dateIn doesn’t cause any conflicts if somebody else decides to extend the String-class by the same method, too? ]

Rendering and Input

Any value you render should show the new representation.
For entering the new dates into forms, don’t use the text_field-helper: You will get the underlying attribute instead of the shadow object, i.e. the form will contain YYYY-MM-DD. Use the following partial instead which also explicitly handles the highlighting in case of input-errors. It uses text_field_tag explicitly.


# _date.rhtml
<% if @bill.errors[method] -%>
  <div class="fieldWithErrors">
<% end -%>
<%= text_field_tag "bill[#{method}]", @bill.send(method) , {:size=>10,:maxsize=>10} %>
<% if @bill.errors[method] -%>
  </div>
<% end -%>

[ My gut-feeling says that @textfield@ is misbehaving. Any thoughts on that?_ ]

To Do

Conclusion

This should serve you as a short tutorial on Aggregations. I hope you find it valuable, and please do post suggestions.
Being a complete Ruby-newbie, there should be lots of space for improvement! I’m also grateful on feedback if this has been helpful to you.

Or simply use one of the rails plugin to handle that for you such as GlobaLite localization made simple and lite. Read more

In the long run, I hope that RoR gets localization and Internationalization natively. — vs

Is the whole point of it that your database can’t store dates before the epoch? Why are ISO date formats every database uses internally unfit for your needs? If you don’t need to have dates before the epoch in your database then what is the point of this helper? —Julik

Answer: No, I just needed the localized date on screen for output AND input, without having to make any changes to the PostgresDB. — vs

An alternative?: I landed on this page because rails and postgresql defaulted to YYYY-MM-DD format and the users for my application preferred to interact with dates in the MM/DD/YYYY format. After reading this page and some mailing list stuff, I thought every solution suggested looked too complex. After poking around in date.rb I came up with a much simpler solution that seems to be working fine, although I (like 90% of rails developers) am new to ruby and rails so perhaps I’m being naive? Here’s my solution.

In [RAILS_APP]/lib/us_date_format.rb:


class Date
  def to_s
    strftime('%m/%d/%Y')
  end
end

In [RAILS_APP]/app/controllers/application.rb:


require 'us_date_format'

This causes the ruby Date class to display in MM/DD/YYYY format by default. The Date class already automagically parses MM/DD/YYYY correctly, so no further work is needed there. I think perhaps vs needed to do all of the work above because the german format DD.MM.YYYY is not handled already by the Date class?
DavYaginuma

Answer: Yes, that is pretty much the point. — vs

UPDATE TO ALTERNATIVE: I just realized that my alternative approach doesn’t work right with FormHelper.text_field because it gets its value set via object.#{method}_before_type_cast which is just the raw string from the database (which is still in the ‘YYYY-MM-DD’ format). I’ll update again when I get this figured out.
DavYaginuma

For different output date formats, you can put e.g. this in environment.rb:

ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
	:date => "%Y-%m-%d",
	:presentable_datetime => "%a %b %d, %Y %H:%M",
	:presentable_date => "%a %b %d, %Y"
)

and use the formats like


myTimeObject.to_s(:presentable_date)

— HenrikN

I prefer using the Globalize Plugin for this sort of Localization.

In environment.rb:

include Globalize
Locale.set_base_language('no-NO')
Locale.set('no-NO')

Then, when printing dates, use i.e.


<%= booking.from_date.localize %>

Or use a helper like:


module ApplicationHelper
  def fmt_date(date)
    # TODO: add some format = {} option
    # for optional: date.localize(format) 
    date.localize("%d. %B, %Y")
  end
end

I also like overriding default errors, even though this ideally should be done in a translation using Globalize, I currently add the following to environment.rb:


Locale.set('no-NO')

  1. Norwegian ActiveRecord errors:
    ActiveRecord::Errors.default_error_messages = \
    @@norwegian_error_messages = {
    :inclusion => “finnes ikke i listen”,
    :exclusion => “er reservert”,
    :invalid => “er ugyldig”,
    :confirmation => “er ikke overens med bekreftelsen”,
    :accepted => “må bekreftes”,
    :empty => “kan ikke være tom”,
    :blank => “kan ikke være blank”,
    :too_long => “er for lang (maksimalt %d tegn)”,
    :too_short => “er for kort (minimum %d tegn)”,
    :wrong_length => “har feil lengde (må være %d tegn)”,
    :taken => “er allerede i bruk”,
    :not_a_number => “er ikke et tall”
    }

—oc