# `Moar.Duration`
[🔗](https://github.com/synchronal/moar/blob/main/lib/duration.ex#L1)

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`
* `:approx_month` (30 days)
* `:approx_year` (360 days)

> #### Note {: .info}
>
> This module is naive and intentionally doesn't account for real-world calendars and all of their complexity,
> such as leap years, leap days, daylight saving time, past and future calendar oddities, etc.
>
> As ["Falsehoods programmers believe about time"](https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca)
> says, "If you think you understand everything about time, you're probably doing it wrong."
>
> See [`Cldr.Calendar.Duration`](https://hexdocs.pm/ex_cldr_calendars/Cldr.Calendar.Duration.html) for one example
> of a full-featured library that is far more likely to be correct.

# `date_time_ish`

```elixir
@type date_time_ish() :: DateTime.t() | NaiveDateTime.t() | binary()
```

# `format_style`

```elixir
@type format_style() :: :long | :short
```

# `format_transformer`

```elixir
@type format_transformer() :: :ago | :approx | :humanize
```

# `format_transformers`

```elixir
@type format_transformers() :: format_transformer() | [format_transformer()]
```

# `t`

```elixir
@type t() :: {time :: number(), unit :: time_unit()}
```

# `time_unit`

```elixir
@type time_unit() ::
  :nanosecond
  | :microsecond
  | :millisecond
  | :second
  | :minute
  | :hour
  | :day
  | :approx_month
  | :approx_year
```

# `ago`

```elixir
@spec ago(date_time_ish()) :: t()
```

Returns the duration between `datetime` and now, in the largest possible unit.

`datetime` can be an ISO8601-formatted string, a `DateTime`, or a `NaiveDateTime`.

See also `from_now/1`.

```elixir
iex> DateTime.utc_now()
...> |> Moar.DateTime.subtract({121, :minute})
...> |> Moar.Duration.ago()
...> |> Moar.Duration.shift(:minute)
{121, :minute}
```

# `approx`

```elixir
@spec approx(t()) :: t()
```

Shifts `duration` to an approximately equal duration that's simpler. For example, `{121, :second}` would get
shifted to `{2, :minute}`.

> #### Warning {: .warning}
>
> This function is lossy because it intentionally loses precision.

If the time value of the duration is exactly 1, the duration is returned unchanged: `{1, :minute}` => `{1, :minute}`.
Otherwise, the duration is shifted to the highest unit where the time value is >= 2.

```elixir
iex> Moar.Duration.approx({1, :minute})
{1, :minute}

iex> Moar.Duration.approx({7300, :second})
{2, :hour}
```

# `between`

```elixir
@spec between(date_time_ish(), date_time_ish()) :: t()
```

Returns the duration between `earlier` and `later`, in the largest possible unit.

`earlier` and `later` can be ISO8601-formatted strings, `DateTime`s, or `NaiveDateTime`s.

```elixir
iex> earlier = ~U[2020-01-01T00:00:00.000000Z]
iex> later = ~U[2020-01-01T02:01:00.000000Z]
iex> Moar.Duration.between(earlier, later)
{121, :minute}
```

# `convert`

```elixir
@spec convert(from :: t(), to :: time_unit()) :: number()
```

Converts a `{duration, time_unit}` tuple into a numeric duration, rounding down to the nearest whole number.

> #### Warning {: .warning}
>
> This function is lossy because it rounds down to the nearest whole number.

Uses `System.convert_time_unit/3` under the hood; see its documentation for more details.

It is similar to `shift/1` but this function returns an integer value, while `shift/1` returns a duration tuple.

```elixir
iex> Moar.Duration.convert({121, :second}, :minute)
2
```

# `format`

```elixir
@spec format(
  t() | date_time_ish(),
  format_style() | format_transformers() | binary() | nil,
  format_transformers() | binary() | nil,
  binary() | nil
) :: binary()
```

Formats a duration in either a long or short style, with optional transformers and an optional suffix.

* The first argument is a duration tuple, unless one of the transformers is `:ago`, in which case
  it can be a `DateTime`, `NaiveDateTime`, or an ISO8601-formatted string.
* The second argument is optional and is the style, transformer, list of transformers, or suffix.
* The third argument is optional and is the transformer, list of transformers, or suffix.
* The fourth argument is optional and is the suffix.

Styles:
  * `:long`, which formats like `"25 seconds"`.
  * `:short`, which formats like `"25s"`.
  * Defaults to `:long`
  
Transformers:
  * `:ago` transforms via `ago/1`
  * `:approx` transforms via `approx/1`
  * `:humanize` transforms via `humanize/1`
  * If no transformers are specified, no transformations are applied.
  
Suffix:
  * A string that will be appended to the formatted result.
  * If the `:ago` transformer is specified and a suffix is not specified, the suffix will default to `"ago"`.
    To use the "ago" transformer with no suffix, specify an empty string as the suffix (`nil` will not suffice).
  
```elixir
iex> Moar.Duration.format({1, :second})
"1 second"

iex> Moar.Duration.format({120, :second})
"120 seconds"

iex> Moar.Duration.format({120, :second}, :long)
"120 seconds"

iex> Moar.Duration.format({120, :second}, :short)
"120s"

iex> Moar.Duration.format({120, :second}, "yonder")
"120 seconds yonder"

iex> Moar.Duration.format({120, :second}, :humanize)
"2 minutes"

iex> Moar.Duration.format({120, :second}, :humanize, "yonder")
"2 minutes yonder"

iex> Moar.Duration.format({310, :second})
"310 seconds"

iex> Moar.Duration.format({310, :second}, :approx)
"5 minutes"

iex> DateTime.utc_now()
...> |> Moar.DateTime.add({-310, :second})
...> |> Moar.Duration.format(:short, [:ago, :approx], "henceforth")
"5m henceforth"
```

# `from_now`

```elixir
@spec from_now(date_time_ish()) :: t()
```

Returns the duration between now and `datetime`, in the largest possible unit.

`datetime` can be an ISO8601-formatted string, a `DateTime`, or a `NaiveDateTime`.

See also `ago/1`.

```elixir
iex> DateTime.utc_now()
...> |> Moar.DateTime.add({121, :minute})
...> |> Moar.Duration.from_now()
...> |> Moar.Duration.approx()
{2, :hour}
```

# `humanize`

```elixir
@spec humanize(t()) :: t()
```

If possible, shifts `duration` to a higher time unit that is more readable to a human. Returns `duration`
unchanged if it cannot be exactly shifted.

```elixir
iex> Moar.Duration.humanize({60000, :millisecond})
{1, :minute}

iex> Moar.Duration.humanize({48, :hour})
{2, :day}

iex> Moar.Duration.humanize({49, :hour})
{49, :hour}
```

# `shift`

```elixir
@spec shift(t(), time_unit()) :: t()
```

Shifts `duration` to `time_unit`. It is similar to `convert/1` but this function returns a duration tuple,
while `convert/1` just returns an integer value.

> #### Warning {: .warning}
>
> This function is lossy because it rounds down to the nearest whole number.

```elixir
iex> Moar.Duration.shift({121, :second}, :minute)
{2, :minute}
```

# `shift_down`

```elixir
@spec shift_down(t()) :: t()
```

Shifts `duration` to the next smaller unit. Raises if it's already at the smallest unit (nanosecond).

```elixir
iex> Moar.Duration.shift_down({1, :hour})
{60, :minute}
```

# `shift_up`

Shifts `duration` to the next larger unit. Raises if it's already at the largest unit (approx_year).
Rounds the result towards zero.

> #### Warning {: .warning}
>
> This function is lossy because it rounds down to the nearest whole number.

```elixir
iex> Moar.Duration.shift_up({60, :minute})
{1, :hour}

iex> Moar.Duration.shift_up({125, :minute})
{2, :hour}
```

# `to_string`

```elixir
@spec to_string(t()) :: String.t()
```

Shortcut to `format(duration, :long)`. See `format/4`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
