Library / SDK
radar/distance_of_time_in_words avatar
radar/distance_of_time_in_words

dotiw: a distance_of_time_in_words that says 1 year, 2 months and 3 weeks

Better distance of time in words for Rails

537 stars108 forksRubyMIT

At a glance

What is it?
dotiw is a Ruby gem that replaces Rails' distance_of_time_in_words with an output that lists every unit instead of saying about 1 year, supports seconds down to the unit, and takes options for locale, accumulation, maximum unit and inclusion or exclusion of specific measures. It works standalone in plain Ruby and can either override the Rails helpers or sit alongside them.
Who is it for?
Adopt dotiw if your interface shows relative times and the vague about 1 year output is wrong for your product, or if you need seconds in the output. Do not adopt it if you want the Rails wording, because the :vague option exists precisely to fall back to it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 44 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the gem replaces, and the output difference that motivates it

The dotiw library adds distance_of_time_in_words to any Ruby project, or overrides the default implementation in Rails with more accurate output. The author's framing is that if you crave accuracy down to the second, this gem exists for that reason.

The difference is visible in one example. Rails gives you about 1 year for a year and some months, and dotiw gives you every unit:

ruby
>> 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"

Zero-valued measures are dropped, so the same call without the weeks and days produces 1 year, 2 months, 4 days, 6 minutes, and 7 seconds. The output is assembled with to_sentence, which is where the comma and the final and come from, and that is also why the locale option is passed through to to_sentence.

The seconds question is handled explicitly. By default the seconds are not shown, matching Rails behaviour, but the third positional argument turns them on, and there is a worked example for the case that worried the author, a one second difference with seconds disabled, which still returns 1 second rather than an empty string.

So the practical difference from the Rails helper is granularity and exactness. Anything that is a year and a bit away is no longer flattened into a single vague unit.

Two ways to install it, and the difference that catches people out

The gem is added to the Gemfile like any other:

ruby
gem 'dotiw'

and then bundle install is run. The require is what differs, and there are two shapes.

In plain Ruby, you require the gem and include its methods module:

ruby
require 'dotiw'

include DOTIW::Methods

In Rails, the same require plus the three Action View helper modules it builds on:

ruby
require 'dotiw'

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

The third option is the one that matters, because 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, you require dotiw/core instead:

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

That loads DOTIW::Methods and the dotiw translations but leaves ActionView::Helpers::DateHelper untouched, and you then call the module directly:

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

The difference in behaviour is not cosmetic. The full require overrides the helpers globally, so every view in the application starts producing the new wording, including views that were written against the old one. The core require is opt-in at each call site, which is the safer choice in an application with a large existing view layer, and it is also the one that leaves the old output available for comparison.

The project is MIT licensed, the primary language is Ruby, the last push was on 2026-09-09, and the recent releases are v5.6.1 on 2026-09-09, v5.6.0 on 2026-08-28 and v5.5.0 on 2026-03-28.

The options that control which units appear

Four options do the interesting work, and each one has a specific purpose.

The locale option takes a symbol and outputs in that language, provided you have the translations, and otherwise it falls back to the app's default locale as set in the application config. The example passes locale: :es and gets 1 minuto, and the locale is also passed to to_sentence, so the list joining is localised too.

The vague option is the escape hatch back to Rails. Specify it and it uses the old distance_of_time_in_words, and the value can be anything except nil or false. The important detail is where the translations come from: :vague falls back to the Rails or ActionView implementation, which looks up its translations under datetime.distance_in_words, a namespace provided by Rails core translations such as the rails-i18n gem for non-English locales, and not by dotiw. The project documents the consequence, namely that combining :vague with a non-English :locale and seeing a translation missing error means your application also needs to load the corresponding Rails translations for that locale.

The accumulate_on option sets the maximum output unit which will accumulate all the surplus, so setting it to seconds and having a two minute difference gives 120 seconds. Units below accumulate_on are still shown separately, and for a single number in one unit you use max_unit instead.

The max_unit option expresses the entire distance as a single number in the given unit, discarding or flooring any remainder smaller than that unit, and unlike accumulate_on no other units are shown:

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

There is a hash variant of the same call, distance_of_time_in_words_hash, which returns a hash rather than a string, and in the example it returns hours: 74. The valid units are seconds, minutes, hours, days, weeks, months and years.

Selecting and excluding specific measures, and the folding behaviour

The only and except options are the most precise controls, and they share a behaviour that is easy to miss.

The only option restricts the output to specific measurements:

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

The except option removes specific measurements:

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

