Tempo.Builder (Tempo v2.1.1)

Copy Markdown View Source

Builds an expression a step at a time, so that a pipeline reads as the sentence it means.

import Tempo.Builder

every(:week)
|> on(:monday)
|> at(hour: 9)
|> starting(~o"2026-06-01")
|> times(10)

Every week, on Monday, at nine, starting the 1st of June, ten times.

Each step takes an expression, or the result of the step before it, and returns {:ok, expression} or {:error, exception}. An error passes through every step after it untouched, so a pipeline ends with the first error it met. What each step returns is a complete expression, the one its ISO 8601 text reads to: every(:week) |> on(:monday) is ~o"R/../P1W/FL1KN", as usable as the finished pipeline.

The steps refine any expression where they have a meaning: the same on/3 and at/2 that refine a recurrence refine a value, so ~o"2026-06" |> on(:monday) |> at(hour: 9) is the Mondays of June 2026 at nine.

The steps, by what they say:

The module is meant to be imported. Its names are English words, and at/2 and on/3 are not Tempo.at/2 and Tempo.on/2, which place one value on another.

Summary

Types

What each step returns.

An expression, or the result of a step: what each step takes.

A day of the week by its name.

Functions

A set of expressions, each a member: a holiday and the day it is moved to, a rule and the dates given beside it.

Marks a value approximate, as ISO 8601-2's ~ does: the whole value, or the units named. A unit marked approximate and uncertain is both (%).

Selects a time of day: in each repeat of a recurrence, or in a value.

Where a recurrence counted back from its end ends: its last repeat ends with this value.

A recurrence of a cadence, with no start and no end yet: the first step of a rule.

An expression with some values left out of it: a set of it, and the values excluded.

The nth group of a length within a value, as ISO 8601-2 writes one (2026Y2G3MU): the second three months of 2026.

The calendar a recurrence with no start of its own counts its years, months and days in. The years of a domain in_years/3 gave it are read as years of this calendar. A recurrence with a start is in its start's calendar, and a value is converted with Tempo.to_calendar/2.

The years a recurrence repeats in: a domain of years, or every year of a kind.

An expression in a time zone: its clock readings are the zone's.

The days an expression selects, each the start of a stretch of a length: a holiday of several days.

Selects days: in each repeat of a recurrence, or in a value.

A value with one of its units left unspecified, as ISO 8601-2's X* writes one: some day of June 2026 is 2026Y6MX*D.

Where a recurrence begins: its first repeat is the one that holds this value.

How many occurrences a recurrence has.

Marks a value uncertain, as ISO 8601-2's ? does: the whole value, or the units named. A unit marked uncertain and approximate is both (%).

Where an expression ends, the value it runs through: a recurrence's last repeat is the one that holds it, and a value runs through it as an interval.

An expression whose weeks begin on another day than Monday: its numbered weeks are counted from that day, and a recurrence of every second week or more steps in those weeks. It is RFC 5545's WKST, written [u-fw=sun].

An expression carrying a caller's map: a recurrence's every occurrence carries it too.

Types

result()

@type result() :: {:ok, Tempo.t()} | {:error, Exception.t()}

What each step returns.

step()

@type step() :: Tempo.t() | {:ok, Tempo.t()} | {:error, Exception.t()}

An expression, or the result of a step: what each step takes.

weekday()

@type weekday() ::
  :monday | :tuesday | :wednesday | :thursday | :friday | :saturday | :sunday

A day of the week by its name.

Functions

all_of(steps)

@spec all_of([step()]) :: result()

A set of expressions, each a member: a holiday and the day it is moved to, a rule and the dates given beside it.

Arguments

  • steps is a list of expressions, or the results of pipelines; the first error among them is returned.

Returns

Examples

iex> import Tempo.Builder
iex> all_of([
...>   every(:year) |> on(month: 12, day: 25, day_of_week: 1..5),
...>   every(:year) |> on(selection: [month: 12, day: 25, day_of_week: 6..7], window: ~o"P8D", day_of_week: 1, instance: -1)
...> ])
{:ok, ~o"{R/../P1Y/FL12M25D{1..5}KN,R/../P1Y/FLL12M25D{6..7}KN/P8D1K-1IN}"}

