Need to display a date in your Rails app? First try:
Time.now.strftime "arrrround %H'ish"
To remove duplication, you imagine a time format initializer. Second try:
Time.now.to_s :pirate
Then you remember the :default localized key. Third time’s a charm:
l Time.now
It formats based on the config/locales/en.yml file:
en:
time:
formats:
default: "arrrround %H'ish"
date:
formats:
default: "arrrround %H'ish"
Dates work, too:
l Date.today
The same system lets you alter the display of “days ago” and “minute from now.”
I needed an open? method. First try:
def open?
opens_at < Time.now < closes_at
end
However, Ruby doesn’t support that kind of expression. Second try:
def open?
(opens_at < Time.now) && (Time.now < closes_at)
end
It’s noisy and lacks expression. Third time’s a charm:
def open?
Time.now.between? opens_at, closes_at
end
Ship it.
Date::DATE_FORMATS is quite helpful. It lets you do this:
Date.today.to_s(:custom)
with only this code in config/initializers/date_format.rb:
Date::DATE_FORMATS[:custom] = "%Y-%m-%d"
To do the same thing for DateTime instances, like created_at columns, use Time::DATE_FORMATS.
Time::DATE_FORMATS
To set the default format for either one, set DATE_FORMATS[:default]. This will cause <%= item.created_at %> to output “2011-08-16”, with no extra work from you.
Fred asked if we could shorten the “about 1 month ago” text in an information-rich table that was squeezing the created_at data.

I’m not familiar with the i18n API beyond supporting internationalization in Clearance. Therefore, it took me mildly by surprise when I learned that you can override distance_in_words with the i18n API (and, by extension, time_ago_in_words).
i18n is the right tool to solve a problem like this even in an English-only application. A quick change to config/locales/en.yml, however, and we’re done:
en:
datetime:
distance_in_words:
less_than_x_seconds:
other: '1 minute'
half_a_minute: '1 minute'
less_than_x_minutes:
one: '1 minute'
x_minutes:
one: '1 minute'
other: '{{count}} minutes'
about_x_hours:
one: '1 hour'
other: '{{count}} hours'
about_x_months:
one: '1 month'
other: '{{count}} months'
about_x_years:
one: '1 year'
other: '{{count}} years'
over_x_years:
one: 'over 1 year'
other: 'over {{count}} years'
The result:

You can see other options in svenfuchs/i18n.
I want to format a date range in a view for our training app. This is a job for helpers. A fundamental skill for Rails developers is writing the helper using TDD. This process takes about 10 minutes and results in a confidence-building regression suite.
Working outside-in, we start in the interface:
<%= format_date_range(course.date_range) %>
Tests fail, “format_date_range” doesn’t exist.
script/generate helper date_time
Produces:
# app/helpers/date_time_helper.rb
module DateTimeHelper
end
# test/unit/helpers/date_time_helper_test.rb
require 'test_helper'
class DateTimeHelperTest < ActionView::TestCase
end
Incidentally, former thoughtbot intern Eugene Bolshakov wrote the Rails patch that creates the helper test stub generator.
First case:
should "format date range on same day" do
eight_oclock = DateTime.new(2009, 10, 12, 8)
nine_oclock = DateTime.new(2009, 10, 12, 9)
date_range = eight_oclock..nine_oclock
expected = "October 12, 2009"
assert_equal expected, format_date_range(date_range)
end
My style is flat test structure, intention-revealing temporary variables, respect an 80-character line limit.
Make it pass:
def format_date_range(date_range)
first = date_range.first
last = date_range.last
if same_day?(first, last)
"#{first.to_s(:month_day)}, #{first.year}"
end
end
private
def same_day?(date_one, date_two)
date_one.day == date_two.day
end
More temporary variables. Simple private method is there for a more expressive question in the conditional.
Next case:
should "format date range on different days of same month" do
monday = DateTime.new(2009, 10, 12)
tuesday = DateTime.new(2009, 10, 13)
date_range = monday..tuesday
expected = "October 12-13, 2009"
assert_equal expected, format_date_range(date_range)
end
Same pattern. Make it pass:
def format_date_range(date_range)
# ...
elsif same_month?(first, last)
"#{first.to_s(:month_day)}-#{last.day}, #{last.year}"
end
end
private
def same_month?(date_one, date_two)
date_one.month == date_two.month
end
Same pattern. Date and time formatting already has a home in config/initializers/time_formats.rb, so we’re using our existing date format, month_day. Here’s what my typical initializer looks like:
{ :short_date => "%x", # 04/13/10
:long_date => "%a, %b %d, %Y", # Tue, Apr 13, 2010
:longer_date => "%B %d, %Y %H:%M %Z", # April 13, 2010 11:20
:index => "%Y/%m/%d %H:%M", # 2010/04/13 11:20
:standard => "%B %d, %Y", # April 13, 2010
:month_day => "%B %d", # April 13
:abbr_month => "%b" # Apr
}.each do |key, value|
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(
key => value
)
end
Final case:
should "format date range on days of different months" do
october = DateTime.new(2009, 10, 31)
november = DateTime.new(2009, 11, 1)
date_range = october..november
expected = "October 31-November 01, 2009"
assert_equal expected, format_date_range(date_range)
end
Make it pass:
else
"#{first.to_s(:month_day)}-#{last.to_s(:month_day)}, #{last.year}"
end
There’s room for improvement: that leading zero sucks. We could use %e instead of %d, which replaces the leading zero with a space:
expected = "October 31-November 1, 2009"
That makes the test look a little ridiculous, but since the output will be HTML, the extra space is fine.
Make it pass:
:month_day => "%B %e"
Sneak peak of the app in progress, taking advantage of the date helper:
