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.
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 needs to provide the following:
initialization, which receives an argument of type Date and converts it to the German representation and stores it internally,to_s method, which simply renders the shadow representation,String-class which casts the internal representation back into a string that Date can handle.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? ]
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?_ ]
. or ,. This would save our staff some hassle. For the time being, they have to cope with having to enter the decimal digit, though.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.
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')
- 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
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.
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 needs to provide the following:
initialization, which receives an argument of type Date and converts it to the German representation and stores it internally,to_s method, which simply renders the shadow representation,String-class which casts the internal representation back into a string that Date can handle.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? ]
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?_ ]
. or ,. This would save our staff some hassle. For the time being, they have to cope with having to enter the decimal digit, though.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.
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')
- 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