Tempo. SQL. Conversion
(Tempo SQL v0.1.0)
View Source
Internal helpers that translate between Tempo.Interval.t/0
endpoints and the DateTime / :unbound values that
Postgrex.Range understands.
The storage contract this module enforces — see the README for the full rationale:
%Tempo{}endpoints must carry a fully anchored year/month/day/hour/minute/second time slot (Tempo's highest resolution). Partial values must be materialised viaTempo.to_interval/1first.:qualification,:qualifications, and:extendedmetadata are dropped on storage — round-tripping is lossy by design for round 1.Non-Gregorian calendars are rejected.
Multi-valued token slots (lists like
day_of_week: [1, 3, 5]or ranges likeday: 1..15) are rejected.Tempo.Intervalrecurrence (:recurrence,:repeat_rule) is rejected — callers must materialise recurring intervals to aTempo.IntervalSetviaTempo.to_interval/1and store that aststzmultirangeinstead.
Summary
Functions
Convert a Tempo.Interval.t/0 into a %Postgrex.Range{}
tolerant enough for the composite tempo_range type.
Convert a Tempo.Interval.t/0 into a %Postgrex.Range{} with
DateTime bounds in UTC.
Convert a %Postgrex.Range{} back into a Tempo.Interval.t/0.
Truncate a Tempo.t/0 token list to the given resolution,
dropping all sub-resolution components.
The valid values for the :resolution option on the Ecto types.
Validate a :resolution option value. Returns the atom
unchanged or raises ArgumentError.
Functions
@spec interval_to_queryable_range(Tempo.Interval.t()) :: {:ok, Postgrex.Range.t()} | {:error, Tempo.SQL.UnsupportedValueError.t()}
Convert a Tempo.Interval.t/0 into a %Postgrex.Range{}
tolerant enough for the composite tempo_range type.
Unlike interval_to_range/1, this variant accepts Tempo
endpoints the plain tstzrange encoder rejects — qualifications,
non-Gregorian calendars, multi-valued slots, ordinal/week dates
— because the composite's meta column carries the original
shape losslessly. The range is still populated from the
materialised endpoints so Postgres range-operator queries work.
Endpoints are materialised via Tempo.to_interval/1 where
needed to produce usable DateTime bounds.
@spec interval_to_range(Tempo.Interval.t()) :: {:ok, Postgrex.Range.t()} | {:error, Tempo.SQL.UnsupportedValueError.t()}
Convert a Tempo.Interval.t/0 into a %Postgrex.Range{} with
DateTime bounds in UTC.
Returns {:ok, range} on success, {:error, exception} when the
interval violates the storage contract.
@spec range_to_interval( Postgrex.Range.t(), keyword() ) :: {:ok, Tempo.Interval.t()} | {:error, Tempo.SQL.UnsupportedValueError.t()}
Convert a %Postgrex.Range{} back into a Tempo.Interval.t/0.
Unlike discrete range types (int4range, daterange), PostgreSQL
does not canonicalise tstzrange to [lower, upper) on
output — a range written as [a, b] round-trips as [a, b].
This loader therefore normalises any non-half-open range to
Tempo's [first, last) convention by shifting the offending
endpoint one second:
[a, b]→[a, b + 1s)(a, b)→[a + 1s, b)(a, b]→[a + 1s, b + 1s)
Tempo is second-resolution so the shift is exact — the loaded interval covers the same instants as the stored range.
Options
:resolutiontruncates both endpoints to the given component, dropping all sub-components. Must be one of:year,:month,:day,:hour,:minute, or:second. Defaults to:second(no truncation). See the storage contract guide for semantics.
Truncate a Tempo.t/0 token list to the given resolution,
dropping all sub-resolution components.
The valid values for the :resolution option on the Ecto types.
Validate a :resolution option value. Returns the atom
unchanged or raises ArgumentError.