View Source Tempus (Tempus v0.15.0)
Tempus
is a library to deal with timeslots.
It aims to be a fast yet easy to use implementation of a schedule of any type, including but not limited to free/busy time schedules.
The example of it might be a calendar software, where slots might be marked as free, or busy. It also allows simple arithmetics with schedules, like adding five days or subtracting 7 hours 30 minutes from now, considering busy slots.
Summary
Types
Number of slots (:stream
means lazy folding, unknown upfront)
Direction for slots navigation
Navigation option
Argument containing navigation options
The type defining how slicing is to be applied.
When :greedy
, overlapping boundary slots would be included,
:reluctant
would take only those fully contained in the interval.
Functions
Adds an amount of units to the origin, considering slots given.
Returns the reversed list of free days after origin.
Returns the reversed list of free days after origin.
Returns the reversed list of free days after origin.
Drops slots at the beginning of the %Slots{}
struct while fun
returns a truthy value.
Checks whether the slot is disjoined against slots.
Helper to instantiate slot from any known format, by wrapping the argument.
Helper to instantiate slot from any known format, by joining the arguments.
Creates the new instance of Tempus.Slots.t(Tempus.Slots.Stream.t())
backed by stream.
Returns the next busy slot from the slots passed as a first argument,
that immediately follows origin
. If slots are overlapped, the overlapped
one gets returned.
Returns the next free slot from the slots passed as a first argument,
that immediately follows origin
. If slots are overlapped, the overlapped
one gets returned.
Creates a Tempus.Slots.Stream.t()
slots stream by a cron-like definition.
Slices the %Slots{}
based on origins from
and to
and an optional type
(default: :reluctant
.) Returns sliced %Slots{}
back.
Syntactic sugar for |> Enum.into(%Slots{})
.
Syntactic sugar for Tempus.Slots.Stream.iterate/3
with default options return_as: :slots, join: true
.
Takes an amount of elements from the beginning of the Tempus.Slots
structure, returning Tempus.Slots.List
instance.
Takes slots at the beginning of the %Slots{}
struct while fun
returns a truthy value.
Types
@type count() :: :infinity | :stream | non_neg_integer()
Number of slots (:stream
means lazy folding, unknown upfront)
@type direction() :: :fwd | :bwd
Direction for slots navigation
@type option() :: {:origin, Tempus.Slot.origin()} | {:count, count() | neg_integer()} | {:direction, direction()}
Navigation option
@type options() :: [option()]
Argument containing navigation options
@type slice_type() :: :greedy | :reluctant
The type defining how slicing is to be applied.
When :greedy
, overlapping boundary slots would be included,
:reluctant
would take only those fully contained in the interval.
Functions
add(slots, origin \\ DateTime.utc_now(), amount_to_add, unit \\ :second)
View Source@spec add( slots :: Tempus.Slots.t(), origin :: DateTime.t(), amount_to_add :: integer(), unit :: System.time_unit() ) :: DateTime.t()
Adds an amount of units to the origin, considering slots given.
Examples
iex> slots = [
...> ~D|2020-08-07|,
...> ~D|2020-08-10|,
...> ~D|2020-08-11|,
...> ~D|2020-08-14|
...> ] |> Enum.into(Tempus.Slots.new(:stream, []))
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, 0, :second)
~U[2020-08-12 00:00:00Z]
iex> Tempus.add(slots, ~U|2020-08-12 01:00:00Z|, 0, :second)
~U[2020-08-12 01:00:00Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, -10*60+1, :second)
~U[2020-08-09 23:50:00Z]
iex> Tempus.add(slots, ~U|2020-08-12 00:09:00Z|, -10*60, :second)
~U[2020-08-09 23:59:00Z]
iex> Tempus.add(slots, ~U|2020-08-12 00:10:00Z|, -10*60, :second)
~U[2020-08-12 00:00:00Z]
iex> Tempus.add(slots, ~U|2020-08-12 00:10:00Z|, -10*60-1, :second)
~U[2020-08-09 23:59:59Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, 10*60, :second)
~U[2020-08-12 00:10:00Z]
iex> Tempus.add(slots, ~U|2020-08-12 00:00:00Z|, 10*60, :second)
~U[2020-08-12 00:10:00Z]
iex> Tempus.add(slots, ~U|2020-08-09 23:55:00Z|, 10*60, :second)
~U[2020-08-12 00:05:00Z]
iex> Tempus.add(slots, ~U|2020-08-08 23:55:00Z|, 10*60, :second)
~U[2020-08-09 00:05:00Z]
iex> Tempus.add(slots, ~U|2020-08-06 23:55:00Z|, 2*3600*24 + 10*60, :second)
~U[2020-08-12 00:05:00Z]
iex> slots = Tempus.Slots.new(:stream, [])
iex> Tempus.add(slots, ~U|2020-08-12 01:00:00Z|, 0, :second)
~U[2020-08-12 01:00:00Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, 5*60+1, :second)
~U[2020-08-11 23:05:01.000000Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, -10*60+1, :second)
~U[2020-08-11 22:50:01Z]
iex> slots = Tempus.Slots.new(:list, [])
iex> Tempus.add(slots, ~U|2020-08-12 01:00:00Z|, 0, :second)
~U[2020-08-12 01:00:00Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, 5*60+1, :second)
~U[2020-08-11 23:05:01Z]
iex> Tempus.add(slots, ~U|2020-08-11 23:00:00Z|, -10*60+1, :second)
~U[2020-08-11 22:50:01Z]
iex> slots |> Tempus.add(1) |> DateTime.to_date()
Date.utc_today()
@spec days_add(slots :: Tempus.Slots.t(), opts :: options()) :: [Date.t()]
Returns the reversed list of free days after origin.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.days_add(slots, origin: ~D|2020-08-07|, count: 3) |> hd()
~D|2020-08-12|
iex> Tempus.days_add(slots, origin: ~D|2020-08-07|, count: 3, direction: :fwd) |> hd()
~D|2020-08-12|
iex> Tempus.days_add(slots, origin: ~D|2020-08-07|, count: -3, direction: :bwd) |> hd()
~D|2020-08-12|
iex> Tempus.days_add(slots, origin: ~D|2020-08-12|, count: -4) |> hd()
~D|2020-08-06|
iex> Tempus.days_add(slots, origin: ~D|2020-08-12|, count: 4, direction: :bwd) |> hd()
~D|2020-08-06|
iex> Tempus.days_add(slots, origin: ~D|2020-08-12|, count: -4, direction: :fwd) |> hd()
~D|2020-08-06|
@spec days_ago(slots :: Tempus.Slots.t(), origin :: Date.t(), count :: integer()) :: [ Date.t() ]
Returns the reversed list of free days after origin.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.days_ago(slots, ~D|2020-08-07|, 0)
[~D|2020-08-06|]
iex> Tempus.days_ago(slots, ~D|2020-08-12|, 4) |> hd()
~D|2020-08-06|
@spec days_ahead(slots :: Tempus.Slots.t(), origin :: Date.t(), count :: integer()) :: [Date.t()]
Returns the reversed list of free days after origin.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.days_ahead(slots, ~D|2020-08-07|, 0)
[~D|2020-08-08|]
iex> Tempus.days_ahead(slots, ~D|2020-08-07|, 3) |> hd()
~D|2020-08-12|
@spec drop_while(slots :: Tempus.Slots.t(), fun :: (Tempus.Slot.t() -> boolean())) :: Tempus.Slots.t()
Drops slots at the beginning of the %Slots{}
struct while fun
returns a truthy value.
Examples
iex> import Tempus.Guards
...> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> slots
...> |> Tempus.drop_while(&is_slot_coming_before(&1, Tempus.Slot.wrap(~D|2020-08-09|)))
...> |> Enum.count()
1
@spec free?(slots :: Tempus.Slots.t(), slot :: Tempus.Slot.origin()) :: boolean()
Checks whether the slot is disjoined against slots.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.free?(slots, ~D|2020-08-07|)
false
iex> Tempus.free?(slots, ~D|2020-08-08|)
true
iex> Tempus.free?(slots, ~U|2020-08-09T23:59:59.999999Z|)
true
iex> Tempus.free?(slots, DateTime.add(~U|2020-08-09T23:59:59.999999Z|, 1, :microsecond))
false
@spec guess(input :: nil | binary()) :: {:ok, Tempus.Slot.t()} | {:error, any()}
Helper to instantiate slot from any known format, by wrapping the argument.
Examples
iex> Tempus.guess("2023-04-10")
{:ok, Tempus.Slot.wrap(~D[2023-04-10])}
iex> Tempus.guess(nil)
{:ok, %Tempus.Slot{from: nil, to: nil}}
iex> Tempus.guess("∞")
{:ok, %Tempus.Slot{from: nil, to: nil}}
iex> Tempus.guess("")
{:ok, %Tempus.Slot{from: nil, to: nil}}
iex> Tempus.guess("2023-04-10T10:00:00Z")
{:ok, Tempus.Slot.wrap(~U[2023-04-10T10:00:00Z])}
iex> Tempus.guess("10:00:00")
{:ok, Date.utc_today() |> DateTime.new!(Time.from_erl!({10, 0, 0})) |> Tempus.Slot.wrap()}
iex> Tempus.guess("20230410T235007.123+0230")
if Version.compare(System.version(), "1.14.0") == :lt do
{:error, :invalid_format}
else
{:ok, Tempus.Slot.wrap(~U[2023-04-10T21:20:07.123Z])}
end
iex> Tempus.guess("2023-04-10-15")
{:error, :invalid_format}
@spec guess(from :: nil | binary(), to :: nil | binary()) :: {:ok, Tempus.Slot.t()} | {:error, any()}
Helper to instantiate slot from any known format, by joining the arguments.
Examples
iex> import Tempus.Sigils
iex> Tempus.guess("2023-04-10", "2023-04-12")
{:ok, ~I[2023-04-10 00:00:00.000000Z|2023-04-12 23:59:59.999999Z]}
iex> Tempus.guess("2023-04-10", nil)
{:ok, ~I[2023-04-10 00:00:00.000000Z|2023-04-10T23:59:59.999999Z]}
iex> Tempus.guess("2023-04-10T10:00:00Z", "2023-04-12")
{:ok, ~I[2023-04-10 10:00:00Z|2023-04-12 23:59:59.999999Z]}
iex> Tempus.guess("20230410T235007.123+0230", "2023-04-12")
if Version.compare(System.version(), "1.14.0") == :lt do
{:error, {:invalid_arguments, [from: :invalid_format]}}
else
{:ok, ~I[2023-04-10 21:20:07.123Z|2023-04-12 23:59:59.999999Z]}
end
iex> Tempus.guess("2023-04-10", :ok)
{:error, {:invalid_arguments, [to: :invalid_argument]}}
iex> Tempus.guess(:ok, "2023-04-10")
{:error, {:invalid_arguments, [from: :invalid_argument]}}
iex> Tempus.guess("2023-04-10-15", :ok)
{:error, {:invalid_arguments, [from: :invalid_format, to: :invalid_argument]}}
iex> Tempus.guess("2023-20-40", "10:70:80")
{:error, {:invalid_arguments, [from: :invalid_date, to: :invalid_time]}}
@spec new(data :: Tempus.Slots.container()) :: Tempus.Slots.t(Tempus.Slots.Stream)
Creates the new instance of Tempus.Slots.t(Tempus.Slots.Stream.t())
backed by stream.
This is a convinience wrapper for Tempus.Slots.new(:stream, data)
.
@spec next_busy(Tempus.Slots.t(), options()) :: [Tempus.Slot.t()] | Tempus.Slot.t() | nil | no_return()
Returns the next busy slot from the slots passed as a first argument,
that immediately follows origin
. If slots are overlapped, the overlapped
one gets returned.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-08 23:00:00Z|, to: ~U|2020-08-09 12:00:00Z|})
%Tempus.Slot{from: ~U[2020-08-10 00:00:00.000000Z], to: ~U[2020-08-10 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-07 11:00:00Z|, to: ~U|2020-08-07 12:00:00Z|}, count: 2) |> hd()
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-07 11:00:00Z|, to: ~U|2020-08-08 12:00:00Z|})
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-07 11:00:00Z|, to: ~U|2020-08-10 12:00:00Z|})
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: ~D|2020-08-07|)
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: ~D|2020-08-08|)
%Tempus.Slot{from: ~U[2020-08-10 00:00:00.000000Z], to: ~U[2020-08-10 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: ~D|2020-08-08|, direction: :bwd)
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: ~D|2020-08-10|, direction: :bwd)
%Tempus.Slot{from: ~U[2020-08-10 00:00:00.000000Z], to: ~U[2020-08-10 23:59:59.999999Z]}
iex> Tempus.next_busy(slots, origin: ~D|2020-08-10|, direction: :bwd, count: :infinity)
slots
iex> Tempus.next_busy(slots, origin: ~D|2020-08-10|, count: 3.1415)
** (Tempus.ArgumentError) invalid argument: expected ‹Elixir.Integer›, got: ‹3.1415›
iex> Tempus.next_busy(slots, origin: ~D|2020-08-10|, direction: :bwd, count: 2)
Enum.to_list(slots)
iex> Tempus.next_busy(slots, origin: ~D|2020-08-10|, direction: :bwd, count: 1)
Enum.drop(slots, 1)
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-11 11:00:00Z|, to: ~U|2020-08-11 12:00:00Z|})
nil
iex> Tempus.next_busy(slots, origin: %Tempus.Slot{from: ~U|2020-08-06 11:00:00Z|, to: ~U|2020-08-06 12:00:00Z|}, direction: :bwd)
nil
iex> Tempus.next_busy(%Tempus.Slots{})
nil
@spec next_free(Tempus.Slots.t(), options()) :: [Tempus.Slot.t()] | Tempus.Slot.t() | no_return()
Returns the next free slot from the slots passed as a first argument,
that immediately follows origin
. If slots are overlapped, the overlapped
one gets returned.
Examples
iex> import Tempus.Sigils
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|),
...> Tempus.Slot.wrap(~D|2020-08-12|),
...> Tempus.Slot.wrap(~D|2020-08-14|),
...> Tempus.Slot.wrap(~D|2030-08-14|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> Tempus.next_free(slots, origin: %Tempus.Slot{from: ~U|2020-08-08 23:00:00Z|, to: ~U|2020-08-09 12:00:00Z|})
~I[2020-08-08 00:00:00.000000Z → 2020-08-09 23:59:59.999999Z]
iex> Tempus.next_free(slots, origin: %Tempus.Slot{from: ~U|2020-08-06 11:00:00Z|, to: ~U|2020-08-06 12:00:00Z|})
~I[∞ → 2020-08-06 23:59:59.999999Z]nu
iex> Tempus.next_free(slots, origin: ~U|2020-08-13 01:00:00.000000Z|)
~I[2020-08-13 00:00:00.000000Z → 2020-08-13 23:59:59.999999Z]
iex> Tempus.next_free(slots, origin: ~D|2020-08-13|)
~I[2020-08-13 00:00:00.000000Z → 2020-08-13 23:59:59.999999Z]
iex> Tempus.next_free(slots, origin: ~D|2020-08-14|)
~I[2020-08-15 00:00:00.000000Z → 2030-08-13 23:59:59.999999Z]
iex> Tempus.next_free(slots)
~I[2020-08-15 00:00:00.000000Z → 2030-08-13 23:59:59.999999Z]
@spec parse_cron( origin :: Tempus.Slot.origin() | nil, cron :: binary() | Tempus.Crontab.t() ) :: Tempus.Slots.t(Stream)
Creates a Tempus.Slots.Stream.t()
slots stream by a cron-like definition.
Examples
iex> import Tempus.Sigils
iex> ~U[2024-06-07 12:00:00Z] |> Tempus.parse_cron("10-30/15 */4 1 */1 6,7") |> Enum.take(5)
[
~I(2024-06-08T00:10:00Z → 2024-06-08T00:10:00Z),
~I(2024-06-08T00:25:00Z → 2024-06-08T00:25:00Z),
~I(2024-06-08T04:10:00Z → 2024-06-08T04:10:00Z),
~I(2024-06-08T04:25:00Z → 2024-06-08T04:25:00Z),
~I(2024-06-08T08:10:00Z → 2024-06-08T08:10:00Z)
]
@spec slice( slots :: Tempus.Slots.t(), from :: Tempus.Slots.locator(), to :: Tempus.Slots.locator(), type :: slice_type() ) :: Tempus.Slots.t()
Slices the %Slots{}
based on origins from
and to
and an optional type
(default: :reluctant
.) Returns sliced %Slots{}
back.
Examples
iex> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> slots |> Tempus.slice(~D|2020-08-07|, ~D|2020-08-11|) |> Enum.count()
1
See Tempus.Slot.new/2
.
See Tempus.Slot.new!/2
.
Syntactic sugar for |> Enum.into(%Slots{})
.
Examples
iex> [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|),
...> Tempus.Slot.wrap(~D|2020-08-12|)
...> ] |> Tempus.slots()
%Tempus.Slots{slots: %Tempus.Slots.List{slots: [
%Tempus.Slot{from: ~U[2020-08-07 00:00:00.000000Z], to: ~U[2020-08-07 23:59:59.999999Z]},
%Tempus.Slot{from: ~U[2020-08-10 00:00:00.000000Z], to: ~U[2020-08-10 23:59:59.999999Z]},
%Tempus.Slot{from: ~U[2020-08-12 00:00:00.000000Z], to: ~U[2020-08-12 23:59:59.999999Z]}]}}
@spec stream(Tempus.Slot.origin(), (Tempus.Slot.t() -> Tempus.Slot.t())) :: Tempus.Slots.t(Tempus.Slots.Stream)
Syntactic sugar for Tempus.Slots.Stream.iterate/3
with default options return_as: :slots, join: true
.
Examples
iex> import Tempus.Sigils
...> Tempus.stream(~D|2024-01-20|, &Tempus.Slot.shift(&1, by: rem(&1.from.day, 2) + 1, unit: :day)) |> Enum.take(3)
[~I(2024-01-20T00:00:00.000000Z → 2024-01-21T23:59:59.999999Z),
~I(2024-01-23T00:00:00.000000Z → 2024-01-23T23:59:59.999999Z),
~I(2024-01-25T00:00:00.000000Z → 2024-01-25T23:59:59.999999Z)]
@spec take(Tempus.Slots.t(), non_neg_integer()) :: Tempus.Slots.t(Tempus.Slots.List)
Takes an amount of elements from the beginning of the Tempus.Slots
structure, returning Tempus.Slots.List
instance.
If amount is 0, it returns Tempus.Slots.List.new/0
.
Examples
iex> import Tempus.Sigils
...> Tempus.stream(~D|2024-01-20|, &Tempus.Slot.shift(&1, by: rem(&1.from.day, 2) + 1, unit: :day)) |> Tempus.take(3)
Tempus.Slots.new(:list, [~I(2024-01-20T00:00:00.000000Z → 2024-01-21T23:59:59.999999Z),
~I(2024-01-23T00:00:00.000000Z → 2024-01-23T23:59:59.999999Z),
~I(2024-01-25T00:00:00.000000Z → 2024-01-25T23:59:59.999999Z)])
@spec take_while(slots :: Tempus.Slots.t(), fun :: (Tempus.Slot.t() -> boolean())) :: Tempus.Slots.t()
Takes slots at the beginning of the %Slots{}
struct while fun
returns a truthy value.
Examples
iex> import Tempus.Guards
...> slots = [
...> Tempus.Slot.wrap(~D|2020-08-07|),
...> Tempus.Slot.wrap(~D|2020-08-10|)
...> ] |> Enum.into(%Tempus.Slots{})
iex> slots
...> |> Tempus.take_while(&is_slot_coming_before(&1, Tempus.Slot.wrap(~D|2020-08-09|)))
...> |> Enum.count()
1