Declares a time slot interval and provides functions for interval arithmetic, coverage checks, boundary comparisons, and transformations.
Slots in Tempus are modeled as mathematical intervals with configurable boundary openness:
- Half-Open Intervals: Standard slots default to $[from, to)$ (
from_open: false,to_open: true). For example, a date~D[2023-04-12]wraps into%Slot{from: ~U[2023-04-12 00:00:00Z], to: ~U[2023-04-13 00:00:00Z], from_open: false, to_open: true}. - Infinite / Unbound Limits: A
nilboundary represents infinity ($-infty$ or $+infty$) and is always treated as open (from_open: truewhenfrom: nil,to_open: truewhento: nil). - Void / Identity Slot: Represented by
%Slot{from: nil, to: nil, from_open: true, to_open: true}(Slot.id/0).
Key Functions
Slot.wrap/1: Converts aDate,DateTime, orTimeinto a%Tempus.Slot{}.Slot.join/2: Merges two overlapping or contiguous slots into a single maximal slot.Slot.intersect/2: Calculates the overlapping intersection of slots.Slot.xor/2: Returns the symmetric difference between two slots.Slot.gap/1: Finds the uncovered gap between slots.Slot.cover?/2: Checks if aDateTimeorDatefalls within the slot interval.Slot.disjoint?/2&Slot.neighbour?/2: Checks if slots are non-overlapping or adjacent/touching.
Summary
Functions
Compares two slot structs.
Checks whether the Slot covers the date/datetime passed as a second argument.
Returns true if two slots are disjoined, false otherwise.
Calculates the duration of a slot in units given as a second parameter
(default: :second.)
Identity element, void slot ~I(nil → nil)
Intersects slots to the minimal covered timeslice.
Joins slots to the maximal covered timeslice.
Joins two slots to the maximal covered timeslice.
Returns true if two slots are neighbours (touch at a boundary), false otherwise.
Creates new slot using arg[:from] as a starting origin and arg[:to] and an ending origin.
Creates new slot using from as a starting origin and to and an ending origin.
See new/1 for more readable implementation.
Creates new slot using from as a starting origin and to and an ending origin.
Unlike new/1, this function raises on malformed input.
Shifts both from and to values to UTC zone.
Compares two slot structs. The same as compare/2, but returns :joint if
the slots are overlapped.
Checks whether the Slot is valid (to > from, or to == from if closed point) or not.
Helper macro to pattern-match void slots.
Splits the slot given as a first argument to two on borders given as a second slot.
Types
@type origin() :: t() | Date.t() | DateTime.t() | nil
The origin used in comparisons and calculations
@type t() :: %Tempus.Slot{ from: nil | DateTime.t(), from_open: boolean(), to: nil | DateTime.t(), to_open: boolean() }
A timeslot to be used in Tempus
Functions
Compares two slot structs.
Returns :gt if first slot is strictly later than the second and :lt for vice versa.
NB :eq is returned not only if slots are equal, but also when they are overlapped.
Might be used in Enum.sort/2.
Examples
iex> slot = Tempus.Slot.wrap(~D[2020-09-30])
iex> slot1 = %Tempus.Slot{from: nil, to: ~U[2020-09-30 00:00:00.000000Z]}
iex> slot2 = %Tempus.Slot{from: nil, to: DateTime.utc_now()}
iex> slot3 = %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: nil}
iex> slot4 = %Tempus.Slot{from: DateTime.utc_now(), to: nil}
iex> Tempus.Slot.compare(Tempus.Slot.id(), Tempus.Slot.id(), true)
:eq
iex> Tempus.Slot.compare(slot1, slot2, false)
:eq
iex> Tempus.Slot.compare(slot1, slot2, true)
:joint
iex> Tempus.Slot.compare(slot3, slot4, false)
:eq
iex> Tempus.Slot.compare(slot3, slot4, true)
:joint
iex> Tempus.Slot.compare(slot, slot, true)
:eq
iex> Tempus.Slot.compare(slot, DateTime.utc_now(), true)
:lt
iex> Tempus.Slot.compare(slot, ~D|2000-01-01|, true)
:gt
iex> Tempus.Slot.compare(slot, slot.from, true)
:joint
Checks whether the Slot covers the date/datetime passed as a second argument.
Examples
iex> dt_between = ~U|2015-09-30 01:00:00Z|
...> dt_from = ~U|2015-09-30 00:00:00Z|
...> dt_to = ~U|2015-10-01 01:00:00Z|
...> d_from = Date.from_iso8601!("2015-09-30")
...> d_to = Date.from_iso8601!("2015-10-01")
iex> slot = %Tempus.Slot{from: dt_from, to: dt_to}
iex> Tempus.Slot.cover?(slot, dt_between)
true
iex> Tempus.Slot.cover?(slot, dt_to)
false
iex> Tempus.Slot.cover?(slot, dt_from)
true
iex> Tempus.Slot.cover?(slot, d_from)
true
iex> Tempus.Slot.cover?(slot, ~U|2000-01-01 00:00:00Z|)
false
iex> Tempus.Slot.cover?(slot, d_to)
false
Returns true if two slots are disjoined, false otherwise.
Examples
iex> slot = %Tempus.Slot{from: ~U|2015-09-01 00:00:00Z|, to: ~U|2015-10-01 00:00:00Z|}
iex> inner = %Tempus.Slot{from: ~U|2015-09-01 00:00:00Z|, to: ~U|2015-09-01 01:00:00Z|}
iex> Tempus.Slot.disjoint?(slot, inner)
false
iex> outer = %Tempus.Slot{from: ~U|2015-10-01 00:00:01Z|, to: ~U|2015-10-01 01:00:00Z|}
iex> Tempus.Slot.disjoint?(slot, outer)
true
iex> Tempus.Slot.disjoint?(~D|2000-01-01|, ~U|2015-10-01 00:00:01Z|)
true
@spec duration(slot :: origin(), unit :: System.time_unit()) :: non_neg_integer() | :infinity
Calculates the duration of a slot in units given as a second parameter
(default: :second.)
Example
iex> Tempus.Slot.duration(~D|2020-09-03|)
86400
iex> Tempus.Slot.duration(Tempus.Slot.id())
0
iex> Tempus.Slot.duration(%Tempus.Slot{from: nil, to: DateTime.utc_now()})
:infinity
iex> Tempus.Slot.duration(%Tempus.Slot{from: DateTime.utc_now(), to: nil})
:infinity
@spec id() :: %Tempus.Slot{from: nil, to: nil, from_open: true, to_open: true}
Identity element, void slot ~I(nil → nil)
Intersects slots to the minimal covered timeslice.
Example
iex> Tempus.Slot.intersect([Tempus.Slot.id(), Tempus.Slot.id()])
Tempus.Slot.id()
iex> Tempus.Slot.intersect([%Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z]},
...> %Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z]}])
%Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z], from_open: true, to_open: true}
iex> Tempus.Slot.intersect([~D|2020-09-30|, Tempus.Slot.id()])
%Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-01 00:00:00.000000Z], from_open: false, to_open: true}
Joins slots to the maximal covered timeslice.
Example
iex> Tempus.Slot.join([])
Tempus.Slot.id()
iex> Tempus.Slot.join([Tempus.Slot.wrap(~D|2020-09-30|), Tempus.Slot.wrap(~D|2020-10-02|)])
%Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}
iex> Tempus.Slot.join([~D|2020-09-30|, ~D|2020-10-02|])
%Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}
Joins two slots to the maximal covered timeslice.
Example
iex> Tempus.Slot.join(Tempus.Slot.wrap(~D|2020-09-30|), Tempus.Slot.wrap(~D|2020-10-02|))
%Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}
iex> Tempus.Slot.join(~D|2020-09-30|, ~D|2020-10-02|)
%Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}
Returns true if two slots are neighbours (touch at a boundary), false otherwise.
Examples
iex> slot = Tempus.Slot.wrap(~D|2015-09-01|)
iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-09-02|))
true
iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-08-31|))
true
iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-09-03|))
false
Creates new slot using arg[:from] as a starting origin and arg[:to] and an ending origin.
Examples
iex> Tempus.Slot.new(from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|)
{:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}}
iex> Tempus.Slot.new(%{from: ~D|2015-09-30|, to: ~U|2015-10-01T12:00:00Z|})
{:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}}
Creates new slot using from as a starting origin and to and an ending origin.
See new/1 for more readable implementation.
Examples
iex> import Tempus.Sigils
iex> Tempus.Slot.new(~U|2015-09-30 00:00:00Z|, ~U|2015-10-01 01:00:00Z|)
{:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}}
iex> Tempus.Slot.new(~D|2015-09-30|, ~U|2015-10-01T12:00:00Z|)
{:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}}
iex> Tempus.Slot.new(nil, nil)
{:ok, Tempus.Slot.id()}
iex> Tempus.Slot.new(~D|2015-09-30|, nil)
{:ok, %Tempus.Slot{from: ~U[2015-09-30 00:00:00.000000Z], to: nil, from_open: false, to_open: true}}
iex> Tempus.Slot.new(nil, ~D|2015-09-30|)
{:ok, %Tempus.Slot{from: nil, to: ~U[2015-10-01 00:00:00.000000Z], from_open: true, to_open: true}}
iex> Tempus.Slot.new(:ok, :ok)
{:error, :invalid_input}
Creates new slot using from as a starting origin and to and an ending origin.
Unlike new/1, this function raises on malformed input.
Examples
iex> Tempus.Slot.new!(~U|2015-09-30 00:00:00Z|, ~U|2015-10-01 01:00:00Z|)
%Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}
iex> Tempus.Slot.new!(~D|2015-09-30|, ~U|2015-10-01T12:00:00Z|)
%Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}
iex> Tempus.Slot.new!(:ok, :ok)
** (ArgumentError) malformed from/to argument, expected `origin`
@spec shift_tz( slot :: t(), tz :: Calendar.time_zone(), tz_db :: Calendar.time_zone_database() ) :: t()
Shifts both from and to values to UTC zone.
Examples
slot = %Tempus.Slot{
from: DateTime.from_naive!(~N|2018-01-05 21:00:00|, "America/New_York"),
to: DateTime.from_naive!(~N|2018-01-08 08:59:59|, "Australia/Sydney")
}
#⇒ %Tempus.Slot{from: ~U[2018-01-06 02:00:00Z], to: ~U[2018-01-07 21:59:59Z], from_open: false, to_open: true}
Compares two slot structs. The same as compare/2, but returns :joint if
the slots are overlapped.
Examples
iex> Tempus.Slot.strict_compare(~D|2020-01-01|, DateTime.utc_now())
:lt
Checks whether the Slot is valid (to > from, or to == from if closed point) or not.
Examples
iex> slot = %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|}
iex> Tempus.Slot.valid?(slot)
true
iex> Tempus.Slot.valid?(%Tempus.Slot{from: slot.to, to: slot.from})
false
iex> slot = %Tempus.Slot{from: nil, to: ~U|2015-10-01 01:00:00Z|}
...> Tempus.Slot.valid?(slot)
true
iex> slot = %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: nil}
...> Tempus.Slot.valid?(slot)
true
iex> Tempus.Slot.valid?(:ok)
false
Helper macro to pattern-match void slots.
@spec wrap(origin(), DateTime.t()) :: t()
Wraps the argument into a slot. For DateTime it'd be a point slot.
For a Date, it would be the whole day $[00:00:00.000000 o 00:00:00.000000 ext{ next day})$.
Examples
iex> Tempus.Slot.wrap(~D|2020-08-06|)
%Tempus.Slot{from: ~U[2020-08-06 00:00:00.000000Z], to: ~U[2020-08-07 00:00:00.000000Z], from_open: false, to_open: true}
iex> Tempus.Slot.wrap(:ok)
Tempus.Slot.id()
Splits the slot given as a first argument to two on borders given as a second slot.
Examples
iex> outer = Tempus.Slot.wrap(~D[2023-04-12])
...> {:ok, inner} = Tempus.Slot.new(~U[2023-04-12 12:00:00Z], ~U[2023-04-12 13:00:00Z])
iex> Tempus.Slot.xor(outer, inner)
[%Tempus.Slot{from: ~U[2023-04-12 00:00:00.000000Z], to: ~U[2023-04-12 12:00:00Z], from_open: false, to_open: true},
%Tempus.Slot{from: ~U[2023-04-12 13:00:00Z], to: ~U[2023-04-13 00:00:00.000000Z], from_open: true, to_open: true}]
iex> Tempus.Slot.xor(outer, inner) == Tempus.Slot.xor(inner, outer)
false
iex> {:ok, past} = Tempus.Slot.new(~U[2020-04-12 12:00:00Z], ~U[2020-04-12 13:00:00Z])
...> Tempus.Slot.xor(past, inner)
[past, inner]
...> Tempus.Slot.xor(inner, past)
[past, inner]
iex> {:ok, border} = Tempus.Slot.new(~U[2023-04-12 11:00:00Z], ~U[2023-04-12 12:00:00Z])
...> Tempus.Slot.xor(border, inner)
[Tempus.Slot.new!(~U[2023-04-12 11:00:00Z], ~U[2023-04-12 13:00:00Z])]