# `Tempo.Builder`
[🔗](https://github.com/elixir-tempo/tempo/blob/v2.1.0/lib/tempo/builder.ex#L1)

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:

* What repeats: `every/1`.

* What is selected in each repeat, or in a value: `on/3` for days,
  `at/2` for times of day, and `lasting/2` for days that run on.

* What a value says of itself: `uncertain/2` and `approximate/2`, a
  group within it (`grouped/3`), and a unit it leaves unspecified
  (`some/2`).

* Where a recurrence begins and ends: `starting/2`, `until/2`,
  `times/2` and `ending/2`.

* Its frame: `in_zone/2`, `in_calendar/2`, `weeks_beginning/2` and
  `in_years/3`.

* What is left out, what is kept with it, and what goes beside it:
  `except/2`, `with_metadata/2` and `all_of/1`.

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.

# `result`

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

What each step returns.

# `step`

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

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

# `weekday`

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

A day of the week by its name.

# `all_of`

```elixir
@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

* `{:ok, set}`, a set of them, as `Tempo.IntervalSet.new/2` makes one.

* `{:error, exception}` for an error passed on, or as
  `Tempo.IntervalSet.new/2` refuses the members.

### 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`

```elixir
@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`

```elixir
@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`

```elixir
@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 `t: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`

```elixir
@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
  `t:Tempo.Duration.t/0`, or a duration's units (`[week: 2]`).

### Returns

* `{:ok, recurrence}`, a `t: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`

```elixir
@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 `t:Tempo.t/0` value, or a list of them.

### Returns

* `{:ok, set}`, a set of the expression's members with the values
  excluded, as `Tempo.IntervalSet.new/2` makes one with `:except`.

* `{:error, exception}` for an error passed on, or as
  `Tempo.IntervalSet.new/2` refuses it.

### 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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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 `t: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`

```elixir
@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 `t: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`

```elixir
@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`

```elixir
@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 `t: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`

```elixir
@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`

```elixir
@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`

```elixir
@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 `t: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`

```elixir
@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`

```elixir
@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"}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
