Project

dotiw

0.25
A long-lived project that still receives updates
dotiw is a gem for Rails that overrides the default distance_of_time_in_words and provides a more accurate output. Do you crave accuracy down to the second? So do I. That's why I made this gem. - Ryan
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

Runtime

 Project Readme

dotiw Ruby Coverage Status

The dotiw library that adds distance_of_time_in_words to any Ruby project, or overrides the default implementation in Rails with more accurate output.

Do you crave accuracy down to the second? So do I. That's why I made this gem.

Install

Add to your Gemfile.

gem 'dotiw'

Run bundle install.

Pure Ruby

require 'dotiw'

include DOTIW::Methods

Rails

require 'dotiw'

include ActionView::Helpers::DateHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::NumberHelper

Rails, without overriding the built-in helpers

Requiring dotiw replaces the Rails implementation of distance_of_time_in_words and time_ago_in_words. If you would rather keep the built-in Rails helpers and call dotiw explicitly, require dotiw/core instead:

gem 'dotiw', require: 'dotiw/core'

This loads DOTIW::Methods and the dotiw translations, but leaves ActionView::Helpers::DateHelper untouched:

DOTIW.distance_of_time_in_words(from_time, to_time, highest_measures: 1)

distance_of_time_in_words

Take this for a totally kick-ass example:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds, true)
=> "1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds"

Also if one of the measurement is zero it will not output it:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 5.hours + 6.minutes + 7.seconds, true)
=> "1 year, 2 months, 4 days, 6 minutes, and 7 seconds"

Better than "about 1 year", am I right? Of course I am.

"But Ryan!", you say, "What happens if the time is only in seconds but because of the default the seconds aren't shown? Won't it be blank?" "No!" I triumphantly reply:

>> distance_of_time_in_words(Time.now, Time.now + 1.second, false)
=> "1 second"

It also supports numeric arguments like the original Rails version:

>> distance_of_time_in_words(0, 150)
=> "2 minutes and 30 seconds"

as an alternative to:

>> distance_of_time_in_words(Time.now, Time.now + 2.5.minutes)
=> "2 minutes and 30 seconds"

This is useful if you're just interested in "stringifying" the length of time. Alternatively, you can use the #distance_of_time helper as described below.

The third argument for this method is whether or not to include seconds. By default this is false (because in Rails' distance_of_time_in_words it is), you can turn it on though by passing true as the third argument:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 1.second, true)
=> "1 year, and 1 second"

Yes this could just be merged into the options hash but I'm leaving it here to ensure "backwards-compatibility", because that's just an insanely radical thing to do. \m/

Alternatively this can be included in the options hash as include_seconds: true removing this argument altogether.

The last argument is an optional options hash that can be used to manipulate behavior and (which uses to_sentence).

Don't like having to pass in Time.now all the time? Then use time_ago_in_words or distance_of_time_in_words_to_now which also will rock your world:

>> time_ago_in_words(Time.now + 3.days + 1.second)
=> "3 days, and 1 second"

>> distance_of_time_in_words_to_now(Time.now + 3.days + 1.second)
=> "3 days, and 1 second"

Oh, and did I mention it supports I18n? Oh yeah. Rock on!

Options

:locale

You can pass in a locale and it'll output it in whatever language you want (provided you have translations, otherwise it'll default to your app's default locale (the config.i18n.default_locale you have set in /config/application.rb):

>> distance_of_time_in_words(Time.now, Time.now + 1.minute, false, locale: :es)
=> "1 minuto"

This will also be passed to to_sentence.

:vague

Specify this if you want it to use the old distance_of_time_in_words. The value can be anything except nil or false.

Note that :vague falls back to Rails/ActionView's own distance_of_time_in_words, which looks up its translations under datetime.distance_in_words. This namespace is provided by Rails core translations (e.g. via the rails-i18n gem) for non-English locales, not by dotiw. If you combine :vague with a non-English :locale and see a "translation missing" error, make sure your application also loads the corresponding Rails translations for that locale (see #44).

:include_seconds

As described above this option is the equivalent to the third argument whether to include seconds.

:accumulate_on

Specifies the maximum output unit which will accumulate all the surplus. Say you set it to seconds and your time difference is of 2 minutes then the output would be 120 seconds.

>> distance_of_time_in_words(Time.now, Time.now + 2.hours + 70.seconds, true, accumulate_on: :minutes)
=> "121 minutes and 10 seconds"

Note that units below accumulate_on are still shown separately. If you want the entire distance expressed as a single number in one unit, use :max_unit instead.

:max_unit

Expresses the entire distance as a single number in the given unit, discarding (flooring) any remainder smaller than that unit. Unlike :accumulate_on, no other units are shown in the output.

>> distance_of_time_in_words(Time.now, Time.now + 1.day, true, max_unit: :seconds)
=> "86400 seconds"

>> distance_of_time_in_words(Time.now, Time.now + 3.days + 2.hours, true, max_unit: :hours)
=> "74 hours"

>> distance_of_time_in_words_hash(Time.now, Time.now + 3.days + 2.hours, max_unit: :hours)
=> {hours: 74}

Valid units are :seconds, :minutes, :hours, :days, :weeks, :months and :years.

:only

Only want a specific measurement of time? No problem!

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, only: :minutes)
=> "61 minutes"

