defmodule Moar.Duration do # @related [test](/test/duration_test.exs) @moduledoc """ A duration is a `{time, unit}` tuple. The time is a number and the unit is one of: `:nanosecond`, `:microsecond`, `:millisecond`, `:second`, `:minute`, `:hour`, `:day`. """ @seconds_per_minute 60 @seconds_per_hour 60 * 60 @seconds_per_day 60 * 60 * 24 @type t() :: {time :: number(), unit :: time_unit()} @type time_unit() :: :nanosecond | :microsecond | :millisecond | :second | :minute | :hour | :day @doc """ Converts a `{duration, time_unit}` tuple into a numeric duration, rounding down to the nearest whole number. Uses `System.convert_time_unit/3` under the hood; see its documentation for more details. ```elixir iex> Moar.Duration.convert({121, :second}, :minute) 2 ``` """ @spec convert(from :: t(), to :: time_unit()) :: number() def convert({time, :minute}, to_unit), do: convert({time * @seconds_per_minute, :second}, to_unit) def convert({time, :hour}, to_unit), do: convert({time * @seconds_per_hour, :second}, to_unit) def convert({time, :day}, to_unit), do: convert({time * @seconds_per_day, :second}, to_unit) def convert(duration, :minute), do: convert(duration, :second) |> Integer.floor_div(@seconds_per_minute) def convert(duration, :hour), do: convert(duration, :second) |> Integer.floor_div(@seconds_per_hour) def convert(duration, :day), do: convert(duration, :second) |> Integer.floor_div(@seconds_per_day) def convert({time, from_unit}, to_unit), do: System.convert_time_unit(time, from_unit, to_unit) @doc """ Converts a `{duration, time_unit}` tuple into a string. ```elixir iex> Moar.Duration.to_string({1, :second}) "1 second" iex> Moar.Duration.to_string({25, :millisecond}) "25 milliseconds" ``` """ @spec to_string({duration :: t(), unit :: time_unit()}) :: String.t() def to_string({1, unit}), do: "1 #{unit}" def to_string({-1, unit}), do: "-1 #{unit}" def to_string({time, unit}), do: "#{time} #{unit}s" end