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

An Ecto type for a range of days. The value is a `Date.Range` with a
step of 1, both ends included.

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

  * `:postgres` (the default) - a PostgreSQL `daterange` column. It
    needs `:postgrex`.
  * `:string` - a text column with an ISO 8601 interval,
    `"2027-03-01/2027-03-07"`: SQLite, MySQL, and embedded schemas.
  * `:map` - a map column (JSON in SQLite and MySQL, `jsonb` in
    PostgreSQL) with `%{"from" => "2027-03-01", "to" => "2027-03-07"}`.

```elixir
# PostgreSQL
add :stay, :daterange
field :stay, Koyomi.DateRange

# SQLite (ecto_sqlite3), MySQL
add :stay, :string
field :stay, Koyomi.DateRange, storage: :string
```

A string or a map sorts and compares as text, not as a range: a query
for the ranges that hold a day needs PostgreSQL. With another database,
two `:date` columns also work, with the `from` and `to` of
`Koyomi.Components.date_range_picker/1`, and can be queried as dates.

The range picker posts the range as a map, `%{"from" => "2027-03-01",
"to" => "2027-03-07"}`; the type also casts a map with `"start"` and
`"end"`, a `Date.Range`, a `{first, last}` tuple, an ISO 8601 interval
(`"2027-03-01/2027-03-07"`), and the key of a built-in preset
(`"last_7_days"`, which a relative picker posts), as the days it covers
today in UTC (see `Koyomi.Presets.to_range/2` to count from another day).
Both ends empty cast to `nil`. A range with one end, or with an end
before its start, is invalid.

PostgreSQL keeps a `daterange` in its canonical form, `[first, last + 1)`;
the type writes that form and reads any form back as a `Date.Range`. A
range without a bound (infinite) or an empty range reads back as `nil`.

# `cast`

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

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

## Examples

    iex> Koyomi.DateRange.cast("2027-03-01/2027-03-07")
    {:ok, Date.range(~D[2027-03-01], ~D[2027-03-07])}

# `dump`

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

Dumps a range to a PostgreSQL `daterange` (`Postgrex.Range`), in its
canonical form `[first, last + 1)`.

# `load`

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

Loads a PostgreSQL `daterange` (`Postgrex.Range`) of any form as a
`Date.Range`; an infinite or empty range loads as `nil`.

# `to_params`

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

The value of a range in the form that the picker reads and posts:
`%{"from" => iso, "to" => iso}`, or empty strings for `nil`.

## Examples

    iex> Koyomi.DateRange.to_params(Date.range(~D[2027-03-01], ~D[2027-03-07]))
    %{"from" => "2027-03-01", "to" => "2027-03-07"}

---

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