Tempus Tempus    Kantox ❤ OSS  Test  Dialyzer  Coverage Status

Easy handling of time periods aka slots, like business days, holidays, schedules, and cron-like recurring slots.


Why Estructura? Elevating Code Quality

Tempus and related libraries in the ecosystem rely heavily on Estructura, an advanced data-structuring and domain-modelling framework for Elixir.

Building complex domain libraries (especially time-arithmetic and schedule processing engines) requires strict data guarantees, type-safety, and minimal boilerplate. Estructura dramatically improves code quality across several crucial dimensions:

1. Strict Validation & Type Coercion at Boundaries

Instead of manually writing defensive functions or risking invalid internal state, Estructura allows defining strict field schemas with type coercion and validation contracts. Inputs are normalized at construction time, ensuring data structures are guaranteed valid throughout the pipeline.

defmodule BusinessHours do
use Estructura
defstruct [
open_time: {Time, :from_iso8601!},
close_time: {Time, :from_iso8601!}
]
# Enforces runtime contracts and validates constraints
def validate(%__MODULE__{open_time: o, close_time: c}) do
if Time.compare(c, o) == :gt, do: :ok, else: {:error, :invalid_hours}
end
end
# Coerces ISO8601 string inputs automatically into %Time{} structs
{:ok, hours} = BusinessHours.new(open_time: "09:00:00", close_time: "17:00:00")

2. Guard & Pattern Matching Primitives

Estructura generates compile-time guard helpers and pattern-matching primitives (is_...), avoiding ad-hoc map key checks or defensive is_map/1 guards across module boundaries.

3. Ergonomic Immutability & Nested Access (Access Protocol & Lenses)

Manipulating deeply nested data structures in idiomatic Elixir can become verbose. Estructura generates full Access protocol implementations and lens transformers (get_in, put_in, update_in), making complex nested updates concise and type-safe.

# Update nested values safely using generated Access lenses
update_in(schedule, [:business_hours, :open_time], &Time.add(&1, 3600))

4. Automated Property-Based Testing Integration

Estructura integrates directly with StreamData, automatically deriving generators for data structures. This allows instant property-based testing of domain models without manually constructing complex test generators.

# Automatically generate random valid structs for property tests
property "slots are always valid" do
check all slot <- BusinessHours.generator() do
assert Time.compare(slot.close_time, slot.open_time) == :gt
end
end

Key Concepts in Tempus

Tempus models time periods using half-open intervals $[from, to)$ (from_open: false, to_open: true by default):


Installation

Add tempus to your list of dependencies in mix.exs:

def deps do
[
{:tempus, "~> 0.16"}
]
end

Usage Examples

1. Creating Slots with ~I Sigil

import Tempus.Sigils
# Whole day slot [2023-04-10 00:00:00Z -> 2023-04-11 00:00:00Z)
day_slot = ~I(2023-04-10)d
# Explicit datetime slot
slot = ~I[2023-04-10 09:00:00Z → 2023-04-10 17:00:00Z)

2. Schedule Arithmetic & Busy/Free Time

alias Tempus.{Slot, Slots}
import Tempus.Sigils
# Define holiday and weekend slots
holidays = [~D[2020-08-06], ~D[2020-08-13]] |> Tempus.slots()
weekends = [~D[2020-08-08], ~D[2020-08-09]] |> Tempus.slots()
schedule = Slots.merge(holidays, weekends)
# Calculate 5 business days ahead starting from Aug 5th
business_days = Tempus.days_ahead(schedule, ~D[2020-08-05], 5)

Changelog


Documentation