Current section

Files

Jump to
livebook lib livebook utils time.ex
Raw

lib/livebook/utils/time.ex

defmodule Livebook.Utils.Time do
# A simplified version of https://gist.github.com/tlemens/88e9b08f62150ba6082f478a4a03ac52
@doc """
Formats the given point in time relatively to present.
## Examples
To deal with clock-drifts when receiving timestamps from the server, we accept future times:
iex> Livebook.Utils.Time.time_ago_in_words(~N[2100-06-20 18:15:00])
"less than 5 seconds"
"""
@spec time_ago_in_words(NaiveDateTime.t()) :: String.t()
def time_ago_in_words(naive_date_time) when is_struct(naive_date_time, NaiveDateTime) do
distance_of_time_in_words(naive_date_time, NaiveDateTime.utc_now())
end
@doc """
Formats time distance between `from_ndt` and `to_ndt` as a
human-readable string.
## Examples
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:04])
"less than 5 seconds"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:09])
"less than 10 seconds"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:19])
"less than 20 seconds"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:20])
"half a minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:39])
"half a minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:40])
"less than a minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:15:59])
"less than a minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:16:00])
"1 minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:16:29])
"1 minute"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:16:30])
"2 minutes"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:58:30])
"44 minutes"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 18:59:30])
"about 1 hour"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-20 19:59:30])
"about 2 hours"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-21 18:14:00])
"about 24 hours"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-21 18:15:00])
"1 day"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-06-22 18:15:00])
"2 days"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2020-07-22 18:15:00])
"about 1 month"
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:00], ~N[2021-08-22 18:15:00])
"about 14 months"
To deal with clock-drifts when receiving timestamps from another machine, we accept future times:
iex> Livebook.Utils.Time.distance_of_time_in_words(~N[2020-06-20 18:15:06], ~N[2020-06-20 18:15:04])
"less than 5 seconds"
"""
@spec distance_of_time_in_words(NaiveDateTime.t(), NaiveDateTime.t()) :: String.t()
def distance_of_time_in_words(from_ndt, to_ndt)
when is_struct(from_ndt, NaiveDateTime) and is_struct(to_ndt, NaiveDateTime) do
duration_seconds = NaiveDateTime.diff(to_ndt, from_ndt)
{:seconds, duration_seconds}
|> maybe_convert_to_minutes()
|> duration_in_words()
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 duration_in_words({:seconds, seconds}) when seconds <= 4 do
"less than 5 seconds"
end
defp duration_in_words({:seconds, seconds}) when seconds in 5..9 do
"less than 10 seconds"
end
defp duration_in_words({:seconds, seconds}) when seconds in 10..19 do
"less than 20 seconds"
end
defp duration_in_words({:seconds, seconds}) when seconds in 20..39 do
"half a minute"
end
defp duration_in_words({:seconds, seconds}) when seconds in 40..59 do
"less than a minute"
end
defp duration_in_words({:minutes, minutes}) when minutes == 1 do
"1 minute"
end
defp duration_in_words({:minutes, minutes}) when minutes in 2..44 do
"#{minutes} minutes"
end
defp duration_in_words({:minutes, minutes}) when minutes in 45..89 do
"about 1 hour"
end
# 90 mins up to 24 hours
defp duration_in_words({:minutes, minutes}) when minutes in 90..1439 do
"about #{round(minutes / 60)} hours"
end
# 24 hours up to 42 hours
defp duration_in_words({:minutes, minutes}) when minutes in 1440..2519 do
"1 day"
end
# 42 hours up to 30 days
defp duration_in_words({:minutes, minutes}) when minutes in 2520..43_199 do
"#{round(minutes / 1440)} days"
end
# 30 days up to 45 days
defp duration_in_words({:minutes, minutes}) when minutes in 43_200..64_799 do
"about 1 month"
end
# 45 days up to 60 days
defp duration_in_words({:minutes, minutes}) when minutes in 64_800..86_399 do
"about 2 months"
end
defp duration_in_words({:minutes, minutes}) do
"about #{round(minutes / 43_200)} months"
end
end