approximate(step, units \\ :all)

@spec approximate(step(), :all | Tempo.unit() | [Tempo.unit()]) :: result()

Marks a value approximate, as ISO 8601-2's ~ does: the whole value, or the units named. A unit marked approximate and uncertain is both (%).

Arguments

  • step is a value or a set of them, or the result of the step before.

  • units is :all, the default, or a unit or list of units the value has.

Returns

  • {:ok, expression}, the value marked.

  • {:error, exception} as for uncertain/2.

Examples

iex> import Tempo.Builder
iex> approximate(~o"2026-06")
{:ok, ~o"2026-06~"}

iex> import Tempo.Builder
iex> ~o"2026-06-15" |> uncertain() |> approximate()
{:ok, ~o"2026-06-15%"}

at(step, time)

@spec at(step(), keyword() | Tempo.t()) :: result()

Selects a time of day: in each repeat of a recurrence, or in a value.

Arguments

  • step is an expression, or the result of the step before.

  • time is the time of day: its parts ([hour: 9], [hour: 9, minute: 30]) or a time of day written as a value (~o"T09:30").

Returns

  • {:ok, expression}, the expression with the time of day selected in it.

  • {:error, exception} for an error passed on, a time with a date in it, or a unit the expression already selects.

Examples

iex> import Tempo.Builder
iex> every(:week) |> on(:monday) |> at(hour: 9)
{:ok, ~o"R/../P1W/FL1KT9HN"}

iex> import Tempo.Builder
iex> ~o"2026-06" |> on(:monday) |> at(~o"T09:30")
{:ok, ~o"2026Y6ML1KT9H30MN"}

ending(step, to)

@spec ending(step(), Tempo.t()) :: result()

Where a recurrence counted back from its end ends: its last repeat ends with this value.

Arguments

  • step is a recurrence, or the result of the step before.

  • to is a Tempo.t/0 value.

Returns

  • {:ok, recurrence}, the recurrence counted back from to.

  • {:error, exception} for an error passed on, or a recurrence that already has a start.

Examples

iex> import Tempo.Builder
iex> every(:month) |> ending(~o"2026-06-01") |> times(5)
{:ok, ~o"R5/P1M/2026-06-01"}

every(unit)

@spec every(atom() | Tempo.Duration.t() | keyword()) :: result()

A recurrence of a cadence, with no start and no end yet: the first step of a rule.

Arguments

  • cadence is the length of each repeat: a unit (:year, :quarter, :month, :week, :day, :hour, :minute or :second), a Tempo.Duration.t/0, or a duration's units ([week: 2]).

Returns

  • {:ok, recurrence}, a Tempo.t/0 that repeats with no start of its own until starting/2 gives it one.

  • {:error, exception} for a cadence that is no length.

Examples

iex> Tempo.Builder.every(:week)
{:ok, ~o"R/../P1W"}

iex> Tempo.Builder.every(week: 2)
{:ok, ~o"R/../P2W"}

iex> Tempo.Builder.every(~o"P1M")
{:ok, ~o"R/../P1M"}

except(step, value)

@spec except(step(), Tempo.t() | [Tempo.t()]) :: result()

An expression with some values left out of it: a set of it, and the values excluded.

Arguments

  • step is an expression, or the result of the step before.

  • values is a Tempo.t/0 value, or a list of them.

Returns

Examples

iex> import Tempo.Builder
iex> every(:week) |> starting(~o"2026-06-01") |> except(~o"2026-06-08")
{:ok, ~o"{R/2026-06-01/P1W,^2026-06-08}"}

grouped(step, nth, length)

@spec grouped(
  step(),
  pos_integer() | [pos_integer() | Range.t()] | Range.t(),
  keyword()
) :: result()

The nth group of a length within a value, as ISO 8601-2 writes one (2026Y2G3MU): the second three months of 2026.

