Datatype and operations for both discrete and continuous intervals, Inspired by PostgreSQL's range types.
Find the documentation at https://hexdocs.pm/interval/
The package can be installed by adding interval
to your list of dependencies in mix.exs
:
def deps do
[
{:interval, "~> 2.0.0"}
]
end
The builtin types (Like Interval.DateTimeInterval
) can be used as an Ecto.Type
which will convert to Postgres' range types.
# ...
schema "reservations" do
field :period, Interval.DateTimeInterval
# ...
end
# ...
Interval.IntegerInterval
A discrete interval between two integers.Interval.FloatInterval
A continuous interval between two floats.Interval.DecimalInterval
A continuous interval between twoDecimal
structs.Interval.DateInterval
A discrete interval between twoDate
structs.Interval.DateTimeInterval
A continuous interval between twoDateTime
structs.Interval.NaiveDateTimeInterval
A continuous interval between twoNaiveDateTime
structs.
See Interval
for reference documentation on the available API functions.
Note though, that this feature only works with Postgrex
, as the
intervals are converted to a Postgrex.Range
struct which maps to the correct
range type in the database (like tstzrange
etc.)
The library contains a use
macro that does most of the work for you.
You must implement the Interval.Behaviour
, which contains a handful of functions.
This is the full definition of the built-in Interval.DecimalInterval
:
defmodule Interval.DecimalInterval do
use Interval, type: Decimal, discrete: false
if Interval.Support.EctoType.supported?() do
use Interval.Support.EctoType, ecto_type: :numrange
end
@impl true
@spec point_normalize(any()) :: {:ok, Decimal.t()} | :error
def point_normalize(a) when is_struct(a, Decimal), do: {:ok, a}
def point_normalize(_), do: :error
@impl true
@spec point_compare(Decimal.t(), Decimal.t()) :: :lt | :eq | :gt
def point_compare(a, b) when is_struct(a, Decimal) and is_struct(b, Decimal) do
Decimal.compare(a, b)
end
end
Integer intervals are discrete intervals (just like the int4range
in Postgres).
alias Interval.IntegerInterval
# ...
a = IntegerInterval.new(1, 4, "[]")
# [1, 5)
b = IntegerInterval.new(2, 5, "[]")
# [2, 6)
assert IntegerInterval.contains?(a, b)
assert IntegerInterval.overlaps?(a, b)
c = IntegerInterval.intersection(a, b) # [2, 5)
d = IntegerInterval.union(a, b) # [1, 6)
e = IntegerInterval.difference(a, b) # [1, 2)
Discrete intervals are always normalized to the bound form [)
(just like in Postgres).
DateTime intervals are continuous intervals (just like tstzrange
in Postgres).
alias Interval.DateTimeInterval
# ...
# default bound is "[)"
a = DateTimeInterval.new(~U[2022-01-01 00:00:00Z], ~U[2023-01-01 00:00:00Z])
b = DateTimeInterval.new(~U[2018-07-01 00:00:00Z], ~U[2022-03-01 00:00:00Z])
DateTimeInterval.intersection(a, b)
# %DateTimeInterval{
# left: {:inclusive, ~U[2022-01-01 00:00:00Z]},
# right: {:exclusive, ~U[2022-03-01 00:00:00Z]}
# }