Tempo.Ecto.Interval (Tempo SQL v0.4.0)

Copy Markdown View Source

Ecto.ParameterizedType for persisting an interval as a PostgreSQL tstzrange value, or as a tsrange value when it is floating.

It takes any expression that is one span — an interval, or a value (~o"2026-06") as the interval it names — and loads an interval.

Usage

schema "meetings" do
  field :window, Tempo.Ecto.Interval
end

and in the migration:

add :window, :tstzrange

(or use Tempo.SQL.Migration.add_interval/2).

A field of floating values (with no zone and no time shift, such as a shop's opening hours wherever it is) is declared on the floating frame, and stored in a tsrange:

field :opening_hours, Tempo.Ecto.Interval, frame: :floating

add :opening_hours, :tsrange

Options

  • :resolution — on load, truncate both endpoints to the given component and drop all sub-components. One of :year, :month, :day, :hour, :minute, or :second. Defaults to :second (no truncation). See the storage contract guide for the semantics and caveats.

    field :reporting_period, Tempo.Ecto.Interval, resolution: :year
  • :frame — the line the column's instants lie on. :utc (the default) stores a value in a zone or at a time shift as the UTC instants it is, in a tstzrange; :floating stores a value with neither, in a tsrange. A value of the other frame is refused by cast/2, as a changeset error that names the frame to declare, and by dump/3.

Storage contract

This type refuses to store values it cannot round-trip semantically. The dump callback returns :error for:

  • A value of the other frame: a floating value for a column on the UTC line, or a value in a zone or at a time shift for a floating column.

  • An expression of several spans: a recurrence, a set, or a value naming several (~o"2026Y{6,8}M"). Store those as Tempo.Ecto.IntervalSet instead.

  • A qualification (:uncertain, :approximate) or a calendar other than the Gregorian, which a range of instants cannot keep. Tempo.Ecto.TempoRange keeps both.

  • An interval unbounded on both sides. One open end is stored as a (,b) or [a,) range in Postgres.

Bracket normalisation on load

PostgreSQL does not canonicalise tstzrange values on output — a column populated by another writer might hold [a, b], (a, b), or (a, b]. The loader normalises any non-half-open range to [lower, upper) by shifting the offending endpoint one second (Tempo is second-resolution so this is exact).

Summary

Functions

Builds the parameterized-type tuple for use outside a schema.

Functions

cast_type(options \\ [])

@spec cast_type(keyword()) :: Ecto.Type.t()

Builds the parameterized-type tuple for use outside a schema.

Inside a schema, field :window, Tempo.Ecto.Interval, resolution: :day does this for you. Reach for this when calling Ecto.Type.cast/2 and friends directly.

Arguments

  • options is a keyword list of options.

Options

  • :resolution truncates both endpoints on load to the given component. One of :year, :month, :day, :hour, :minute, or :second. Defaults to :second.

  • :frame is :utc (the default) for a tstzrange column or :floating for a tsrange column.

Returns

Examples

iex> Tempo.Ecto.Interval.cast_type()
{:parameterized, {Tempo.Ecto.Interval, %{frame: :utc, resolution: :second}}}

iex> Tempo.Ecto.Interval.cast_type(resolution: :year, frame: :floating)
{:parameterized, {Tempo.Ecto.Interval, %{frame: :floating, resolution: :year}}}