# `Ash.Type.Duration`
[🔗](https://github.com/ash-project/ash/blob/v3.36.1/lib/ash/type/duration.ex#L5)

Represents a Duration

A builtin type that can be referenced via `:duration`

### Constraints

* `:signs` - The signs the value may have, compared against zero by `Ash.Type.Duration.compare/2`. Any combination is permitted: `:positive` or `[:positive]` requires a positive duration, `[:positive, :zero]` a non-negative one, and `[:positive, :negative]` a non-zero one. Omit the constraint to allow any sign. This is the sign of the duration as a whole, not of each unit — `%Duration{day: 1, hour: -5}` is positive, as `compare/2` counts it as nineteen hours. Only where units in different buckets carry opposite signs does the sign depend on `compare/2`'s conventions of a 30-day month and a 24-hour day.
   Valid values are :positive, :negative, :zero

* `:units` - The units the value may be expressed in. A duration is always re-expressed in the largest of these units that will hold it, on the way in and on the way out, so `[:week, :minute]` turns `14 days 2 hours` into `2 weeks 120 minutes`. Conversion only happens within a bucket: year/month, week/day, and hour down to microsecond. A month is not a fixed number of days and a day is not a fixed number of hours (it is 23 or 25 across a daylight-saving change, which is why PostgreSQL keeps days and microseconds in separate interval buckets), so a value that no combination of the permitted units expresses exactly is rejected, including anything that would have to cross a bucket. This applies on the way out as well as in: a stored duration the permitted units cannot express is refused rather than quietly rewritten. Either a single unit, an explicit list of them, or a shorthand: `:year_month` (`[:year, :month]`) or `:day_time` (`[:week, :day, :hour, :minute, :second, :microsecond]`), which covers both the week/day and the hour to microsecond buckets. Confining an attribute to one of these keeps its comparisons free of the 30-day month (see `Ash.Type.Duration.compare/2`). With no constraint every unit is permitted, so the same normalization applies and nothing is ever lost.

# `compare`

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

Compares two durations as a total order, matching how the AshPostgres data
layer (PostgreSQL `interval`) compares them: a fixed conversion of `month` → 30
days and `day` → 24 hours (so `year` → 360 days, `week` → 7 days), down to
microseconds.

`Duration` is only *partially* ordered in general — a month is not a fixed
number of days — which is why Elixir ships `Duration` without a `compare/2`, and
why data layers disagree on cross-unit comparison: PostgreSQL uses 30-day
months, Neo4j ~30.44-day months, and Elixir's `to_timeout/1` refuses `month`/
`year` outright. This adopts PostgreSQL's convention so in-memory comparison
stays aligned with the dominant data layer rather than raising or drifting.
Within a single bucket (year/month, week/day, or hour down to microsecond) the
result is exact and portable across those backends. Comparison across buckets
depends on a convention: the 30-day month, or the 24-hour day. The 24-hour day
matches PostgreSQL's interval comparison and Elixir's `DateTime.shift/3`, but not
PostgreSQL's `timestamptz` arithmetic, which adds a day on the wall clock and so
can differ from 24 hours across a daylight saving change.

Computed from the integer fields directly, so microsecond precision is kept
(unlike `to_timeout/1`, which truncates to milliseconds).

This function is the single place the convention lives; if Elixir core later
gains a `Duration.compare/2`, it can delegate here.

# `handle_change?`

# `prepare_change?`

---

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