Tempo.Compare (Tempo v0.5.0)

Copy Markdown View Source

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.

Summary

Functions

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

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

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

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

Functions

compare_endpoints(a, b)

@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(arg1, arg2)

@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(tempo)

@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(arg1)

@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).