zocam — composable time for Elixir

ZOCAM_

Composable time for Elixir. Point names a calendar thing, Span builds sets of instants over points, and ground/3 maps a set to concrete intervals in a timezone — DST included.

Zocam is the Muisca year: a cycle of twenty moons, recorded in colonial Bogotá and later studied by Humboldt. A library about calendars carries a calendar word.

AI SLOP An AI agent wrote this page. yuri4n, a senior engineer, gave the direction and did the review. The review is human, thus errors can stay.

The two layers

LayerModuleWhat it holds
CalendarZocam.PointOne calendar thing: "May", "the 23rd", "15:00"
CalendarZocam.SpanSets of instants: arcs, steps, ordinals, set algebra
TimelineZocam.IntervalsConcrete intervals: union, intersection, complement, diff

Figure 1 — The two layers of the library and the module that owns each. AI generated, human reviewed.

flowchart LR
  P["Zocam.Point<br/>one calendar thing<br/>(May · the 23rd · 15:00)"]
  S["Zocam.Span<br/>sets of instants<br/>(arcs · steps · nth · algebra)"]
  I["Zocam.Intervals<br/>concrete intervals<br/>on the timeline"]
  P -- "Span.of/1 · arc!/1" --> S
  S -- "ground/3 (timezone, DST)" --> I

Figure 2 — The two layers and the one crossing point: a span meets the real timeline only in ground/3. AI generated, human reviewed.

One recipe

You want every second Friday, 09:00–12:00, as concrete UTC intervals. Name the day, set the rhythm, cut the hours, intersect, and ground:

fridays =
  Zocam.Point.weekday(:friday)
  |> Zocam.Point.every(2, ~D[2026-01-02])
  |> Zocam.Span.of()

morning =
  Zocam.Span.arc!(
    from: Zocam.Point.time(~T[09:00:00]),
    until: Zocam.Point.time(~T[12:00:00])
  )

span = Zocam.Span.intersection([fridays, morning])

horizon = %{
  from: ~U[2026-01-01 00:00:00Z],
  until: ~U[2026-02-01 00:00:00Z],
  left: :closed,
  right: :open
}

Zocam.Span.ground(span, horizon, "Etc/UTC").intervals
#=> Jan 2, Jan 16, and Jan 30 — each 09:00–12:00 UTC

The same recipe opens the Zocam.Span reference, where it runs as a doctest — the test suite proves it stays true.

One denotation, two questions

    Points: calendar values
    Zocam.Point models one calendar thing as a scope plus a refinement chain — concrete ("May 2026") or abstract ("May"). The shape of a point is a design decision on its own: see ADR-001.
    Spans: sets of instants
    Zocam.Span is a recursive set algebra over points and arcs: unions, intersections, complements, steps, and ordinal picks such as "last working day of the month". Sets come first; single moments are the special case.
    The interval kernel
    Zocam.Intervals is the linear kernel: sets of concrete intervals with union, intersection, complement, and difference. Span.member?/2 and Span.ground/3 share ONE denotation function, so the two answers can never disagree — even across a DST jump.
Copyright © 2026