A Variation On Rails' Verbose Time Helper

February 5th, 2008

Oh tax season. Does anyone else find that January is the busiest month of the year?

A client of ours recently wanted a custom version of the built-in Rails helper distance_of_time_in_words. Basically, they were looking for a countdown that would dynamically let the customers know how much time they had left to order.

For those unfamiliar with distance_of_time_in_words, you provide it with Date & Time objects and it will state the duration between them in a more human readable format. For instance:

index.html.erb (View)

1
2
3
4
<%= distance_of_time_in_words( Time.now, 45.days.from_now ) %>
  # => about 1 month
<%= distance_of_time_in_words( Time.mktime( 2008, 02, 05 ), Time.mktime( 2008, 03, 29 ) ) %>
  # => about 1 month

This default behavior is great for informal purposes, such as how long it’s been since a blog article was posted or a comment made. However, I’ve come up with something a little more accurate:

1
2
3
4
<%= distance_of_time_in_words( Time.now, 45.days.from_now ) %> 
  # => 1 month, 14 days
<%= distance_of_time_in_words( Time.mktime( 2008, 02, 05 ), Time.mktime( 2008, 03, 29 ) ) %>
  # => 1 month, 22 days

application_helper.rb (Helper)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def distance_of_time_in_words( from_time, to_time = 0, include_seconds = true )
  from_time = from_time.to_time if from_time.respond_to?(:to_time)
  to_time = to_time.to_time if to_time.respond_to?(:to_time)
  distance_in_minutes = (((to_time - from_time).abs)/60).round
  distance_in_seconds = ((to_time - from_time).abs).round

  case distance_in_minutes
    when 0..1
    case distance_in_seconds
      when 0..59 then "#{pluralize( distance_in_seconds, 'second' )}"
      else             "1 minute"
    end

    when 2..59           then "#{distance_in_minutes} minutes"
    when 60              then "1 hour"
    when 61..1439        then
      hours, minutes = (distance_in_minutes.to_f / 60.0), (distance_in_minutes.to_f % 60.0)
      "#{pluralize( hours.floor, 'hour' )}, #{pluralize( minutes.floor, 'minutes' )}"      
    when 1440..1500      then "1 day"
    when 1501..2879      then "1 day, #{pluralize( (distance_in_minutes.to_f / 60 - 24).floor, 'hour' )}"
    when 2880..43199     then "#{(distance_in_minutes / 1440).round} days"
    when 43200..44641    then "1 month"
    when 44641..86399    then "1 month, #{pluralize( ( distance_in_minutes / 1440 - 30 ).floor, 'day' )}"
    when 86400..525599   then "over #{(distance_in_minutes / 43200).floor} months"
    else                      "over #{pluralize( (distance_in_minutes / 525600).round, 'years' ) }"
  end
end
Alternatively, we could use the method alias time_ago_in_words to skip the need for a first argument, and assume we’re comparing the current time with the provided distant time:
1
2
<%= time_ago_in_words( Time.mktime( 2008, 03, 29 ) ) %>
 #=> 1 month, 22 days (assuming current time is February 5th, 2008)

Sorry, comments are closed for this article.