Skip to content

Instantly share code, notes, and snippets.

@tlemens
Last active March 8, 2023 22:55
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tlemens/88e9b08f62150ba6082f478a4a03ac52 to your computer and use it in GitHub Desktop.
Save tlemens/88e9b08f62150ba6082f478a4a03ac52 to your computer and use it in GitHub Desktop.
time_ago_in_words and distance_of_time_in_words for Phoenix/Elixir

time_ago_in_words and distance_of_time_in_words for Phoenix/Elixir

Ported from Rails/Ruby.

Localization is supported: Copy the translations from here and port them to your gettext localization files.

Usage

MyApp.DistanceOfTimeHelpers.time_ago_in_words(~N[2015-03-05 21:45:00])
"about 6 years"
MyApp.DistanceOfTimeHelpers.distance_of_time_in_words(~N[2004-06-06 21:45:00], ~N[2004-06-06 21:45:00], include_seconds: true)
"less than 5 seconds"
MyApp.DistanceOfTimeHelpers.distance_of_time_in_words(~N[2004-06-06 21:45:00], ~N[2004-06-06 21:45:00])                       
"less than a minute"
msgid "less than %{count} seconds"
msgid_plural "less than %{count} seconds"
msgstr[0] "weniger als %{count} Sekunden"
msgstr[1] "weniger als %{count} Sekunden"
msgid "half a minute"
msgstr "eine halbe Minute"
msgid "less than a minute"
msgstr "weniger als eine Minute"
msgid "1 minute"
msgstr "eine Minute"
msgid "%{count} minutes"
msgid_plural "%{count} minutes"
msgstr[0] "%{count} Minuten"
msgstr[1] "%{count} Minuten"
msgid "about 1 hour"
msgstr "etwa eine Stunde"
msgid "about %{count} hours"
msgid_plural "about %{count} hours"
msgstr[0] "etwa %{count} Stunden"
msgstr[1] "etwa %{count} Stunden"
msgid "1 day"
msgstr "ein Tag"
msgid "%{count} days"
msgid_plural "%{count} days"
msgstr[0] "%{count} Tage"
msgstr[1] "%{count} Tage"
msgid "about 1 month"
msgstr "etwa ein Monat"
msgid "about %{count} months"
msgid_plural "about %{count} months"
msgstr[0] "etwa %{count} Monate"
msgstr[1] "etwa %{count} Monate"
msgid "%{count} months"
msgid_plural "%{count} months"
msgstr[0] "%{count} Monate"
msgstr[1] "%{count} Monate"
msgid "about 1 year"
msgstr "etwa ein Jahr"
msgid "over 1 year"
msgstr "mehr als ein Jahr"
msgid "about %{count} years"
msgstr "etwa %{count} Jahre"
msgid "over %{count} years"
msgid_plural "over %{count} years"
msgstr[0] "mehr als %{count} Jahre"
msgstr[1] "mehr als %{count} Jahre"
msgid "almost %{count} years"
msgid_plural "almost %{count} years"
msgstr[0] "fast %{count} Jahre"
msgstr[1] "fast %{count} Jahre"
defmodule MyApp.DistanceOfTimeHelpers do
@moduledoc false
@minutes_in_year 525_600
@minutes_in_quarter_year 131_400
@minutes_in_three_quarters_year 394_200
def time_ago_in_words(naive_date_time) do
distance_of_time_in_words(naive_date_time, NaiveDateTime.utc_now())
end
def distance_of_time_in_words(from_ndt, to_ndt, opts \\ []) do
duration =
%{
include_seconds: opts[:include_seconds],
seconds: NaiveDateTime.diff(to_ndt, from_ndt)
}
|> maybe_convert_to_minutes
|> maybe_convert_to_years(from_ndt, to_ndt)
duration_in_words(duration)
end
defp maybe_convert_to_minutes(%{seconds: seconds}) when seconds > 59 do
%{minutes: round(seconds / 60)}
end
defp maybe_convert_to_minutes(duration), do: duration
defp maybe_convert_to_years(%{minutes: minutes}, from_ndt, to_ndt) when minutes > 525_599 do
from_year = if from_ndt.month >= 3, do: from_ndt.year + 1, else: from_ndt.year
to_year = if to_ndt.month < 3, do: to_ndt.year - 1, else: to_ndt.year
leap_years =
if from_year > to_year,
do: 0,
else: from_year..to_year |> Enum.count(&Calendar.ISO.leap_year?(&1))
minute_offset_for_leap_year = leap_years * 1440
# Discount the leap year days when calculating year distance.
# e.g. if there are 20 leap year days between 2 dates having the same day
# and month then the based on 365 days calculation
# the distance in years will come out to over 80 years when in written
# English it would read better as about 80 years.
minutes_with_offset = minutes - minute_offset_for_leap_year
%{
remainder: rem(minutes_with_offset, @minutes_in_year),
years: div(minutes_with_offset, @minutes_in_year)
}
end
defp maybe_convert_to_years(duration, _from_ndt, _to_ndt), do: duration
defp duration_in_words(%{include_seconds: true, seconds: seconds}) when seconds in 0..4 do
translate_duration("less than %{count} seconds", count: 5)
end
defp duration_in_words(%{include_seconds: true, seconds: seconds}) when seconds in 5..9 do
translate_duration("less than %{count} seconds", count: 10)
end
defp duration_in_words(%{include_seconds: true, seconds: seconds}) when seconds in 10..19 do
translate_duration("less than %{count} seconds", count: 20)
end
defp duration_in_words(%{include_seconds: true, seconds: seconds}) when seconds in 20..39 do
translate_duration("half a minute")
end
defp duration_in_words(%{include_seconds: true, seconds: seconds}) when seconds in 40..59 do
translate_duration("less than a minute")
end
defp duration_in_words(%{seconds: seconds}) when seconds in 0..29 do
translate_duration("less than a minute")
end
defp duration_in_words(%{seconds: seconds}) when seconds in 30..59 do
translate_duration("1 minute")
end
defp duration_in_words(%{minutes: minutes}) when minutes == 1 do
translate_duration("1 minute")
end
defp duration_in_words(%{minutes: minutes}) when minutes in 2..44 do
translate_duration("%{count} minutes", count: minutes)
end
defp duration_in_words(%{minutes: minutes}) when minutes in 45..89 do
translate_duration("about 1 hour")
end
# 90 mins up to 24 hours
defp duration_in_words(%{minutes: minutes}) when minutes in 90..1439 do
translate_duration("about %{count} hours", count: round(minutes / 60))
end
# 24 hours up to 42 hours
defp duration_in_words(%{minutes: minutes}) when minutes in 1440..2519 do
translate_duration("1 day")
end
# 42 hours up to 30 days
defp duration_in_words(%{minutes: minutes}) when minutes in 2520..43_199 do
translate_duration("%{count} days", count: round(minutes / 1440))
end
# 30 days up to 45 days
defp duration_in_words(%{minutes: minutes}) when minutes in 43_200..64_799 do
translate_duration("about 1 month")
end
# 45 days up to 60 days
defp duration_in_words(%{minutes: minutes}) when minutes in 64_800..86_399 do
translate_duration("about %{count} months", count: 2)
end
# 60 days up to 365 days
defp duration_in_words(%{minutes: minutes}) when minutes in 86_400..525_599 do
translate_duration("%{count} months", count: round(minutes / 43_200))
end
defp duration_in_words(%{remainder: remainder, years: years})
when years == 1 and remainder < @minutes_in_quarter_year do
translate_duration("about 1 year")
end
defp duration_in_words(%{remainder: remainder, years: years})
when years == 1 and remainder < @minutes_in_three_quarters_year do
translate_duration("over 1 year")
end
defp duration_in_words(%{remainder: remainder, years: years})
when remainder < @minutes_in_quarter_year do
translate_duration("about %{count} years", count: years)
end
defp duration_in_words(%{remainder: remainder, years: years})
when remainder < @minutes_in_three_quarters_year do
translate_duration("over %{count} years", count: years)
end
defp duration_in_words(%{years: years}) do
translate_duration("almost %{count} years", count: years + 1)
end
defp translate_duration(msg, opts \\ []) do
if count = opts[:count] do
Gettext.dngettext(MyApp.Gettext, "durations", msg, msg, count, opts)
else
Gettext.dgettext(MyApp.Gettext, "durations", msg, opts)
end
end
end
defmodule MyApp.DistanceOfTimeHelpersTest do
use MyApp.ConnCase
alias MyApp.DistanceOfTimeHelpers
describe "DistanceOfTimeHelpers.distance_of_time_in_words/2" do
test "0..1 minute with include_seconds: true" do
opts = [include_seconds: true]
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:00],
opts
) == "less than 5 seconds"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:09],
opts
) == "less than 10 seconds"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:10],
opts
) == "less than 20 seconds"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:19],
opts
) == "less than 20 seconds"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:20],
opts
) == "half a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:39],
opts
) == "half a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:40],
opts
) == "less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:59],
opts
) == "less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:00],
opts
) == "1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:29],
opts
) == "1 minute"
end
test "0..1 minute without include_seconds" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:00]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:04]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:09]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:10]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:19]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:20]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:39]
) ==
"1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:40]
) ==
"1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:59]
) ==
"1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:00]
) ==
"1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:29]
) ==
"1 minute"
end
# Note that we are including a 30-second boundary around the interval we
# want to test. For instance, "1 minute" is actually 30s to 1m29s. The
# reason for doing this is simple -- in `distance_of_time_to_words`, when we
# take the distance between our two Time objects in seconds and convert it
# to minutes, we round the number. So 29s gets rounded down to 0m, 30s gets
# rounded up to 1m, and 1m29s gets rounded down to 1m. A similar thing
# happens with the other cases.
test "first case 0..1 minute" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:00]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:29]
) ==
"less than a minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:45:30]
) ==
"1 minute"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:29]
) ==
"1 minute"
end
test "2 minutes up to 45 minutes" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 21:46:30]
) ==
"2 minutes"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 22:29:29]
) ==
"44 minutes"
end
test "45 minutes up to 90 minutes" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 22:29:30]
) ==
"about 1 hour"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 23:14:29]
) ==
"about 1 hour"
end
test "90 minutes up to 24 hours" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-06 23:14:30]
) ==
"about 2 hours"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-07 21:44:29]
) ==
"about 24 hours"
end
test "24 hours up to 42 hours" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-07 21:44:30]
) ==
"1 day"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-08 15:44:29]
) ==
"1 day"
end
test "42 hours up to 30 days" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-08 15:44:30]
) ==
"2 days"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-06-09 12:45:00]
) ==
"3 days"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-07-06 21:44:29]
) ==
"30 days"
end
test "30 days up to 60 days" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-07-06 21:44:30]
) ==
"about 1 month"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-07-21 21:44:29]
) ==
"about 1 month"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-07-21 21:44:30]
) ==
"about 2 months"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-08-05 21:44:29]
) ==
"about 2 months"
end
test "60 days up to 365 days" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2004-08-05 21:44:30]
) ==
"2 months"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2005-06-06 21:44:29]
) ==
"12 months"
end
test ">= 365 days" do
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2005-06-06 21:44:30]
) ==
"about 1 year"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2005-09-05 21:45:00]
) ==
"about 1 year"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2005-12-06 21:45:00]
) ==
"over 1 year"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2006-03-07 21:45:00]
) ==
"almost 2 years"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2006-09-05 21:45:00]
) ==
"about 2 years"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2006-09-07 21:45:00]
) ==
"over 2 years"
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2007-03-05 21:45:00]
) ==
"over 2 years"
# + 2 years + 9 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2007-03-07 21:45:00]
) ==
"almost 3 years"
# + 5 years - 3 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2009-03-07 21:45:00]
) ==
"almost 5 years"
# + 5 years + 3 months - 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2009-09-05 21:45:00]
) ==
"about 5 years"
# + 5 years + 3 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2009-09-07 21:45:00]
) ==
"over 5 years"
# + 5 years + 9 months - 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2010-03-05 21:45:00]
) ==
"over 5 years"
# + 5 years + 9 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2010-03-07 21:45:00]
) ==
"almost 6 years"
# + 10 years - 3 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2014-03-07 21:45:00]
) ==
"almost 10 years"
# + 10 years + 3 months - 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2014-09-05 21:45:00]
) ==
"about 10 years"
# + 10 years + 3 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2014-09-07 21:45:00]
) ==
"over 10 years"
# + 10 years + 9 months - 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2015-03-05 21:45:00]
) ==
"over 10 years"
# + 10 years + 9 months + 1 day
assert DistanceOfTimeHelpers.distance_of_time_in_words(
~N[2004-06-06 21:45:00],
~N[2015-03-07 21:45:00]
) ==
"almost 11 years"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment