Skip to content
@elixir-tempo

Elixir Tempo

TIme as a set of intervals, not instants

Elixir Tempo

Time as an interval, not an instant.

Every mainstream language gives you date, time, and datetime as scalar points, and then leaves you to invent the spans yourself. Is ~D[2026-06-15] the instant of midnight, or the whole day? The type cannot say — so every codebase grows its own end_of_day/1, its own month-end arithmetic, its own quiet off-by-one.

These libraries take the other road. A temporal value is a bounded span at a stated resolution: 2026 is the whole year, 2026-06 the whole month, 2026-06-15T10 that one hour. Once time is an interval, the operations that are awkward elsewhere become ordinary — iteration, comparison, overlap, free-busy, set algebra — and a class of bugs stops being possible rather than being caught.

The libraries

tempo — the core

Hex.pm Docs

One type for every temporal value — a year, an afternoon, a meeting, an archaeological period, a recurring event, a whole free-busy calendar — with every operation defined uniformly over it.

  • ISO 8601 Parts 1 and 2, plus IXDTF and RFC 5545 RRULE. Partial dates, unspecified digits, uncertainty qualifiers, and ± margins are first-class rather than parse errors.

  • Set algebra over time — union, intersection, difference, and overlap depth, on a half-open [from, to) convention that makes adjacent spans join cleanly.

  • Allen's interval algebra — all thirteen relations, with three-valued certainty (:certain / :possible / :impossible) when the inputs are uncertain.

  • Recurrence in ISO 8601 or RFC 5545 form, expanded correctly across daylight-saving transitions.

  • iCalendar and JSCalendar import — RFC 5545 .ics through Tempo.ICal.from_ical/2, RFC 7953 VAVAILABILITY through Tempo.ICal.available_from_ical/2 (the time someone offers, as against the time they have taken), and RFC 8984 JSCalendar through Tempo.JSCalendar.from_jscalendar/2. Event metadata — summary, location, attendees, status — rides along on each interval, so intersecting a schedule with work hours tells you which meetings fall inside, not merely how much time.

  • Calendar- and territory-aware arithmetic through Localize and Calendrical — non-Gregorian calendars, locale-aware formatting, and workday arithmetic that knows which days are the weekend where you are.

  • Constraint networks and dependency scheduling — critical paths, and consistency checking over relative or undated constraints.

work  = ~o"2026-06-15T09:00:00/2026-06-15T17:00:00"
lunch = ~o"2026-06-15T12:00:00/2026-06-15T13:00:00"

{:ok, free} = Tempo.difference(work, lunch)
free |> Tempo.IntervalSet.slots(~o"PT1H") |> Tempo.IntervalSet.count()
#=> 7

"The workday minus lunch, cut into bookable hours."

tempo_sql — persistence

Hex.pm Docs

Ecto types and migration helpers that store Tempo intervals as PostgreSQL range values.

PostgreSQL already models this shape natively: a tstzrange is a bounded span with the same half-open convention, and tstzmultirange is a set of disjoint spans. There is no impedance mismatch to paper over — an interval dumps straight to a range, loads straight back, and takes part in Postgres range queries (@>, &&, -|-) as a first-class value.

create table(:meetings) do
  add :name, :string
  add_interval :window, null: false
end

Requires PostgreSQL 14+ for multirange support; tstzrange alone works on 9.2+.

jscalendar — interchange

Hex.pm Docs

RFC 8984 JSCalendar — events, tasks and groups — read and written as ordinary JSON.

JSCalendar carries what iCalendar carries, in a form that needs no parser of its own. Where iCalendar's line folding, backslash escaping and property parameters have produced two decades of interoperability bugs, this is a JSON object with named fields — so Erlang's :json and Elixir's Duration are the entire toolchain and the library has no dependencies at all. Values arrive as standard types: NaiveDateTime, DateTime, Duration, MapSet.

An event carries a start and a duration rather than a start and an end — the same commitment the rest of these libraries make. The end is start + duration in the event's own time zone, so an hour-long meeting stays an hour long across a daylight-saving boundary, where a stored end time would silently become two hours or none.

{:ok, event} = JSCalendar.decode(~s({
  "@type": "Event",
  "uid": "a8df6573-0474-496d-8496-033ad45d7fea",
  "title": "Budget review",
  "start": "2026-03-07T13:00:00",
  "timeZone": "America/New_York",
  "duration": "PT1H"
}))

JSCalendar.Event.ends_at(event)
#=> ~N[2026-03-07 14:00:00]

Properties the library has never heard of are kept and written back untouched, and properties equal to their RFC default are omitted on encode, so a document neither loses information nor grows each time it is relayed. Expanding a recurrence rule into the dates it generates is calendar arithmetic rather than a format concern, and stays with tempo — Tempo.JSCalendar.from_jscalendar/2 reads a document straight onto a timeline as a Tempo.IntervalSet, recurrence and overrides resolved.

Requires OTP 27 or later — that is where Erlang's :json arrived.

Where to start

New to the idea? The Tempo README opens with why intervals remove whole classes of bug, and the getting-started livebook is a tour you can run in Livebook without installing anything.

If you would rather see it applied, the cookbook works through the problems people actually bring — free-busy, recurring events, business days, uncertain historical dates, and scheduling across time zones.

Contributing

Issues and pull requests are welcome on the individual repositories. All the libraries hold to the same standards: formatted, no compiler warnings, tests and doctests passing, Dialyzer clean, Credo strict, and documentation that builds without warnings. Every example in the documentation is executed rather than illustrated — if a snippet is in the docs, it runs.

Licence

Apache-2.0.

Popular repositories Loading

  1. tempo tempo Public

    Date/Time library for Elixir based upon intervals not instants

    Elixir 215 8

  2. tempo_sql tempo_sql Public

    Ecto types for Tempo and Postgres

    Elixir 7 1

  3. agenda agenda Public

    Scheduling based upon Tempo (time as intervals, not instants)

    Elixir 4

  4. tempo_holidays tempo_holidays Public

    Worldwide public holidays as Tempo recurrences, compiled from the date-holidays dataset

    Elixir 2

  5. jscalendar jscalendar Public

    Reads and writes RFC 8984 JSCalendar objects

    Elixir 1

  6. .github .github Public

    Organization overview

Repositories

Showing 10 of 10 repositories

People

This organization has no public members. You must be a member to see who’s a part of this organization.

Top languages

Loading…

Most used topics

Loading…