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

Shared comparison primitives for Tempo values.

Set operations, enumeration, and IntervalSet construction all
need to compare two time keyword lists as start-moments on the
time line. This module is the single place that definition
lives. The comparison treats missing trailing units as their
unit minimum — so `[year: 2022]` (which means "start of 2022")
compares correctly against `[year: 2022, month: 6]` (which
means "start of June 2022") without ambiguity.

For set operations that span timezones, `to_utc_seconds/1`
projects a zoned `%Tempo{}` into gregorian-seconds-since-UTC
epoch so operands in different zones can share a total order.
The projection is computed on demand and never cached — that
policy decision was made in the implicit-to-explicit plan and
revisited in the set-operations plan.

# `compare_endpoints`

```elixir
@spec compare_endpoints(Tempo.t(), Tempo.t()) :: :earlier | :later | :same
```

Return `:earlier`, `:later`, or `:same` for two `%Tempo{}`
endpoints comparing by their UTC-projected start-moments.

When both Tempos share a zone (or both have `nil` zone info),
this reduces to `compare_time/2` on their `:time` lists with a
renamed return. When zones differ, both sides are projected to
UTC via `to_utc_seconds/1` for a common reference frame.

### Arguments

* `a` and `b` are `%Tempo{}` structs, typically interval
  endpoints.

### Returns

* `:earlier`, `:later`, or `:same`.

# `compare_time`

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

Compare two time keyword lists as start-moments on the time
line.

Missing trailing units are filled with their unit minimum
(`:month` / `:day` / `:week` / `:day_of_year` / `:day_of_week`
count from 1; everything else counts from 0). Both lists must
be sorted descending-by-unit — the invariant the tokenizer and
`Unit.sort/2` maintain.

Mismatched units at the same position (e.g. `:week` vs
`:month`) fall through to `:eq` as a conservative bailout. A
well-formed comparison has operands using the same unit
vocabulary.

### Arguments

* `a` and `b` are keyword lists like `[year: 2022, month: 6]`.

### Returns

* `:lt` when `a` is earlier than `b`.

* `:gt` when `a` is later than `b`.

* `:eq` when they are the same start-moment, or when mismatched
  unit vocabularies prevent a meaningful order.

### Examples

    iex> Tempo.Compare.compare_time([year: 2022], [year: 2022, month: 6])
    :lt

    iex> Tempo.Compare.compare_time([year: 2022, month: 6, day: 15], [year: 2022, month: 6, day: 15])
    :eq

    iex> Tempo.Compare.compare_time([year: 2023], [year: 2022, month: 12])
    :gt

# `to_utc_seconds`

```elixir
@spec to_utc_seconds(Tempo.t()) :: integer() | float()
```

Project a zoned `%Tempo{}` to UTC gregorian seconds since
year 0 (matching Erlang's `:calendar.datetime_to_gregorian_seconds/1`
epoch).

The projection is per-call, never cached. When `Tzdata` is
updated with new zone rules, the next call automatically uses
them. Stored IntervalSet endpoints carry wall-clock + zone as
authoritative — see `plans/set-operations.md` for the full
rationale on why no UTC cache exists.

### Arguments

* `tempo` is a `%Tempo{}` with at minimum year/month/day/hour/
  minute/second components. Missing components are padded with
  their unit minimum.

### Returns

* `integer` — gregorian seconds since year 0 in UTC.

### Raises

* `ArgumentError` when the Tempo has no `:year` component
  (non-anchored values can't be projected to a universal
  instant).

# `unit_minimum`

```elixir
@spec unit_minimum(atom()) :: integer()
```

The start-of-unit minimum — 1 for `:month`, `:day`, `:week`,
`:day_of_year`, `:day_of_week`; 0 for everything else.

Exposed on `Tempo.Compare` and `Tempo.Math` as the same
definition (both modules re-export via delegation).

---

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