# `Koyomi.DateTimeRange`
[🔗](https://github.com/hattoriai/koyomi/blob/v0.2.1/lib/koyomi/date_time_range.ex#L1)

An Ecto type for a span of time: a start and an end instant, the start
included and the end left out (`[from, to)`), as for a booking from
14:00 to 16:00.

The `:storage` option says how the span is stored, so that the same
field works with any database:

  * `:postgres` (the default) - a PostgreSQL `tstzrange` column. It
    needs `:postgrex`.
  * `:string` - a text column with an ISO 8601 interval in UTC,
    `"2027-03-15T13:00:00Z/2027-03-15T15:00:00Z"`: SQLite, MySQL, and
    embedded schemas.
  * `:map` - a map column (JSON in SQLite and MySQL) with
    `%{"from" => "2027-03-15T13:00:00Z", "to" => "2027-03-15T15:00:00Z"}`.

```elixir
# PostgreSQL
add :booking, :tstzrange
field :booking, Koyomi.DateTimeRange

# SQLite (ecto_sqlite3), MySQL
add :booking, :string
field :booking, Koyomi.DateTimeRange, storage: :string
```

A query for the spans that hold an instant needs PostgreSQL; with
another database, two `:utc_datetime` columns can be queried as
instants.

The value is a `%Koyomi.DateTimeRange{from: DateTime, to: DateTime}`,
in UTC. The type casts this struct, a `{from, to}` tuple, a map with
`"from"` and `"to"` (two `DateTime`, or two ISO 8601 strings with an
offset, `"2027-03-15T14:00:00+01:00"`, as two date and time pickers
with a `time_zone` post them), and an ISO 8601 interval of two such
strings. Both ends empty cast to `nil`. A span that ends before it
starts is invalid.

# `t`

```elixir
@type t() :: %Koyomi.DateTimeRange{from: DateTime.t(), to: DateTime.t()}
```

# `cast`

```elixir
@spec cast(term()) :: {:ok, t() | nil} | :error | {:error, keyword()}
```

Casts a value to a `Koyomi.DateTimeRange` (see the module docs for the
forms it takes).

# `dump`

```elixir
@spec dump(t() | nil) :: {:ok, term()} | :error
```

Dumps a span to a PostgreSQL `tstzrange` (`Postgrex.Range`), `[from, to)`.

# `duration`

```elixir
@spec duration(t(), System.time_unit()) :: integer()
```

The length of the span.

## Examples

    iex> {:ok, span} = Koyomi.DateTimeRange.cast(%{"from" => "2027-03-15T14:00:00Z", "to" => "2027-03-15T16:30:00Z"})
    iex> Koyomi.DateTimeRange.duration(span, :minute)
    150

# `load`

```elixir
@spec load(term()) :: {:ok, t() | nil} | :error
```

Loads a PostgreSQL `tstzrange` (`Postgrex.Range`); a span without a
bound (infinite) or an empty one loads as `nil`.

# `to_params`

```elixir
@spec to_params(t()) :: %{required(String.t()) =&gt; String.t()}
```

The span as ISO 8601 strings in UTC: `%{"from" => iso, "to" => iso}`.

## Examples

    iex> {:ok, span} = Koyomi.DateTimeRange.cast(%{"from" => "2027-03-15T14:00:00+01:00", "to" => "2027-03-15T16:00:00+01:00"})
    iex> Koyomi.DateTimeRange.to_params(span)
    %{"from" => "2027-03-15T13:00:00Z", "to" => "2027-03-15T15:00:00Z"}

---

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