# `Localize.Duration`
[🔗](https://github.com/elixir-localize/localize/blob/v1.2.0/lib/localize/duration.ex#L1)

Functions to create and format durations — the difference
between two dates, times, or datetimes expressed in calendar
units.

A duration is represented as years, months, days, hours,
minutes, seconds, and microseconds. This is useful for
producing human-readable strings like "11 months, 30 days"
or numeric patterns like "37:48:12".

## Creating durations

* `new/2` — calculates the duration between two dates, times,
  or datetimes.

* `new_from_seconds/1` — creates a duration from a number of
  seconds.

## Formatting durations

* `to_string/2` — formats a duration as a localized string
  using unit names (e.g., "11 months, 30 days") via
  `Localize.Unit` and `Localize.List`.

* `to_time_string/2` — formats the time portion of a duration
  using a pattern like `"hh:mm:ss"`. Hours are unbounded
  (e.g., "37:48:12" for 37 hours).

# `date_or_time_or_datetime`

```elixir
@type date_or_time_or_datetime() ::
  Calendar.date()
  | Calendar.time()
  | Calendar.datetime()
  | Calendar.naive_datetime()
```

A date, time, naive datetime, or datetime.

# `t`

```elixir
@type t() :: %Localize.Duration{
  day: non_neg_integer(),
  hour: non_neg_integer(),
  microsecond: {integer(), 1..6},
  minute: non_neg_integer(),
  month: non_neg_integer(),
  second: non_neg_integer(),
  year: non_neg_integer()
}
```

Duration in calendar units.

# `new`

```elixir
@spec new(Date.Range.t()) :: {:ok, t()} | {:error, Exception.t() | atom()}
```

Calculates the calendar duration of a `t:Date.Range.t/0`.

Equivalent to `new(range.first, range.last)`.

### Arguments

* `range` is a `t:Date.Range.t/0` (e.g., `Date.range/2`).

### Returns

* `{:ok, duration}` where `duration` is a `t:t/0` struct.

* `{:error, exception}` if the range endpoints are incompatible.

### Examples

    iex> {:ok, d} = Localize.Duration.new(Date.range(~D[2019-01-01], ~D[2019-12-31]))
    iex> d.month
    11

# `new`

```elixir
@spec new(from :: date_or_time_or_datetime(), to :: date_or_time_or_datetime()) ::
  {:ok, t()} | {:error, Exception.t() | atom()}
```

Calculates the calendar duration between two dates, times, or
datetimes.

### Arguments

* `from` is a date, time, or datetime representing the start.

* `to` is a date, time, or datetime representing the end.

### Returns

* `{:ok, duration}` where `duration` is a `t:t/0` struct.

* `{:error, exception}` if the arguments are incompatible.

### Examples

    iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
    iex> d.month
    11

    iex> {:ok, d} = Localize.Duration.new(~T[10:00:00], ~T[12:30:45])
    iex> {d.hour, d.minute, d.second}
    {2, 30, 45}

# `new!`

```elixir
@spec new!(from :: date_or_time_or_datetime(), to :: date_or_time_or_datetime()) ::
  t() | no_return()
```

Same as `new/2` but raises on error.

### Arguments

* `from` is a date, time, or datetime representing the start.

* `to` is a date, time, or datetime representing the end.

### Returns

* A `t:t/0` duration struct.

* Raises an exception if the arguments are incompatible.

### Examples

    iex> d = Localize.Duration.new!(~D[2019-01-01], ~D[2019-12-31])
    iex> d.month
    11

# `new_from_seconds`

```elixir
@spec new_from_seconds(seconds :: number()) :: t()
```

Creates a duration from a number of seconds.

The duration will contain only hours, minutes, seconds,
and microseconds (year/month/day will be zero).

### Arguments

* `seconds` is a number of seconds (integer or float).

### Returns

* A `t:t/0` struct.

### Examples

    iex> d = Localize.Duration.new_from_seconds(136_092)
    iex> {d.hour, d.minute, d.second}
    {37, 48, 12}

    iex> d = Localize.Duration.new_from_seconds(90.5)
    iex> {d.minute, d.second}
    {1, 30}

# `to_parts`

```elixir
@spec to_parts(t(), Keyword.t()) ::
  {:ok, [%{type: atom(), value: String.t()}]} | {:error, Exception.t()}
```

Formats a duration into typed parts, mirroring ECMA-402's `formatToParts` for `Intl.DurationFormat`.

The parts concatenate to exactly the string `to_string/2` produces with the same options. Each duration field contributes its unit parts (from `Localize.Unit.to_parts/2`) with the numeric segments carrying a `:unit` key naming the field; the list separators between fields are `:literal` parts.

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options.

### Options

See `to_string/2` for the supported options.

### Returns

* `{:ok, parts}` where `parts` is a list of `%{type: atom(), value: String.t()}` maps; numeric parts also carry a `:unit` key.

* `{:error, exception}` if formatting fails.

### Examples

    iex> duration = %Localize.Duration{hour: 2, minute: 30}
    iex> Localize.Duration.to_parts(duration, locale: :en)
    {:ok,
     [
       %{type: :integer, value: "2", unit: :hour},
       %{type: :literal, value: " "},
       %{type: :unit, value: "hours"},
       %{type: :literal, value: ", "},
       %{type: :integer, value: "30", unit: :minute},
       %{type: :literal, value: " "},
       %{type: :unit, value: "minutes"}
     ]}

# `to_parts!`

```elixir
@spec to_parts!(t(), Keyword.t()) :: [%{type: atom(), value: String.t()}]
```

Same as `to_parts/2` but raises on error.

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options. See `to_parts/2`.

### Returns

* A list of `%{type: atom(), value: String.t()}` maps.

### Raises

* Raises an exception if formatting fails.

### Examples

    iex> Localize.Duration.to_parts!(%Localize.Duration{hour: 2}, locale: :en) |> length()
    3

# `to_string`

```elixir
@spec to_string(t(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}
```

Formats a duration as a localized string using unit names.

Non-zero duration parts are formatted as units and joined
with the locale's unit list pattern for the requested width,
per ECMA-402 `Intl.DurationFormat` (e.g., "11 months,
30 days").

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options.

### Options

* `:except` is a list of time unit atoms to omit from
  the output (e.g., `[:microsecond]`). The default is
  `[:microsecond]`.

* `:locale` is a locale identifier. The default is
  `Localize.get_locale()`.

* `:format` is the display width applied to every unit: one of
  `:long` ("11 months, 30 days"), `:short` ("11 mths, 30 days"),
  or `:narrow` ("11m 30d"). The default is `:long`. It also
  selects the CLDR unit list pattern that joins the parts.

* `:display` is a keyword list of per-unit display control,
  mirroring ECMA-402's per-unit `*Display` options. Each key is
  a unit atom (`:year`, `:month`, `:day`, `:hour`, `:minute`,
  `:second`, `:microsecond`) and each value is `:auto` (omit
  the unit when zero, the default) or `:always` (render the
  unit even when zero).

* `:formats` is a keyword list of per-unit width overrides,
  mirroring ECMA-402's per-unit width options. Each key is a
  unit atom (the same set as `:display`) and each value is
  `:long`, `:short`, or `:narrow`, overriding `:format` for
  that unit alone; units not named keep `:format`. Note the
  plural: `:format` sets the width for the whole duration,
  `:formats` overrides individual units within it.

### Returns

* `{:ok, formatted_string}` on success.

* `{:error, exception}` if formatting fails.

### Examples

    iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
    iex> Localize.Duration.to_string(d, locale: :en)
    {:ok, "11 months, 30 days"}

    iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
    iex> Localize.Duration.to_string(d, locale: :en, format: :narrow)
    {:ok, "11m 30d"}

    iex> duration = %Localize.Duration{hour: 2}
    iex> Localize.Duration.to_string(duration, locale: :en, display: [minute: :always])
    {:ok, "2 hours, 0 minutes"}

    iex> duration = %Localize.Duration{hour: 2, minute: 30}
    iex> Localize.Duration.to_string(duration, locale: :en, formats: [hour: :narrow])
    {:ok, "2h, 30 minutes"}

# `to_string!`

```elixir
@spec to_string!(t(), Keyword.t()) :: String.t() | no_return()
```

Same as `to_string/2` but raises on error.

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options.

### Options

See `to_string/2` for the supported options.

### Returns

* The formatted duration as a string.

* Raises an exception if formatting fails.

### Examples

    iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
    iex> Localize.Duration.to_string!(d, locale: :en)
    "11 months, 30 days"

# `to_time_string`

```elixir
@spec to_time_string(t(), Keyword.t()) :: {:ok, String.t()}
```

Formats the time portion of a duration using a numeric
pattern like `"hh:mm:ss"`.

Hours are unbounded — a duration of 37 hours, 48 minutes,
and 12 seconds formats as `"37:48:12"`.

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options.

### Options

* `:format` is a format pattern string. The default is
  `"hh:mm:ss"`. Use `"h:mm:ss"` for no zero-padding on
  hours, or `"mm:ss"` for minutes and seconds only.

### Returns

* `{:ok, formatted_string}` on success.

### Examples

    iex> d = Localize.Duration.new_from_seconds(136_092)
    iex> Localize.Duration.to_time_string(d)
    {:ok, "37:48:12"}

    iex> d = Localize.Duration.new_from_seconds(65)
    iex> Localize.Duration.to_time_string(d, format: "m:ss")
    {:ok, "1:05"}

# `to_time_string!`

```elixir
@spec to_time_string!(t(), Keyword.t()) :: String.t()
```

Same as `to_time_string/2` but raises on error.

### Arguments

* `duration` is a `t:t/0` struct.

* `options` is a keyword list of options.

### Options

See `to_time_string/2` for the supported options.

### Returns

* The formatted time portion of the duration as a string.

* Raises an exception if formatting fails.

### Examples

    iex> d = Localize.Duration.new_from_seconds(136_092)
    iex> Localize.Duration.to_time_string!(d)
    "37:48:12"

    iex> d = Localize.Duration.new_from_seconds(65)
    iex> Localize.Duration.to_time_string!(d, format: "m:ss")
    "1:05"

---

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