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"}
]
endThe 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.IntegerIntervalA discrete interval between two integers.Interval.FloatIntervalA continuous interval between two floats.Interval.DecimalIntervalA continuous interval between twoDecimalstructs.Interval.DateIntervalA discrete interval between twoDatestructs.Interval.DateTimeIntervalA continuous interval between twoDateTimestructs.Interval.NaiveDateTimeIntervalA continuous interval between twoNaiveDateTimestructs.
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
endInteger 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]}
# }