Excluded measurements are folded into the next smaller included measurement instead of being discarded, so the returned duration is always accurate (1 hour + 1 minute = 61 minutes, not 1 minute).

You only want some? No problem too!

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, only: [:minutes, :hours])
=> "25 hours and 1 minute"

Here the excluded day is folded into hours (1 day + 1 hour = 25 hours).

:except

Don't want a measurement of time? No problem!

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, except: :minutes)
=> "1 hour and 60 seconds"

The excluded minute is folded into seconds (1 minute = 60 seconds) rather than discarded.

Culling a whole group of measurements of time:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, except: [:minutes, :hours])
=> "1 day and 3660 seconds"

The excluded hour and minute cascade down into seconds (1 hour + 1 minute = 3660 seconds).

:highest_measure_only

Deprecated. Use highest_measures: 1 instead.

For times when Rails distance_of_time_in_words is not precise enough and DOTIW is too precise. For instance, if you only want to know the highest time part (measure) that elapsed between two dates.

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, highest_measure_only: true)
=> "1 hour"

Notice how minutes and seconds were removed from the output. Another example:

>> distance_of_time_in_words(Time.now, Time.now + 1.minute + 1.second, true, highest_measure_only: true)
=> "1 minute"

Minutes are the highest measure, so seconds were discarded from the output.

:highest_measures

When you want variable precision from DOTIW:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, highest_measures: 2)
=> "1 hour and 1 minute"

You can also specify what to do with the extra time with the remainder option:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, highest_measures: { max: 2, remainder: :ceiling })
=> "1 hour and 2 minutes"

Valid options for remainder are :floor (default), :ceiling and :round. Note that :round is best-effort and makes some simplifying assumptions:

# Only the next-largest unit is examined, which can unexpectedly round down in some situations.
>> distance_of_time_in_words(Time.now, Time.now + 1.week + 3.days + 23.hours, true, highest_measures: { remainder: :round })
=> "1 week"
# The variability of some measures (like months) is ignored and the shortest duration of that measure is used.
>> distance_of_time_in_words(Time.now, Time.now + 1.month + 14.days, true, highest_measures: { remainder: :round })
=> "2 months"

:words_connector

This is an option for to_sentence, defaults to ', '.

Using something other than a comma:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, words_connector: ' - ')
=> "1 hour - 1 minute, and 1 second"

:two_words_connector

This is an option for to_sentence, defaults to ' and '.

Using something other than 'and':

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, true, two_words_connector: ' plus ')
=> "1 hour plus 1 minute"

:last_word_connector

This is an option for to_sentence, defaults to ', and '.

Using something other than ', and':

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, last_word_connector: ', finally ')
=> "1 hour, 1 minute, finally 1 second"

distance_of_time

If you have simply a number of seconds you can get the "stringified" version of this by using distance_of_time:

>> distance_of_time(300)
=> "5 minutes"

distance_of_time_in_words_hash

Don't like any format you're given? That's cool too! Here, have an indifferent hash version:

>> distance_of_time_in_words_hash(Time.now, Time.now + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds)
=> { days: 4, weeks: 3, seconds: 7, minutes: 6, years: 1, hours: 5, months: 2 }

Indifferent means that you can access all keys by their String or Symbol version.

distance_of_time_in_percent

This method is only available with Rails ActionView.

If you want to calculate a distance of time in percent, use distance_of_time_in_percent. The first argument is the beginning time, the second argument the "current" time and the third argument is the end time.

>> distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time)
=> '15%'

This method takes the same options as number_with_precision.

>> distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time, precision: 1)
=> '15.3%'

:compact

Pressed for space? Try compact: true.

>> distance_of_time_in_words(Time.now, Time.now + 2.year + 1.day + 1.second, compact: true)
=> "2y1d"

Pairs well with words_connector, last_word_connector, and two_words_connector if you can spare just a little more room:

>> distance_of_time_in_words(Time.now, Time.now + 5.years + 1.day + 23.seconds, words_connector: " ", last_word_connector: " ", two_words_connector: " ", compact: true)
=> "5y 1d 23s"

Contributors

  • chendo - for talking through it with me and drawing on the whiteboard
  • Derander - correct Spanish translations
  • DBA - commits leading up to the 0.7 release
  • Sija - rails 4 support, v2.0 release
  • dblock - Ruby w/o Rails support