The key detail is in the second example, which uses only with an array. Excluded measurements are folded into the next smaller included measurement instead of being discarded, so the returned duration is always accurate. The excluded day is folded into hours, giving 25 hours, and the comment in the source makes the arithmetic explicit: 1 day plus 1 hour equals 25 hours.

That is a deliberate choice and it is worth understanding before you rely on it. The alternative design, discarding the excluded unit, would give you 1 hour and 1 minute for a day and an hour, which understates the distance by a factor of twenty-four. Folding preserves the total, which is what you want if the string is going into a billing or quota context, and it is what you do not want if the string is going somewhere a reader will parse the units out of.

The same folding rule applies to except, so excluding days from a day and an hour gives 25 hours rather than 1 hour, and excluding minutes from an hour and a minute gives 1 hour.

The include_seconds option is described as the equivalent of the third positional argument, and the author notes that the argument could have been merged into the options hash but was left in place for backwards compatibility.

The repository, the benchmarks and the release cadence

The repository layout is a conventional Ruby gem with a couple of things that show a mature project.

There is a dotiw.gemspec, a lib/ directory, a spec/ directory with a .rspec file, a benchmarks/ directory, a CHANGELOG.md, a CONTRIBUTING.md, a CODEOWNERS file, a Rakefile, a RELEASING.md, an Appraisals file and a gemfiles/ directory. The Appraisals file and the gemfiles directory together are how a gem tests itself against several dependency sets, which matters for a gem that has to work across a range of Rails versions.

The benchmarks directory is the interesting one for a library whose whole pitch is output formatting. Formatting a duration is cheap, but the numbers here compare output shapes rather than speed, and having them in the repository means a change to the formatting can be reviewed as a diff of strings rather than only through a changelog entry.

The release history tells you how actively the project is maintained. There are three releases in 2026 alone, v5.6.1 on 2026-09-09, v5.6.0 on 2026-08-28 and v5.5.0 on 2026-03-28, and the last push to the repository was on 2026-09-09, the same day as the newest tag. The homepage is ryanbigg.com, the author of the gem, and the README carries a Ruby workflow badge and a coverage badge for the master branch.

For a library this small, that release cadence is the notable fact. A formatting helper is exactly the kind of project that gets written once and abandoned, and the version line at 5.6.1 with three releases a year says somebody is still using it in production and still cares about the output.

The one thing to keep in mind when reading the source is that the gem depends on Rails helper modules for the date, text and number helpers it includes. In a plain Ruby project, including DOTIW::Methods gives you the methods, but the underlying helpers are the Active Support ones it pulls in, and the Rails-specific include lines in the documentation are there because that is where those helpers come from.

Editorial conclusion

Adopt dotiw if your interface shows relative times and the vague about 1 year output is wrong for your product, or if you need seconds in the output. Do not adopt it if you want the Rails wording, because the :vague option exists precisely to fall back to it. Before you require the gem in a Rails app, decide whether you want it to override the built-in helpers or sit beside them, since requiring dotiw replaces distance_of_time_in_words and time_ago_in_words, and requiring dotiw/core leaves them alone.

Frequently asked questions

What does the dotiw gem do differently from Rails distance_of_time_in_words?

It outputs every unit instead of a vague single measure, so a year and some months becomes 1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds rather than about 1 year. Zero-valued measures are dropped and the list is joined with to_sentence.

How do I use dotiw without overriding the Rails helpers?

Require dotiw/core instead of dotiw, either with gem 'dotiw', require: 'dotiw/core' in the Gemfile. That loads DOTIW::Methods and the dotiw translations but leaves ActionView::Helpers::DateHelper untouched, so you call DOTIW.distance_of_time_in_words explicitly.

What is the difference between the accumulate_on and max_unit options in dotiw?

accumulate_on sets the maximum output unit into which all surplus is folded, while units below it are still shown separately. max_unit expresses the entire distance as a single number in that unit, discarding or flooring the remainder, and no other units appear in the output.

What happens to measurements excluded by the only and except options in dotiw?

They are folded into the next smaller included measurement rather than discarded, so the returned duration stays accurate. An excluded day folded into hours gives 25 hours for a day plus an hour, not 1 hour.

How do I get the old Rails wording back in dotiw?

Pass the :vague option, whose value can be anything except nil or false. It falls back to the Rails or ActionView distance_of_time_in_words, which looks up its translations under datetime.distance_in_words from Rails core translations such as rails-i18n, not from dotiw.

Official sources

  1. License: MIT
  2. Project website
  3. radar/distance_of_time_in_words on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/radar-distance-of-time-in-words.svg)](https://hysenlabs.com/projects/radar-distance-of-time-in-words)

Was this useful?