Arguments

  • step is a value or a set of them, or the result of the step before.

  • nth is which group: a number from 1, a list of them, or a range.

  • length is the group's length, one unit and its count: [month: 3].

Returns

  • {:ok, expression}, the value with the group in it.

  • {:error, exception} for an error passed on, a count that is none, a length of no one unit, or a group the value cannot hold.

Examples

iex> import Tempo.Builder
iex> grouped(~o"2026", 2, month: 3)
{:ok, ~o"2026Y2G3MU"}

iex> import Tempo.Builder
iex> {:ok, second_ten_days} = grouped(~o"2026-06", 2, day: 10)
iex> second_ten_days |> Tempo.to_interval!() |> Tempo.Interval.instants()
{:ok, {~N[2026-06-11 00:00:00], ~N[2026-06-21 00:00:00]}}

in_calendar(step, calendar)

@spec in_calendar(step(), module()) :: result()

The calendar a recurrence with no start of its own counts its years, months and days in. The years of a domain in_years/3 gave it are read as years of this calendar. A recurrence with a start is in its start's calendar, and a value is converted with Tempo.to_calendar/2.

Arguments

  • step is a recurrence with no start, or the result of the step before.

  • calendar is a calendar module (Calendrical.Hebrew).

Returns

  • {:ok, recurrence}, the recurrence in that calendar.

  • {:error, exception} for an error passed on, a calendar Tempo does not know, or a recurrence with a start.

Examples

iex> import Tempo.Builder
iex> every(:year) |> on(month: 7, day: 1) |> in_calendar(Calendrical.Hebrew)
{:ok, ~o"R/../P1Y/FL7M1DN[u-ca=hebrew]"}

iex> import Tempo.Builder
iex> every(:year) |> on(traditional_month: 2, day: 12) |> in_years(from: 5758) |> in_calendar(Calendrical.Hebrew)
{:ok, ~o"R/{5758Y/..}/P1Y/FL2m12DN[u-ca=hebrew]"}

in_years(step, years, options \\ [])

@spec in_years(step(), atom() | integer() | Range.t() | keyword() | list(), keyword()) ::
  result()

The years a recurrence repeats in: a domain of years, or every year of a kind.

Arguments

  • step is a recurrence, or the result of the step before.

  • years is either a filter, one of :even, :odd, :leap and :common, which keeps those years of every year or of the domain; or the years themselves: a year (2021), a range of them (1949..1988), the years from one on (from: 2005) or up to one (through: 2005), or a list of those. The years are of the recurrence's calendar.

  • options is a keyword list of options.

Options

  • :except is a year, or a list of years, left out of the domain.

Returns

  • {:ok, recurrence}, the recurrence in those years only.

  • {:error, exception} for an error passed on, years that are none of the above, a recurrence that already has a start or a domain, or a filter of a recurrence that starts at one value.

Examples

iex> import Tempo.Builder
iex> every(:year) |> on(month: 2, day: 29) |> in_years(:leap)
{:ok, ~o"R/..l/P1Y/FL2M29DN"}

iex> import Tempo.Builder
iex> every(:year) |> on(month: 9, day: 15) |> in_years(from: 2005)
{:ok, ~o"R/{2005Y/..}/P1Y/FL9M15DN"}

iex> import Tempo.Builder
iex> every(:year) |> on(month: 6, day: 19) |> in_years([from: 1961], except: 2011)
{:ok, ~o"R/{1961Y/..,^2011Y}/P1Y/FL6M19DN"}

in_zone(step, zone)

@spec in_zone(step(), String.t()) :: result()

An expression in a time zone: its clock readings are the zone's.

Arguments

  • step is an expression, or the result of the step before.

  • zone is a time zone's name ("Europe/Paris").

Returns

  • {:ok, expression}, the expression in the zone, as Tempo.in_zone/2 places it.

  • {:error, exception} for an error passed on, or as Tempo.in_zone/2 refuses it.

Examples

iex> import Tempo.Builder
iex> every(:week) |> on(:monday) |> at(hour: 9) |> in_zone("Europe/Paris")
{:ok, ~o"R/../P1W/FL1KT9HN[Europe/Paris]"}

