# `Tempo.Ecto.TempoRange`
[🔗](https://github.com/elixir-tempo/tempo_sql/blob/v0.4.0/lib/tempo/ecto/tempo_range.ex#L2)

Ecto.ParameterizedType for the PostgreSQL composite type
`tempo_range` — a `tstzrange` paired with a Tempo-resolution
string and a `jsonb` metadata column that together preserve
the full Tempo shape on round-trip.

    CREATE TYPE tempo_range AS (
      range      tstzrange,
      resolution text,
      meta       jsonb
    );

A floating expression, with no zone and no time shift, is kept in
`tempo_floating_range`, the same with a `tsrange` for its range: a
field declared with `frame: :floating`.

Use this type when you care about round-trip fidelity: any
expression with ISO 8601 text — a value, an interval, a
recurrence, a set — round-trips as the same expression, including
qualifications, calendars other than the Gregorian, recurrence
rules, zone identifiers and the value-versus-interval distinction.
The range column holds the stretch it covers, for queries.

## Setup

Run the DDL helpers once, early in the migration history:

    import Tempo.SQL.Migration
    create_tempo_types()

(and `create_tempo_types(frame: :floating)` for floating columns).
Then declare columns with `add_tempo_range/2` (or the raw
`add :window, :tempo_range`).

## Usage

    schema "meetings" do
      field :window, Tempo.Ecto.TempoRange
      field :opening_hours, Tempo.Ecto.TempoRange, frame: :floating
    end

## Query API

The standard Postgres range operators (`@>`, `&&`) do not
apply directly to a composite column — they must reach into
`(column).range`. Use `Tempo.Ecto.QueryAPI.Composite` for
fragments that auto-unwrap the composite; the plain
`Tempo.Ecto.QueryAPI` does not work on these columns.

## Fidelity

Round-trip preserves:

  * The resolution a value is written to (`~o"2026Y"`
    round-trips as `~o"2026Y"`, not as an interval between two
    instants).

  * Qualifications (`:uncertain`, `:approximate`).

  * Non-Gregorian calendars.

  * Recurrence rules and repeat rules.

  * Zone identifiers (IANA names preserved via the meta
    column, not just the UTC offset).

  * The expression's metadata, and a set's members', provided
    each is JSON-serialisable.

# `cast_type`

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

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

See `Tempo.Ecto.Interval.cast_type/1` for the schema-side
equivalent and the full discussion.

### Arguments

* `options` is a keyword list of options.

### Options

* `:resolution` truncates the endpoints recorded in the composite to the given component. One of
  `:year`, `:month`, `:day`, `:hour`, `:minute`, or `:second`.
  Defaults to `:second`.

* `:frame` is `:utc` (the default) for a `tempo_range` column, whose
  expressions are in a zone or at a time shift, or `:floating` for
  a `tempo_floating_range` column, whose expressions have neither.

### Returns

* A `t:Ecto.ParameterizedType.opts/0` tuple.

### Examples

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

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

---

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