# `Tempo.Duration`
[🔗](https://github.com/elixir-tempo/tempo/blob/v2.1.0/lib/tempo/duration.ex#L1)

A length of time with no place on the time line: `P1Y2M3DT4H5M6S`.

A duration is the one companion of `Tempo`: it is not an expression and
denotes no span, and it is what an interval is formed from once it is
given a start (`2026-06-01/P1M`) and what a value is shifted by
(`Tempo.shift`).

`new/1` builds one from its units and `Tempo.duration/2` measures the
length of an expression. `add/2`, `subtract/2`, `sum/1`, `multiply/2` and
`negate/1` are its arithmetic; `compare/3` orders two, and `Tempo`'s
`at_least?/2`, `at_most?/2` and `exactly?/2` say how they compare.
`to_unit/3` and `to_microseconds/1` count one in a unit, and `exact?/1`
says whether it has a length without a place to begin, which a duration
of years or months does not.

# `t`

```elixir
@type t() :: %Tempo.Duration{units: units()}
```

# `units`

```elixir
@type units() :: [
  {:year | :month | :week | :day | :hour | :minute | :second, integer()}
  | {:microsecond, {integer(), 0..6}}
]
```

The units of a duration, coarsest first, each a whole number, negative
for a length in the reverse direction; a part of a second is
`{microseconds, precision}` as Elixir's types hold one.

# `add`

```elixir
@spec add(t(), t()) :: t()
```

The sum of two durations, unit by unit: an hour and a half plus forty-five
minutes is an hour and seventy-five minutes, no unit carried into another,
because an hour is sixty minutes but a month is not a fixed number of days.
A fraction of a second is the one exception, being part of its second.

### Arguments

* `duration1` and `duration2` are each a `t:t/0`.

### Returns

* The `t:t/0` of their units added. A value that is no duration raises a
  `Tempo.ConversionError`.

### Examples

    iex> Tempo.Duration.add(~o"PT1H30M", ~o"PT45M")
    ~o"PT1H75M"

    iex> Tempo.Duration.add(~o"P1Y", ~o"P2M")
    ~o"P1Y2M"

    iex> Tempo.Duration.add(~o"PT0.7S", ~o"PT0.6S")
    ~o"PT1.3S"

# `canonical`

```elixir
@spec canonical(t(), keyword()) :: {:ok, t()} | {:error, Exception.t()}
```

A duration in canonical form, as ISO 8601-2's Amendment 1 (2025) defines
it (§14.7 and D.5): no part past the unit above it that it converts into,
and a negative part only where no coarser part written can take it in.

Parts convert along two chains. Years and months convert by the months in
the calendar's year, where every year has as many. Weeks, days, hours,
minutes, seconds and parts of a second convert by the days in the
calendar's week, 24 hours, 60 minutes and 60 seconds. A minute is 60
seconds, as Tempo's clock has it, which is the nominal duration rule the
amendment allows (§14.6). Months and days do not convert, and nor do years
and weeks: the month and the days of `P1M30D` stay apart.

A chain of positive length is written from the coarsest unit it reaches:
`PT26H` is `P1DT2H`, and `P10D` is `P1W3D`. A chain of negative length
carries its sign on the coarsest unit written in it, the rest positive:
`P-1Y-15M` is `P-3Y9M`.

The canonical form is for comparing and for writing. A shift by it can
land elsewhere in a zone across a change of the clock, where a day is a
day of the calendar and 24 hours are elapsed time.

### Arguments

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

* `options` is a keyword list of options.

### Options

* `:calendar` is the calendar whose years and weeks count the months and
  days. The default is `Calendar.ISO`.

### Returns

* `{:ok, duration}` in canonical form, or

* `{:error, exception}` for a value that is no duration, an option the
  function does not take, or a calendar Tempo does not know.

### Examples

    iex> Tempo.Duration.canonical(~o"P7Y24M")
    {:ok, ~o"P9Y"}

    iex> Tempo.Duration.canonical(~o"PT1H-10M")
    {:ok, ~o"PT50M"}

    iex> Tempo.Duration.canonical(~o"P3DT-10M")
    {:ok, ~o"P2DT23H50M"}

# `canonical!`

```elixir
@spec canonical!(t(), keyword()) :: t()
```

`canonical/2`, returning its answer or raising the error.

### Arguments

* The arguments are those of `canonical/2`.

### Returns

* What `canonical/2` returns as `{:ok, answer}`, the answer alone. The
  error it returns as `{:error, exception}` is raised.

### Examples

    iex> Tempo.Duration.canonical!(~o"PT1H90M")
    ~o"PT2H30M"

# `compare`

```elixir
@spec compare(t(), t(), keyword()) :: :lt | :eq | :gt
```

Orders two durations by length, so that `Tempo.Duration` is a sorter:
`Enum.sort(durations, Tempo.Duration)`.

Two exact durations are compared as lengths, however they are written:
`PT90M` and `PT1H30M` are equal. A duration with a month or a year has no
length until it is counted from a date, given as `:relative_to`. Without
one, years and months are counted in months by the calendar's year, as
ISO 8601-2's Amendment 1 (2025) converts them, and the rest as a length:
`P7Y24M` and `P9Y` are equal, and `P1Y1D` is longer than `P12M`. Where
the months differ one way and the rest the other (`P1M` against `P30D`),
or the calendar's years differ in months, the comparison raises, having
only three answers to give.

### Arguments

* `a` and `b` are each a `t:t/0`.

* `options` is a keyword list of options.

### Options

* `:relative_to` is a `t:Tempo.t/0` the two durations are counted from:
  each is laid from the value's start, and the two ends compared.

* `:calendar` is the calendar whose year counts the months of a year,
  without `:relative_to`. The default is `Calendar.ISO`.

### Returns

* `:lt`, `:eq` or `:gt`.

### Examples

    iex> Tempo.Duration.compare(~o"PT90M", ~o"PT1H30M")
    :eq

    iex> Tempo.Duration.compare(~o"P7Y24M", ~o"P9Y")
    :eq

    iex> Enum.sort([~o"PT2H", ~o"PT30M", ~o"PT1H"], Tempo.Duration)
    [~o"PT30M", ~o"PT1H", ~o"PT2H"]

    iex> Tempo.Duration.compare(~o"P1M", ~o"P30D", relative_to: ~o"2026-02-01")
    :lt

# `exact?`

```elixir
@spec exact?(t()) :: boolean()
```

Whether a duration has a fixed length: one of weeks, days and units of
the clock alone. A month or a year is as long as the one it is counted
from, so a duration with either is not exact.

### Arguments

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

### Returns

* `true` for a duration of weeks, days and units of the clock alone, and
  `false` for one with a month or a year. A value that is no duration
  raises a `Tempo.ConversionError`.

### Examples

    iex> Tempo.Duration.exact?(~o"P1W2DT3H")
    true

    iex> Tempo.Duration.exact?(~o"P1M")
    false

# `multiply`

```elixir
@spec multiply(t(), integer()) :: t()
```

A duration taken a whole number of times: each unit multiplied.

### Arguments

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

* `n` is a whole number.

### Returns

* The `t:t/0` with each unit multiplied by `n`. A value that is no
  duration raises a `Tempo.ConversionError`, and an `n` that is no whole
  number an `ArgumentError`.

### Examples

    iex> Tempo.Duration.multiply(~o"P1M15D", 2)
    ~o"P2M30D"

# `negate`

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

A duration in the reverse direction: each unit negated.

### Arguments

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

### Returns

* The `t:t/0` with each unit negated. A value that is no duration raises
  a `Tempo.ConversionError`.

### Examples

    iex> Tempo.Duration.negate(~o"P1DT2H")
    ~o"-P1DT2H"

# `new`

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

Builds a duration from its units.

### Arguments

* `components` is a keyword list of units (`:year`, `:month`, `:week`,
  `:day`, `:hour`, `:minute`, `:second`, `:microsecond`), in any order,
  each a whole number, negative for a length in the reverse direction; a
  `:microsecond` is `{microseconds, precision}`.

### Returns

* `{:ok, duration}`, a `t:t/0` with its units ordered coarsest first.

* `{:error, %ArgumentError{}}` when a key is not a unit of a duration, is
  given twice, or holds what is not a whole number.

### Examples

    iex> Tempo.Duration.new(month: 6, year: 1)
    {:ok, ~o"P1Y6M"}

    iex> Tempo.Duration.new(hour: 1, minute: 30)
    {:ok, ~o"PT1H30M"}

    iex> {:error, %ArgumentError{}} = Tempo.Duration.new(fortnight: 1)

# `new!`

```elixir
@spec new!(keyword()) :: t()
```

Builds a duration as `new/1` does, returning it or raising.

### Arguments

* `components` is a keyword list of units, as for `new/1`.

### Returns

* The `t:t/0`, or raises the error `new/1` returns.

### Examples

    iex> Tempo.Duration.new!(day: 1)
    ~o"P1D"

# `subtract`

```elixir
@spec subtract(t(), t()) :: t()
```

The first duration less the second: `add/2` of the second negated.

### Arguments

* `a` and `b` are each a `t:t/0`.

### Returns

* The `t:t/0` of `a` less `b`, unit by unit. A value that is no duration
  raises a `Tempo.ConversionError`.

### Examples

    iex> Tempo.Duration.subtract(~o"PT2H", ~o"PT30M")
    ~o"PT2H-30M"

# `sum`

```elixir
@spec sum([t()]) :: t()
```

The sum of a list of durations, as `add/2` sums two.

### Arguments

* `durations` is a list of `t:t/0`.

### Returns

* The `t:t/0` of their units added, `P` with no units for an empty list.
  A member that is no duration raises a `Tempo.ConversionError`.

### Examples

    iex> Tempo.Duration.sum([~o"PT1H", ~o"PT2H", ~o"P1D"])
    ~o"P1DT3H"

# `to_microseconds`

```elixir
@spec to_microseconds(t()) :: {:ok, integer()} | {:error, Exception.t()}
```

The length of an exact duration in microseconds, with a week seven days
and a day twenty-four hours.

### Returns

* `{:ok, microseconds}`.

* `{:error, %ArgumentError{}}` for a duration with a month or a year, and
  `{:error, %Tempo.ConversionError{}}` for a value that is no duration.

### Arguments

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

### Examples

    iex> Tempo.Duration.to_microseconds(~o"PT1H30M")
    {:ok, 5_400_000_000}

    iex> {:error, %ArgumentError{}} = Tempo.Duration.to_microseconds(~o"P1M")

# `to_microseconds!`

```elixir
@spec to_microseconds!(t()) :: integer()
```

`to_microseconds/1`, returning its answer or raising the error.

### Arguments

* The arguments are those of `to_microseconds/1`.

### Returns

* What `to_microseconds/1` returns as `{:ok, answer}`, the answer alone.
  The error it returns as `{:error, exception}` is raised.

### Examples

    iex> Tempo.Duration.to_microseconds!(~o"PT1H30M")
    5_400_000_000

# `to_unit`

```elixir
@spec to_unit(t(), :week | :day | :hour | :minute | :second | :microsecond, keyword()) ::
  {:ok, float()} | {:error, Exception.t()}
```

The length of a duration in one unit of fixed length, as a float.

### Arguments

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

* `unit` is `:week`, `:day`, `:hour`, `:minute`, `:second` or
  `:microsecond`.

* `options` is a keyword list of options.

### Options

* `:relative_to` is a `t:Tempo.t/0` the duration is counted from, which a
  duration with a month or a year needs.

### Returns

* `{:ok, length}`, a float.

* `{:error, %ArgumentError{}}` when the unit is a month or a year, or the
  duration has one and no `:relative_to`.

### Examples

    iex> Tempo.Duration.to_unit(~o"PT90M", :hour)
    {:ok, 1.5}

    iex> Tempo.Duration.to_unit(~o"P1M", :day, relative_to: ~o"2026-02-01")
    {:ok, 28.0}

# `to_unit!`

```elixir
@spec to_unit!(
  t(),
  :week | :day | :hour | :minute | :second | :microsecond,
  keyword()
) :: float()
```

`to_unit/3`, returning its answer or raising the error.

### Arguments

* The arguments are those of `to_unit/3`.

### Options

* The options of `to_unit/3`.

### Returns

* What `to_unit/3` returns as `{:ok, answer}`, the answer alone.
  The error it returns as `{:error, exception}` is raised.

### Examples

    iex> Tempo.Duration.to_unit!(~o"PT90M", :hour)
    1.5

# `units`

```elixir
@spec units() :: [
  :year | :month | :week | :day | :hour | :minute | :second | :microsecond,
  ...
]
```

The units a duration is written in, coarsest first.

### Returns

* The list of unit atoms, from `:year` to `:microsecond`.

### Examples

    iex> Tempo.Duration.units()
    [:year, :month, :week, :day, :hour, :minute, :second, :microsecond]

---

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