lasting(step, length)

@spec lasting(step(), Tempo.Duration.t() | keyword()) :: result()

The days an expression selects, each the start of a stretch of a length: a holiday of several days.

Arguments

  • step is an expression that selects days, or the result of the step before.

  • length is a Tempo.Duration.t/0, or its units ([day: 8]).

Returns

  • {:ok, expression}, the expression selecting the stretches.

  • {:error, exception} for an error passed on, a length that is none, or an expression that selects nothing.

Examples

iex> import Tempo.Builder
iex> every(:year) |> on(month: 8, day: 16) |> lasting(day: 2)
{:ok, ~o"R/../P1Y/FLL8M16DN/P2DN"}

on(step, days, options \\ [])

@spec on(step(), weekday() | [weekday()] | keyword() | Tempo.t(), keyword()) ::
  result()

Selects days: in each repeat of a recurrence, or in a value.

Arguments

  • step is an expression, or the result of the step before.

  • days is what is selected: a day of the week by its name (:monday), a list of them, the parts of a selection of days ([day: 15], [month: 12, day: 25], [event: "easter"]), or a selection Tempo.selection/1 made.

  • options is a keyword list of options.

Options

  • :nth is which of the days a repeat holds: 2 for the second, -1 for the last.

  • :within is a length counted from what the expression already selects, a Tempo.Duration.t/0 or its units ([day: 8], [day: -3] for the days before it): the days are selected in that window. It says the day a holiday is moved to, and a day counted from Easter.

Returns

  • {:ok, expression}, the expression with the days selected in it.

  • {:error, exception} for an error passed on, days that are none of the above, or a unit the expression already selects.

Examples

iex> import Tempo.Builder
iex> every(:month) |> on(:monday, nth: 2)
{:ok, ~o"R/../P1M/FL1K2IN"}

iex> import Tempo.Builder
iex> every(:year) |> on(month: 12, day: 25)
{:ok, ~o"R/../P1Y/FL12M25DN"}

iex> import Tempo.Builder
iex> on(~o"2026-06", [:monday, :wednesday])
{:ok, ~o"2026Y6ML{1,3}KN"}

Pentecost, the last Sunday in the fifty days from Easter, and a Sunday the 25th of December moved to the Monday in the eight days from it:

iex> import Tempo.Builder
iex> every(:year) |> on(event: "easter") |> on(:sunday, nth: -1, within: [day: 50])
{:ok, ~o"R/../P1Y/FLL(easter)eN/P50D7K-1IN"}

iex> import Tempo.Builder
iex> every(:year) |> on(month: 12, day: 25) |> on(:sunday) |> on(:monday, nth: -1, within: [day: 8])
{:ok, ~o"R/../P1Y/FLL12M25D7KN/P8D1K-1IN"}

some(step, unit)

@spec some(step(), Tempo.unit()) :: result()

A value with one of its units left unspecified, as ISO 8601-2's X* writes one: some day of June 2026 is 2026Y6MX*D.

Arguments

  • step is a value or a set of them, or the result of the step before.

  • unit is the unit left unspecified, one the value does not have.

Returns

  • {:ok, expression}, the value with the unit unspecified.

  • {:error, exception} for an error passed on, a unit the value already has, or one it cannot have beside its others.

Examples

iex> import Tempo.Builder
iex> some(~o"2026-06", :day)
{:ok, ~o"2026Y6MX*D"}

starting(step, from)

@spec starting(step(), Tempo.t()) :: result()

Where a recurrence begins: its first repeat is the one that holds this value.

Arguments

  • step is a recurrence, or the result of the step before.

  • from is a Tempo.t/0 value, or a set of them, each a domain the rule is applied in (~o"{2026Y..2030Y}").

Returns

  • {:ok, recurrence}, the recurrence beginning there.

  • {:error, exception} for an error passed on, or as Tempo.every/2 refuses the start.

Examples

iex> import Tempo.Builder
iex> every(:week) |> on(:monday) |> starting(~o"2026-06-01")
{:ok, ~o"R/2026-06-01/P1W/FL1KN"}

times(step, count)

@spec times(step(), pos_integer()) :: result()

How many occurrences a recurrence has.

Arguments

  • step is a recurrence, or the result of the step before.

  • count is a whole number above zero.

Returns

  • {:ok, recurrence}, the recurrence with that many occurrences.

  • {:error, exception} for an error passed on, a count that is not a whole number above zero, or a recurrence that already ends by a value.

Examples

iex> import Tempo.Builder
iex> every(:week) |> starting(~o"2026-06-01") |> times(10)
{:ok, ~o"R10/2026-06-01/P1W"}

uncertain(step, units \\ :all)

@spec uncertain(step(), :all | Tempo.unit() | [Tempo.unit()]) :: result()

Marks a value uncertain, as ISO 8601-2's ? does: the whole value, or the units named. A unit marked uncertain and approximate is both (%).

Arguments

  • step is a value or a set of them, or the result of the step before. An interval's marks are on its ends: mark each value before joining them with until/2.

  • units is :all, the default, or a unit or list of units the value has.

Returns

  • {:ok, expression}, the value marked.

  • {:error, exception} for an error passed on, a unit the value does not have, or an expression that is no value.

Examples

iex> import Tempo.Builder
iex> uncertain(~o"2026-06-15")
{:ok, ~o"2026-06-15?"}

iex> import Tempo.Builder
iex> uncertain(~o"2026-06-15", :month)
{:ok, ~o"2026Y6?M15D"}

iex> import Tempo.Builder
iex> until(uncertain(~o"1984") |> elem(1), approximate(~o"2004") |> elem(1))
{:ok, ~o"1984?/2004~"}

until(step, until)

@spec until(step(), Tempo.t()) :: result()

Where an expression ends, the value it runs through: a recurrence's last repeat is the one that holds it, and a value runs through it as an interval.

Arguments

  • step is a recurrence or a value, or the result of the step before.

  • until is a Tempo.t/0 value.

Returns

  • {:ok, expression}, the recurrence ending there, or the interval from the value through until.

  • {:error, exception} for an error passed on, a recurrence that already ends by a count, or an end before the start.

Examples

iex> import Tempo.Builder
iex> every(:day) |> starting(~o"2026-06-01") |> until(~o"2026-06-30")
{:ok, ~o"R/2026-06-01/P1D/2026-06-30"}

iex> import Tempo.Builder
iex> until(~o"2026-06-01", ~o"2026-06-12")
{:ok, ~o"2026-06-01/2026-06-12"}

weeks_beginning(step, day)

@spec weeks_beginning(step(), weekday()) :: result()

An expression whose weeks begin on another day than Monday: its numbered weeks are counted from that day, and a recurrence of every second week or more steps in those weeks. It is RFC 5545's WKST, written [u-fw=sun].

Arguments

  • step is an expression of the Gregorian calendar, or the result of the step before.

  • day is the weekday weeks begin on, by its name (:sunday).

Returns

  • {:ok, expression}, the expression with its weeks begun on that day, as Tempo.to_week_start/2 makes it.

  • {:error, exception} for an error passed on, a day that is no weekday's name, or an expression of another calendar.

Examples

iex> import Tempo.Builder
iex> every(week: 2) |> on([:tuesday, :thursday]) |> weeks_beginning(:sunday)
{:ok, ~o"R/../P2W/FL{3,5}KN[u-fw=sun]"}

with_metadata(step, metadata)

@spec with_metadata(step(), map()) :: result()

An expression carrying a caller's map: a recurrence's every occurrence carries it too.

Arguments

  • step is an expression, or the result of the step before.

  • metadata is a map, merged into the metadata the expression has.

Returns

  • {:ok, expression}, the expression with the metadata.

  • {:error, exception} for an error passed on, or metadata that is no map.

Examples

iex> import Tempo.Builder
iex> {:ok, standup} = every(:day) |> at(hour: 9) |> with_metadata(%{title: "Standup"})
iex> Tempo.metadata(standup)
%{title: "Standup"}