2 min read

Reporting ActiveSupport::Duration in concise human readable formats

Syed Aslam

Currently, there is no direct way to format ActiveSupport::Duration objects in Rails. You'd have to resort to using the #inspect, which provides limited configuration options. Also, the locale support in the #inspect was removed around Rails 5.1.

You could do something like the following to format an ActiveSupport::Duration object by hand in a view helper:

def duration_as_sentence(duration)
  parts = duration.parts
  units = [:days, :hours, :minutes]
  map   = {
    :days     => { :one => :d },
    :hours    => { :one => :h, :other => :hrs },
    :minutes  => { :one => :m, :other => :mins }
  }

  parts.
    sort_by { |unit, _| units.index(unit) }.
    map     { |unit, val| "#{val} #{val == 1 ? map[unit][:one].to_s : map[unit][:other].to_s}" }.
    to_sentence
end

With that said, I wrote a small gem named duration_in_words to help with this that is a bit more configurable and with locale support.

duration_in_words provides a view helper to convert ActiveSupport::Duration objects into concise string like 1h 20m and 30s, with locale support.

With duration_in_words you could do the following:

include ActionView::Helpers::DurationHelper

>> duration = 2.hours
>> duration_in_words(duration)
=> "2h"
>> duration = 1.day + 2.hours + 30.minutes
>> duration_in_words(duration)
=> "1d 2h and 30m"

Using :format option:

There are two formats available, :compact, and :full. :compact being the default.

>> duration = 1.day + 2.hours + 30.minutes
>> duration_in_words(duration, format: :full)
=> "1 day 2 hours and 30 minutes"

Using :locale option:

Given this locale dictionary:

de:
  duration:
    in_words:
      format:
        compact:
          years:
            one: '%{count}J'
            other: '%{count}J'
          months:
            one: '%{count}M'
            other: '%{count}M'
          days:
            one: '%{count}T'
            other: '%{count}T'
          hours:
            one: '%{count}Std.'
            other: '%{count}Std.'
          minutes:
            one: '%{count}Min'
            other: '%{count}Min'
          seconds:
            one: '%{count}s'
            other: '%{count}s'
          support:
            words_connector: ' '
            two_words_connector: ' und '
            last_word_connector: ' und '
        full:
          years:
            one: '%{count} Jahr'
            other: '%{count} Jahre'
          months:
            one: '%{count} Monat'
            other: '%{count} Monate'
          days:
            one: '%{count} Tag'
            other: '%{count} Tage'
          hours:
            one: '%{count} Stunde'
            other: '%{count} Stunden'
          minutes:
            one: '%{count} Minute'
            other: '%{count} Minuten'
          seconds:
            one: '%{count} Sekunde'
            other: '%{count} Sekunden'
          support:
            words_connector: ', '
            two_words_connector: ' und '
            last_word_connector: ', und '
>> duration = 1.day + 2.hours + 30.minutes
>> duration_in_words(duration, locale: :de)
=> "1T 2Std. und 30s"
>> duration_in_words(duration, format: :full, locale: :de)
>> "1 Tag, 2 Std., und 30 Sekunden"

Give it a try next time you need to format ActiveSupport::Duration objects.