# `Tempo.Math`
[🔗](https://github.com/kipcole9/tempo/blob/v0.5.0/lib/math.ex#L1)

Time-unit arithmetic primitives used by enumeration, interval
materialisation (`Tempo.to_interval/1`), and eventually
`Tempo + Duration` / `Tempo − Duration` operations.

The core function is `add_unit/3`: given a keyword-list time
representation (or a `%Tempo{}`), advance it by exactly one unit
at a specified resolution, carrying into coarser units as needed.
Carry is calendar-aware — months per year and days per month vary
by calendar, week counts too.

`unit_minimum/1` answers "what is the start-of-unit value?" —
used when reasoning about mixed-resolution intervals and when
constructing the lower bound of an implicit span.

The module is kept deliberately minimal and pure: no Tempo struct
construction, no side effects, no exceptions beyond the
`ArgumentError` raised when a unit has no known carry rule.

# `add`

```elixir
@spec add(Tempo.t(), Tempo.Duration.t()) :: Tempo.t()
```

Add a `t:Tempo.Duration.t/0` to a `t:Tempo.t/0`.

The duration's components are applied largest-unit-first
(year → month → day → hour → minute → second), with week
components expanded to days (`P2W` = 14 days). After the
month-level arithmetic, the day field is clamped to the valid
range for the resulting month — so `2022-01-31 + P1M` yields
`2022-02-28`, matching the semantics used by
`java.time.LocalDate.plus/2`.

Single add operations are atomic: `Jan 31 + P1M = Feb 28`, but
`Jan 31 + P1M + P1M` is not the same as `Jan 31 + P2M` — date
arithmetic is not associative. If you need the "absorb" chained
semantic, do the add in one call with a single `P2M` duration.

Negative duration components subtract. `~o"P-100D"` added to
`~o"2022Y1M10D"` yields a date 100 days earlier.

The input Tempo must carry every unit referenced by the
duration. If the duration has a `:hour` component but the Tempo
is at year resolution, the Tempo is extended via
`Tempo.extend_resolution/2` first.

### Arguments

* `tempo` is any `t:Tempo.t/0`.
* `duration` is any `t:Tempo.Duration.t/0`.

### Returns

* A new `t:Tempo.t/0` with the duration applied.

### Examples

    iex> Tempo.Math.add(~o"2022Y1M1D", ~o"P1M")
    ~o"2022Y2M1D"

    iex> Tempo.Math.add(~o"2022Y1M31D", ~o"P1M")
    ~o"2022Y2M28D"

    iex> Tempo.Math.add(~o"2022Y12M31D", ~o"P1D")
    ~o"2023Y1M1D"

    iex> Tempo.Math.add(~o"2022Y1M1D", ~o"P2W")
    ~o"2022Y1M15D"

# `add_unit`

Advance a `%Tempo{}` or a keyword-list time representation by
exactly one unit at the given resolution.

Uses `Keyword.replace!/3` (preserves position) rather than
`Keyword.put/3` (removes + prepends). Keyword-list order is an
invariant maintained elsewhere in Tempo: `compare_time/2`,
`inspect`, and `to_iso8601` all depend on it.

### Arguments

* `tempo_or_time` is either a `t:Tempo.t/0` or the keyword list
  stored in its `:time` field.

* `unit` is the unit at which to increment. Supported units:
  `:year`, `:month`, `:day`, `:hour`, `:minute`, `:second`,
  `:week`, `:day_of_year`, `:day_of_week`.

* `calendar` is the calendar module used for calendar-sensitive
  carry (months per year, days per month, weeks per year).

### Returns

* The input with the unit advanced by 1, carrying into coarser
  units as needed. Shape matches the input — a `%Tempo{}` in
  yields a `%Tempo{}` out; a keyword list yields a keyword list.

### Raises

* `ArgumentError` when no increment rule is defined for the
  requested unit.

### Examples

    iex> Tempo.Math.add_unit(~o"2022Y12M31D", :day, Calendrical.Gregorian)
    ~o"2023Y1M1D"

    iex> Tempo.Math.add_unit(~o"2022Y6M", :month, Calendrical.Gregorian)
    ~o"2022Y7M"

# `subtract`

```elixir
@spec subtract(Tempo.t(), Tempo.Duration.t()) :: Tempo.t()
```

Subtract a `t:Tempo.Duration.t/0` from a `t:Tempo.t/0`.

Equivalent to `add/2` with every duration component negated.
Month arithmetic still clamps day-of-month at the end.

### Arguments

* `tempo` is any `t:Tempo.t/0`.
* `duration` is any `t:Tempo.Duration.t/0`.

### Returns

* A new `t:Tempo.t/0` with the duration subtracted.

### Examples

    iex> Tempo.Math.subtract(~o"2022Y3M1D", ~o"P1M")
    ~o"2022Y2M1D"

    iex> Tempo.Math.subtract(~o"2022Y3M31D", ~o"P1M")
    ~o"2022Y2M28D"

    iex> Tempo.Math.subtract(~o"2022Y1M1D", ~o"P1D")
    ~o"2021Y12M31D"

# `subtract_unit`

The mirror of `add_unit/3`: advance a `%Tempo{}` or keyword-list
time representation backward by exactly one unit at the given
resolution, borrowing from coarser units as needed.

Used internally by `subtract/2` and by any future
backward-walking iteration.

### Arguments

* `tempo_or_time` is a `t:Tempo.t/0` or its time keyword list.
* `unit` is the unit to decrement. Same vocabulary as `add_unit/3`.
* `calendar` is the calendar module used for borrow lookups.

### Returns

* The input with the unit decremented by 1.

### Examples

    iex> Tempo.Math.subtract_unit(~o"2023Y1M1D", :day, Calendrical.Gregorian)
    ~o"2022Y12M31D"

    iex> Tempo.Math.subtract_unit(~o"2022Y1M", :month, Calendrical.Gregorian)
    ~o"2021Y12M"

# `unit_minimum`

Return the start-of-unit minimum value — used when a trailing
unit is unspecified in a mixed-resolution comparison or when
constructing the lower bound of an implicit span.

### Arguments

* `unit` is any time unit atom.

### Returns

* `1` for `:month`, `:day`, `:week`, `:day_of_year`, and
  `:day_of_week` — these count from 1.

* `0` for every other unit (including `:hour`, `:minute`,
  `:second`, `:year`, and any unrecognised atom).

### Examples

    iex> Tempo.Math.unit_minimum(:month)
    1

    iex> Tempo.Math.unit_minimum(:hour)
    0

---

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