Tempo (Tempo v2.1.0)

Copy Markdown View Source

Time as intervals, not instants.

Every Tempo expression denotes an ordered set of half-open spans on the time line: a value (2026-06), an interval (2026-06-01/2026-07-01), a recurrence (R5/2026-06-15/P1D), a set of them ({2026-06,2026-08}), or one of several ([2026,2027]). One struct holds every form, with one calendar, one zone and the caller's metadata; Tempo.Duration is the one companion, a length with no place on the time line.

See plans/architecture-redesign.md for the model: nine laws every module holds to, and the forms below.

Errors

A function that can fail returns {:ok, value} or {:error, exception}, and a constructor, a conversion, a parser and a set operation each have a ! form that raises. A predicate, an accessor and compare/3 answer a bare value (a boolean, a part, :lt), and raise their errors, so that an error is never read as an answer.

An argument that should be an expression and is not one (nil, a string, a Date) is a Tempo.ConversionError with reason :not_an_expression, returned or raised as the function's other errors are. Text given to a reader that is not a string is an ArgumentError, and a zone that is not a string a Tempo.ZoneError.

Summary

Types

The error every function returns in an {:error, reason} tuple.

What an expression is, as written.

What a unit holds in a written value: a whole number; a list of whole numbers and ranges, a negative counting from the end; a mask of digits, :x for one unspecified; :any, a unit unspecified (X*); a number with its significant digits or its margin of error; a traditional leap month. A group is a unit of its own, :group, holding the nth group, or several, and the length it counts in: {2, [month: 3]} is 2G3MU, the second three months.

A mark that a component is uncertain (?), approximate (~) or both (%), held for each unit it qualifies.

The parts of a selection (L…N): the units selected, each with what it holds; instance, event, roll, window, and a nested selection.

A written time shift: whole hours, minutes and seconds, the sign on the first that is not zero.

t()

A unit a value is written in, or a label a day carries.

A value's units, coarsest first, each with what it holds.

The zone of an expression: a name the zone database knows, or a fixed offset from UTC in minutes, with RFC 9557's critical flag.

Functions

A value moved by a number of workdays, forward for a positive count and back for a negative one, stepping over the weekend and the days off.

Whether the first expression meets the second: one ends as the other begins. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Whether the first expression begins no earlier than the second ends. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Whether an expression has a year, so a place on the time line. A value with an unspecified year (X*Y) has none.

One value placed on another: a date at a time of day, a time of day on a date, a month and a day. The value with a year keeps it and the other supplies the finer units it lacks; a unit between the two that neither names is its first. An interval is placed end by end. A selection selects within the units before it, so it follows the other's units and finer units follow it, with nothing filled beside it.

at/2, returning its answer or raising the error.

Whether an expression is at least as long as a duration.

Whether an expression is no longer than a duration.

A value written at a unit: truncated to a coarser one as trunc/2 truncates, and extended to a finer one with the first value of each unit between. A finer unit on another axis than the value's, a week of a month or a day of a quarter, is the period of it the value starts in, as trunc/2 names one.

at_resolution/2, returning its answer or raising the error.

Whether the first expression ends no later than the second begins: an eleven o'clock meeting is before a noon lunch. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Whether every end an expression has is written: an interval with an open end is not bounded, nor a recurrence with no end, nor a set that has no last member (a selection over a span with no end).

Whether a predicate holds between two expressions for every reading of them.

Whether a predicate holds between two expressions however their uncertain parts are read: :certain when it holds for every reading, :impossible when for none, :possible otherwise. A reading is a member of a one-of set, a placing of a year with a margin, or a value a mask allows; an expression with no uncertainty has one.

Orders two expressions by where they begin, ties by where they end, so that Tempo sorts as Date does: Enum.sort(values, Tempo). A plural expression is placed by its first span.

The complement of an expression within a window: every instant of the window not covered by it.

complement/2, returning its answer or raising the error.

Whether the first expression covers every instant of the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

How many values of a unit an expression holds: the values walk/3 gives, counted: each unit the expression touches, the first and the last in part where it begins or ends within them. A plural expression's members are counted in turn.

How many workdays an expression's days hold.

The day an expression names, as a number.

The day of the week an expression names, 1 to 7, counted by default from the day its weeks begin on: Monday in the Gregorian calendar, Sunday in the Hebrew and in an expression written [u-fw=sun]. A day of the week is written and selected the same way (K), so the number this answers is the one the expression would be written with.

The day of the year an expression names, counted from the year's first day.

The number of days in the month an expression names, in its calendar.

The difference of two expressions: every instant in the first and not the second, as spans trimmed to what is left, each carrying the first operand's metadata. A span ends where the member it comes from ends.

difference/3, returning its answer or raising the error.

Whether the first expression shares no instant with the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

The duration an expression covers, in the unit it is written to.

The duration from the start of one value to the start of another, in their finer unit: days between two dates, elapsed hours between two times. Unlike an interval written between them, which runs through its last date, this measures start to start.

duration/2, returning its answer or raising the error.

Whether an expression covers no time: a set with no members.

Whether the first expression covers the same instants as the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Builds a recurrence from its cadence and what it is given: where it starts, how it ends, and what it selects in each repeat. It is what R/start/cadence/F… reads to, built from parts where a program would otherwise write that text to have it read.

every/2, returning its answer or raising the error.

Whether an expression is exactly as long as a duration.

A description of an expression in prose: what it is, the time it covers, the frame it is in, and what walking it gives.

A value with its next finer unit written as the values that unit takes: a year as its months, a month as its days, a day in a zone as the hours its clock shows, a group as the values it names.

extend/1, returning its answer or raising the error.

Whether an expression is floating: a reading of a clock with no zone and no time shift, which names a civil time but not whose. The complement of zoned?/1.

A Tempo value from a Date, as from_elixir/2 reads one.

A Tempo value from a DateTime, as from_elixir/2 reads one.

A Tempo value from one of Elixir's date and time structs.

from_elixir/2, returning the value or raising.

Reads an ISO 8601, ISO 8601-2, EDTF or RFC 9557 text into an expression.

Reads a text as from_iso8601/2 does, returning the expression or raising.

A Tempo value from a NaiveDateTime, as from_elixir/2 reads one.

A Tempo value from a Time, as from_elixir/2 reads one.

The hour an expression names, as a number.

An expression with its seasons given their months in a territory. A season of 21 to 24 (spring, summer, autumn, winter) is three whole months, and which three is the hemisphere's to say: spring is March to May north of the equator and September to November south of it.

in_territory/2, returning the expression or raising.

A floating expression placed in a time zone, its readings unchanged: the runtime form of writing [zone] after it.

in_zone/2, returning its answer or raising the error.

The intersection of two expressions: every instant in both, as spans trimmed to the overlap, each carrying the first operand's metadata. A span ends where the member it comes from ends, so two members that meet are two spans; Tempo.IntervalSet.coalesce/1 joins them.

intersection/3, returning its answer or raising the error.

Whether the year an expression names is a leap year of its calendar.

Whether an expression is longer than a duration.

try_map/2, returning the set or raising.

The members of an expression (law 6): the values a value with a set, a mask or a group in a unit allows; the members of a set; the occurrences of a recurrence; a singular expression itself.

members/2, returning its answer or raising the error.

The members of either expression no instant of which is in the other.

The members of the first expression no instant of which is in the second, each kept whole with its metadata.

members_outside/3, returning its answer or raising the error.

The members of the first expression some instant of which is in the second, each kept whole with its metadata.

members_overlapping/3, returning its answer or raising the error.

The metadata an expression carries: a map, empty by default.

The minute an expression names, as a number.

The month an expression names, as a number.

The nearest workday to a value: the value itself when it is one, else the closer of the next and the previous, the next on a tie.

Builds a value from its units.

Builds a value as new/1 does, returning the value or raising.

The first workday after a value.

The current second in a zone, from the configured Tempo.Clock.

now/1, returning its answer or raising the error.

at/2 with its arguments the other way round, so that the code reads as English does: 10:30 on 4 January.

on/2, returning its answer or raising the error.

Builds a set one of whose members is meant: "in 1984, 1986 or 1988". It has readings and no spans, where the set Tempo.IntervalSet.new/2 builds is all of its members.

one_of/1, returning its answer or raising the error.

Whether the first expression shares an instant with the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Reads a text written in ISO 8601 or in a locale's own words.

parse/2, returning the expression or raising.

Reads a text that must be an ISO 8601 duration and nothing else: an iCalendar DURATION, a configured timeout. A date read where a duration was meant is an error here, where from_iso8601/2 would succeed with a value of the wrong kind.

parse_duration/1, returning its answer or raising the error.

Whether a predicate holds between two expressions for some reading of them.

The last workday before a value.

An expression with metadata merged into what it carries.

The qualifications of a value: which of its units are uncertain (?), approximate (~) or both (%), as a map from unit to qualification.

The qualification of one unit of a value: whether it is uncertain (?), approximate (~) or both (%).

The quarter of the year an expression lies in, 1 to 4.

Allen's relation of one singular expression to another: one of the thirteen Tempo.Allen.relation/0 atoms.

The unit an expression is written to: the finest unit of a value, of an interval's ends or a set's members. A value ending in a group is written to the group's unit.

A value rolled to a workday by a business day convention: itself when it is a workday, else the workday the convention names.

A value rounded to the nearest period of a unit: the period it starts in, or the next when its start is past the midpoint, measured in elapsed time. Half way rounds up, as Kernel.round/1 does.

round/2, returning its answer or raising the error.

The second an expression names, as a number.

The values a selector picks in each span of an expression: the Fridays of June, Christmas Day in each year of a span, half past ten in a working day, a territory's workdays in a term.

select/3, returning the set or raising.

Builds a selection from its parts: what a repeat rule picks in each of its periods, and what select/3 picks in each period of a base. It is the value ISO 8601-2 writes between L and N.

selection/2, returning its answer or raising the error.

An expression moved by a duration.

shift/2, returning its answer or raising the error.

A zoned expression moved to another time zone: the same instants, read on that zone's clock. A value whose span is one reading there stays a value; a day in Paris is a span from six in the evening to six in New York.

shift_zone/2, returning its answer or raising the error.

Whether an expression is shorter than a duration.

A value split into its date and its time of day, each keeping the value's calendar, zone, qualifications and metadata, so that at/2 of the two is the value.

The symmetric difference of two expressions: every instant in exactly one of them, as spans, each ending where the member it comes from ends.

symmetric_difference/3, returning its answer or raising the error.

The time zone database Tempo asks for a zone's rules.

An expression in another calendar: the same spans, their dates written in that calendar. A date keeps its time of day; a value written to a month or a year becomes the span of dates it covers, a month of one calendar being no month of another; a set converts member by member.

to_calendar/2, returning its answer or raising the error.

The Date of a value written to the day or finer, on any axis.

to_date/1, returning its answer or raising the error.

The DateTime of a value with a zone or a time shift.

to_datetime/1, returning its answer or raising the error.

The Elixir struct a value fills: a Date for a day, a Time for a time of day, a NaiveDateTime for a floating date and time, a DateTime for one with a zone or a shift, and an Elixir Duration for a duration. An interval of two dates is the Date.Range of them, which holds its last day as the interval does; one with a duration for an end is the range of the dates it runs between.

to_elixir/1, returning its answer or raising the error.

The interval an expression denotes: a singular expression's one span, or a set of the spans of a plural one.

to_interval/2, returning the interval or set or raising.

Writes an expression or a duration as ISO 8601 text, in the explicit form of ISO 8601-2 (2026Y6M15D) that every expression has.

Writes an expression as to_iso8601/1 does, returning the text or raising.

The NaiveDateTime of a value written to the day or finer; units it does not write are zero, and a zone is dropped.

to_naive_datetime/1, returning its answer or raising the error.

A locale's words for where an expression starts relative to another: yesterday, in 3 hours, next year.

to_relative_string/2, returning its answer or raising the error.

A locale's words for an expression, through Localize and CLDR.

to_string/2, returning the words or raising.

The Time of a value with a time of day; units it does not write are zero.

to_time/1, returning its answer or raising the error.

An expression with its weeks begun on another day: the same days, in the Gregorian calendar whose weeks begin on the day given. It is written with the suffix [u-fw=sun], BCP 47's first day of the week, and is what an RRULE's WKST reads to.

to_week_start/2, returning its answer or raising the error.

Today's date in a zone: a floating day.

today/1, returning its answer or raising the error.

An expression written to a coarser unit: its units through that one, so a date and time truncated to the day is the date.

trunc/2, returning its answer or raising the error.

A function applied to each member of an enumerable of expressions, the results gathered into a set: Enum.map/2 that answers a t/0.

The union of two expressions: every member of either, kept whole with its own metadata, in order of start. A member both hold, the same in its span, its form and its metadata, is one member; two members that cover one time otherwise stay two, and Tempo.IntervalSet.coalesce/1 merges them.

union/3, returning its answer or raising the error.

The current second in UTC.

Today's date in UTC.

An expression walked at a unit: the values of that unit its extent holds, lazily and in order, half-open. A calendar unit steps on the day scale through the calendar's periods; a clock unit on the tick scale, each tick shown as a wall reading of the zone, so a day across a change of the clock has 23 or 25 hours. A plural expression is each member walked in turn.

walk/3, returning its answer or raising the error.

The week an expression names, as a number.

The day an expression's weeks begin on, 1 (Monday) to 7 (Sunday): Monday unless it was written or built with another. Nil for an expression of a calendar that is not the Gregorian, whose weeks are its own.

Whether a value, a day or finer, falls on the weekend of a territory.

The weekend of a territory as a selector: the days of the week its weekend falls on.

weekends/1, returning its answer or raising the error.

Whether the first expression covers no instant outside the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Whether a value, a day or finer, falls on a workday of a territory: a weekday that is not a day off.

The workdays of a territory, less any days off: a selector for select/3 and the calendar the workday functions step through.

workdays/2, returning the workdays or raising: for a territory written in the code, as in a module attribute.

The year an expression names, as a number.

Whether an expression carries a zone or a time shift, so a place on the universal time line. The complement of floating?/1.

Types

error()

@type error() :: Exception.t()

The error every function returns in an {:error, reason} tuple.

form()

@type form() ::
  {:value, units()}
  | {:span, t() | Tempo.Duration.t() | :open, t() | Tempo.Duration.t() | :open}
  | {:every, t(), pos_integer() | :infinity | t(), selection() | nil}
  | {:all, [t()] | Enumerable.t()}
  | {:one, [t()]}
  | {:when, t(), [term()]}

What an expression is, as written.

  • {:value, units} — a written value, its units coarsest first, each with what it holds (held/0).

  • {:span, from, to} — an interval: two ends, each a value, a duration (one of the two) or :open.

  • {:every, first, bound, rule} — a recurrence: the span of its first occurrence, how many (a count, :infinity, or a value it runs until) and the repeat rule applied in each period, if any.

  • {:all, members} — all of several expressions: a written set, an interval set, a recurrence set, the result of a set operation.

  • {:one, members} — one of several expressions, which has readings and no spans.

  • {:when, member, conditions} — a member with the conditions Tempo.RecurrenceSet.keep_when/2 and move_when/2 express.

held()

@type held() ::
  integer()
  | [integer() | Range.t()]
  | {:mask, [0..9 | :x | :negative]}
  | :any
  | {pos_integer() | [pos_integer() | Range.t()], [{unit(), pos_integer()}]}
  | {integer(), [{:significant, pos_integer()}]}
  | {integer(), [{:margin, pos_integer()}]}
  | {integer(), :leap}

What a unit holds in a written value: a whole number; a list of whole numbers and ranges, a negative counting from the end; a mask of digits, :x for one unspecified; :any, a unit unspecified (X*); a number with its significant digits or its margin of error; a traditional leap month. A group is a unit of its own, :group, holding the nth group, or several, and the length it counts in: {2, [month: 3]} is 2G3MU, the second three months.

qualifications()

@type qualifications() ::
  %{optional(unit()) => :uncertain | :approximate | :both} | nil

A mark that a component is uncertain (?), approximate (~) or both (%), held for each unit it qualifies.

selection()

@type selection() :: [
  {unit() | :instance | :event | :roll | :window | :selection, term()}
]

The parts of a selection (L…N): the units selected, each with what it holds; instance, event, roll, window, and a nested selection.

shift()

@type shift() :: [hour: integer(), minute: integer(), second: integer()]

A written time shift: whole hours, minutes and seconds, the sign on the first that is not zero.

t()

@type t() :: %Tempo{
  calendar: module(),
  form: form(),
  metadata: map(),
  qualifications: qualifications(),
  shift: shift() | nil,
  zone: zone() | nil
}

unit()

@type unit() ::
  :century
  | :decade
  | :year
  | :month
  | :week
  | :day
  | :hour
  | :minute
  | :second
  | :microsecond
  | :day_of_week
  | :day_of_year
  | :quarter
  | :season
  | :traditional_month

A unit a value is written in, or a label a day carries.

units()

@type units() :: [{unit(), held()}]

A value's units, coarsest first, each with what it holds.

zone()

@type zone() :: %Tempo.Zone{
  critical: boolean(),
  name: String.t() | nil,
  offset: integer() | nil
}

The zone of an expression: a name the zone database knows, or a fixed offset from UTC in minutes, with RFC 9557's critical flag.

Functions

add_workdays(value, count, territory \\ nil)

@spec add_workdays(t(), integer(), term()) :: {:ok, t()} | {:error, error()}

A value moved by a number of workdays, forward for a positive count and back for a negative one, stepping over the weekend and the days off.

Arguments

  • value is a t/0 within one day.

  • count is the number of workdays to move, an integer.

  • territory is a territory code, a Tempo.Workdays.t/0 or nil, as for workdays/2.

Returns

  • {:ok, workday}, a t/0.

  • {:error, %Tempo.ConversionError{reason: :open}} when the days off never end in the direction moved, and {:error, exception} for a value that names several days or a territory Localize does not know.

Examples

iex> Tempo.add_workdays(~o"2026-06-12", 3, :US)
{:ok, ~o"2026-06-17"}

iex> Tempo.add_workdays(~o"2026-06-15", -1, :US)
{:ok, ~o"2026-06-12"}

adjacent?(a, b, options \\ [])

@spec adjacent?(t(), t(), keyword()) :: boolean()

Whether the first expression meets the second: one ends as the other begins. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.adjacent?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.adjacent?(~o"2026-06", ~o"2026")
false

after?(a, b, options \\ [])

@spec after?(t(), t(), keyword()) :: boolean()

Whether the first expression begins no earlier than the second ends. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.after?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.after?(~o"2026-06", ~o"2026")
false

anchored?(expression)

@spec anchored?(t()) :: boolean()

Whether an expression has a year, so a place on the time line. A value with an unspecified year (X*Y) has none.

Arguments

  • expression is a t/0.

Returns

  • true or false.

Examples

iex> Tempo.anchored?(~o"2022")
true

iex> Tempo.anchored?(~o"2M")
false

iex> Tempo.anchored?(~o"X*Y2M")
false

at(span, other)

@spec at(t(), t()) :: {:ok, t()} | {:error, error()}

One value placed on another: a date at a time of day, a time of day on a date, a month and a day. The value with a year keeps it and the other supplies the finer units it lacks; a unit between the two that neither names is its first. An interval is placed end by end. A selection selects within the units before it, so it follows the other's units and finer units follow it, with nothing filled beside it.

Arguments

  • value and other are each a t/0, at most one of them with a year, or one of them an interval whose ends are placed on the other.

Returns

  • {:ok, placed}, a t/0.

  • {:error, exception} when both have a year, both hold a selection, either is not a t/0, or the result is no date or time the calendar and zone have.

Examples

iex> Tempo.at(~o"2026-06-15", ~o"T17")
{:ok, ~o"2026-06-15T17"}

iex> Tempo.at(~o"T17", ~o"2026-06-15")
{:ok, ~o"2026-06-15T17"}

iex> Tempo.at(~o"3M", ~o"2D")
{:ok, ~o"3M2D"}

iex> Tempo.at(~o"2026-06-15", ~o"T09/T17")
{:ok, ~o"2026-06-15T09/2026-06-15T17"}

iex> Tempo.at(~o"2026-06", ~o"T17")
{:ok, ~o"2026-06-01T17"}

iex> Tempo.on(~o"L(easter)eN", ~o"2027")
{:ok, ~o"2027YL(easter)eN"}

at!(arg1, arg2)

@spec at!(t(), t()) :: t()

at/2, returning its answer or raising the error.

Arguments

  • The arguments are those of at/2.

Returns

  • What at/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.at!(~o"2026-01-04", ~o"T10:30")
~o"2026-01-04T10:30"

at_least?(expression, duration)

@spec at_least?(t(), Tempo.Duration.t()) :: boolean() | {:error, error()}

Whether an expression is at least as long as a duration.

The expression is measured as duration/1 measures it, as the time it covers; the duration is laid from the expression's start, so a month is as long as the month it starts in. An open end is longer than any duration.

Arguments

Returns

  • true or false.

  • {:error, exception} as for duration/1.

Examples

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.at_least?(meeting, ~o"PT1H")
true

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.at_least?(meeting, ~o"PT90M")
true

at_most?(expression, duration)

@spec at_most?(t(), Tempo.Duration.t()) :: boolean() | {:error, error()}

Whether an expression is no longer than a duration.

The expression is measured as duration/1 measures it, as the time it covers; the duration is laid from the expression's start, so a month is as long as the month it starts in. An open end is longer than any duration.

Arguments

Returns

  • true or false.

  • {:error, exception} as for duration/1.

Examples

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.at_most?(meeting, ~o"PT1H")
false

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.at_most?(meeting, ~o"PT90M")
true

at_resolution(expression, unit)

@spec at_resolution(t(), unit()) :: {:ok, t()} | {:error, error()}

A value written at a unit: truncated to a coarser one as trunc/2 truncates, and extended to a finer one with the first value of each unit between. A finer unit on another axis than the value's, a week of a month or a day of a quarter, is the period of it the value starts in, as trunc/2 names one.

Arguments

  • expression is a t/0.

  • unit is a unit.

Returns

  • {:ok, expression}, or {:error, exception} as for trunc/2. A year begins in no season and in no traditional month, so extending to either is {:not_written_in, unit}.

Examples

iex> Tempo.at_resolution(~o"2020Y", :day)
{:ok, ~o"2020Y1M1D"}

iex> Tempo.at_resolution(~o"2026-06", :week)
{:ok, ~o"2026-W23"}

iex> Tempo.at_resolution(~o"2020Y6M15DT10H", :day)
{:ok, ~o"2020Y6M15D"}

iex> Tempo.at_resolution(~o"2026-W25", :hour)
{:ok, ~o"2026Y25W1KT0H"}

at_resolution!(arg1, arg2)

@spec at_resolution!(t(), unit()) :: t()

at_resolution/2, returning its answer or raising the error.

Arguments

Returns

  • What at_resolution/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.at_resolution!(~o"2020", :day)
~o"2020-01-01"

before?(a, b, options \\ [])

@spec before?(t(), t(), keyword()) :: boolean()

Whether the first expression ends no later than the second begins: an eleven o'clock meeting is before a noon lunch. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.before?(~o"2026-01", ~o"2026-03")
true

iex> Tempo.before?(~o"2026-06", ~o"2026")
false

bounded?(expression)

@spec bounded?(t() | Tempo.Duration.t()) :: boolean()

Whether every end an expression has is written: an interval with an open end is not bounded, nor a recurrence with no end, nor a set that has no last member (a selection over a span with no end).

Arguments

Returns

  • true or false. A duration has no end to leave open and is bounded.

Examples

iex> Tempo.bounded?(~o"2026-06-15")
true

iex> Tempo.bounded?(~o"2026-01-15/..")
false

iex> Tempo.bounded?(~o"R/2026-01-15/P1D")
false

iex> {:ok, mondays} = Tempo.select(~o"2026-06-01/..", ~o"1K")
iex> Tempo.bounded?(mondays)
false

certainly?(predicate, a, b)

@spec certainly?(Tempo.Allen.predicate() | Tempo.Allen.relation(), t(), t()) ::
  boolean()

Whether a predicate holds between two expressions for every reading of them.

Arguments

  • predicate is a predicate or relation, as for certainty/3.

  • a and b are each a t/0.

Returns

Examples

iex> Tempo.certainly?(:before, ~o"2018±2Y", ~o"2021")
true

iex> Tempo.certainly?(:before, ~o"2018±2Y", ~o"2020")
false

certainty(predicate, a, b)

@spec certainty(Tempo.Allen.predicate() | Tempo.Allen.relation(), t(), t()) ::
  :certain | :possible | :impossible | {:error, error()}

Whether a predicate holds between two expressions however their uncertain parts are read: :certain when it holds for every reading, :impossible when for none, :possible otherwise. A reading is a member of a one-of set, a placing of a year with a margin, or a value a mask allows; an expression with no uncertainty has one.

Arguments

  • predicate is an everyday predicate (:before, :after, :adjacent, :within, :contains, :overlaps, :disjoint, :equal) or one of Allen's relations (Tempo.Allen.relation/0).

  • a and b are each a t/0.

Returns

Examples

iex> Tempo.certainty(:before, ~o"1984?", ~o"2004~")
:certain

iex> Tempo.certainty(:overlaps, ~o"2018±2Y", ~o"2019")
:possible

iex> Tempo.certainty(:within, ~o"[2026,2027]", ~o"2026")
:possible

iex> Tempo.certainty(:overlaps, ~o"2018±2Y", ~o"2030")
:impossible

compare(a, b, options \\ [])

@spec compare(t(), t(), keyword()) :: :lt | :eq | :gt

Orders two expressions by where they begin, ties by where they end, so that Tempo sorts as Date does: Enum.sort(values, Tempo). A plural expression is placed by its first span.

Arguments

  • a and b are each a t/0 on one frame.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

  • :by is what is compared: :start_and_end, the default, where each begins and, where they begin together, where each ends; or :start, where each begins alone, so that a reading of nine o'clock and the day it begins are :eq.

Returns

Examples

iex> Tempo.compare(~o"2026-06-15", ~o"2026-06-16")
:lt

iex> Tempo.compare(~o"2026-06-15T09:00Z", ~o"2026-06-15T10:00+01:00")
:eq

iex> Tempo.compare(~o"2026-06-15T00", ~o"2026-06-15")
:lt

iex> Tempo.compare(~o"2026-06-15T00", ~o"2026-06-15", by: :start)
:eq

iex> Enum.sort([~o"2026-06-16", ~o"2026-06-15"], Tempo)
[~o"2026-06-15", ~o"2026-06-16"]

complement(a, options)

@spec complement(t(), keyword()) :: {:ok, t()} | {:error, error()}

The complement of an expression within a window: every instant of the window not covered by it.

Arguments

  • a is a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window the complement is taken in. It is required.

  • :metadata says which member's metadata a result carries: :first (the default), :second, :merge, or a function of the two.

Returns

Examples

iex> Tempo.complement(~o"2026-06", within: ~o"2026")
{:ok, ~o"{2026/2026-05,2026-07/2026}"}

complement!(arg1, arg2)

@spec complement!(t(), keyword()) :: t()

complement/2, returning its answer or raising the error.

Arguments

Returns

  • What complement/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.complement!(~o"2026-06", within: ~o"2026")
~o"{2026/2026-05,2026-07/2026}"

contains?(a, b, options \\ [])

@spec contains?(t(), t(), keyword()) :: boolean()

Whether the first expression covers every instant of the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.contains?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.contains?(~o"2026-06", ~o"2026")
false

count(expression, unit, options \\ [])

@spec count(t(), unit() | {:microsecond, 1..6}, keyword()) ::
  {:ok, non_neg_integer()} | {:error, error()}

How many values of a unit an expression holds: the values walk/3 gives, counted: each unit the expression touches, the first and the last in part where it begins or ends within them. A plural expression's members are counted in turn.

Arguments

  • expression is a t/0.

  • unit is a unit/0, as for walk/3.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

Returns

  • {:ok, count}, a non-negative integer, or

  • {:error, %Tempo.ConversionError{reason: :unbounded}} for an expression with no end and no :within, {:error, %Tempo.ConversionError{reason: :one_of}} for one of several values, which has no one count, or another error walk/3 returns.

Examples

iex> Tempo.count(~o"2026-06", :day)
{:ok, 30}

iex> {:ok, weekends} = Tempo.weekends(:BR)
iex> {:ok, business_days} = Tempo.difference(~o"2026-01-02/2026-03-31", weekends)
iex> Tempo.count(business_days, :day)
{:ok, 63}

iex> Tempo.count(~o"2026-06-01T12/2026-06-03T12", :day)
{:ok, 3}

iex> Tempo.count(~o"2026-06-15T10:30/2026-06-15T12:30", :hour)
{:ok, 3}

count_workdays(expression, territory \\ nil)

@spec count_workdays(t(), term()) :: {:ok, non_neg_integer()} | {:error, error()}

How many workdays an expression's days hold.

Arguments

Returns

  • {:ok, count}, a non-negative integer.

  • {:error, %Tempo.ConversionError{reason: :unbounded}} for an expression with no end, and {:error, exception} for a territory Localize does not know.

Examples

iex> Tempo.count_workdays(~o"2026-06", :US)
{:ok, 22}

day(expression)

@spec day(t()) :: integer() | nil

The day an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no day or several.

Examples

iex> Tempo.day(~o"2026-06-15")
15

iex> Tempo.day(~o"2026-06")
nil

day_of_week(expression, starting_on \\ :default)

@spec day_of_week(t(), atom()) :: 1..7 | nil

The day of the week an expression names, 1 to 7, counted by default from the day its weeks begin on: Monday in the Gregorian calendar, Sunday in the Hebrew and in an expression written [u-fw=sun]. A day of the week is written and selected the same way (K), so the number this answers is the one the expression would be written with.

Arguments

  • expression is a t/0: a date, a week date, or an interval or set within one day.

  • starting_on is the day numbered 1: :default (the day the expression's weeks begin on), or a day named, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday or :sunday.

Returns

  • The day of the week, or nil when the expression names no day or several.

Examples

iex> Tempo.day_of_week(~o"2026-06-15")
1

iex> Tempo.day_of_week(~o"2026-06-15", :sunday)
2

iex> Tempo.day_of_week(~o"2026-06-15[u-fw=sun]")
2

iex> Tempo.day_of_week(~o"2026-06-15[u-fw=sun]", :monday)
1

iex> Tempo.day_of_week(~o"2026-06")
nil

day_of_year(expression)

@spec day_of_year(t()) :: pos_integer() | nil

The day of the year an expression names, counted from the year's first day.

Arguments

  • expression is a t/0.

Returns

  • The day of the year, from 1; nil when the expression names no single day.

Examples

iex> Tempo.day_of_year(~o"2026-06-15")
166

iex> Tempo.day_of_year(~o"2026-06")
nil

days_in_month(expression)

@spec days_in_month(t()) :: {:ok, pos_integer()} | {:error, error()}

The number of days in the month an expression names, in its calendar.

Arguments

  • expression is a t/0.

Returns

  • {:ok, days}.

  • {:error, %Tempo.ConversionError{reason: :needs_a_month}} when the expression names no year and month, or several.

Examples

iex> Tempo.days_in_month(~o"2024-02-15")
{:ok, 29}

iex> Tempo.days_in_month(~o"2025-02")
{:ok, 28}

difference(a, b, options \\ [])

@spec difference(t(), t() | [t()], keyword()) :: {:ok, t()} | {:error, error()}

The difference of two expressions: every instant in the first and not the second, as spans trimmed to what is left, each carrying the first operand's metadata. A span ends where the member it comes from ends.

Arguments

  • a is a t/0 that has spans.

  • b is a t/0 that has spans, or a list of them, taken away one after another: an empty list leaves a as it is.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in. With none, an operand with no end or no year is treated as union/3 says.

  • :metadata says which member's metadata a result carries: :first (the default), :second, :merge, or a function of the two.

Returns

  • {:ok, set}, a t/0 of the :all form.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.difference(~o"2026", ~o"2026-06")
{:ok, ~o"{2026/2026-05,2026-07/2026}"}

iex> {:ok, free_weeks} = Tempo.difference(~o"R/2026-06-01/P1W", ~o"2026-06-10/2026-06-20")
iex> Tempo.members(free_weeks, within: ~o"2026-06-01/2026-06-14")
{:ok, [~o"2026-06-01/2026-06-07", ~o"2026-06-08/2026-06-09"]}

iex> Tempo.difference(~o"2026-06-15T09/2026-06-15T17", ~o"2026-06-15T12/2026-06-15T13")
{:ok, ~o"{2026-06-15T09/2026-06-15T12,2026-06-15T13/2026-06-15T17}"}

iex> fortnight = ~o"2026-06-01/2026-06-14"
iex> Tempo.difference(fortnight, ~o"{6,7}K")
{:ok, ~o"{2026-06-01/2026-06-05,2026-06-08/2026-06-12}"}

difference!(arg1, arg2, arg3 \\ [])

@spec difference!(t(), t() | [t()], keyword()) :: t()

difference/3, returning its answer or raising the error.

Arguments

Returns

  • What difference/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.difference!(~o"2026", ~o"2026-06") |> Tempo.IntervalSet.count()
2

disjoint?(a, b, options \\ [])

@spec disjoint?(t(), t(), keyword()) :: boolean()

Whether the first expression shares no instant with the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.disjoint?(~o"2026-01", ~o"2026-03")
true

iex> Tempo.disjoint?(~o"2026-06", ~o"2026")
false

duration(expression)

@spec duration(t() | Tempo.Duration.t()) ::
  Tempo.Duration.t() | :infinity | {:error, error()}

The duration an expression covers, in the unit it is written to.

A value is one unit of its resolution long: a month is P1M, whichever month. An interval is measured between its ends in their finer unit, days between two dates and elapsed hours between two times, so a day across a change of the clocks is 23 or 25 hours. A set, a recurrence or a value that names several is the time it covers once, summed.

Arguments

  • expression is a t/0.

Returns

  • A Tempo.Duration.t/0, or :infinity for an open end or a recurrence with none.

  • {:error, %Tempo.ConversionError{}} with reason :needs_a_year for an interval or set of dates with no year, whose length is its year's, and :bare_duration for a duration.

Examples

iex> Tempo.duration(~o"2026-06-15T09:00/2026-06-15T10:30")
~o"PT90M"

iex> Tempo.duration(~o"2026-06")
~o"P1M"

iex> Tempo.duration(~o"2026-06-15/2026-06-20")
~o"P6D"

iex> Tempo.duration(~o"T22/T02")
~o"PT4H"

iex> bookings = Tempo.IntervalSet.new!([~o"2026-06-15T09/2026-06-15T11", ~o"2026-06-15T10/2026-06-15T12"])
iex> Tempo.duration(bookings)
~o"PT3H"

duration(expression, options)

@spec duration(t(), t()) :: {:ok, Tempo.Duration.t()} | {:error, error()}
@spec duration(t(), keyword()) :: Tempo.Duration.t() | :infinity | {:error, error()}

The duration from the start of one value to the start of another, in their finer unit: days between two dates, elapsed hours between two times. Unlike an interval written between them, which runs through its last date, this measures start to start.

Given an expression and options, it is duration/1 of the expression with the options applied.

Arguments

  • from and to are each a t/0 with one span, on one frame, to after from.

  • Or expression, a t/0, and options, a keyword list.

Options

  • :leap_seconds is true to count the leap seconds the expression spans (Tempo.LeapSeconds.spanned/1) into its length, a second for each. The default is false: a length on the clock, in which every minute is sixty seconds.

Returns

  • {:ok, duration} for two values.

  • A Tempo.Duration.t/0, :infinity or {:error, exception} for an expression and options, as duration/1 returns them.

  • {:error, %Tempo.IntervalEndpointsError{}} when to is not after from, and {:error, %Tempo.FrameError{}} when the two share no frame.

Examples

iex> Tempo.duration(~o"2026-09-28", ~o"2026-11-03")
{:ok, ~o"P36D"}

iex> Tempo.duration(~o"2026-09-28T10", ~o"2026-09-29")
{:ok, ~o"PT14H"}

iex> new_year = ~o"2016-12-31T23:59:00Z/2017-01-01T00:01:00Z"
iex> Tempo.duration(new_year)
~o"PT120S"
iex> Tempo.duration(new_year, leap_seconds: true)
~o"PT121S"

duration!(arg1, arg2)

@spec duration!(t(), t()) :: Tempo.Duration.t()

duration/2, returning its answer or raising the error.

Arguments

Returns

  • What duration/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.duration!(~o"2026-09-28", ~o"2026-11-03")
~o"P36D"

empty?(expression)

@spec empty?(t()) :: boolean()

Whether an expression covers no time: a set with no members.

Arguments

  • expression is a t/0.

Returns

  • true or false.

Examples

iex> Tempo.empty?(~o"{}")
true

iex> Tempo.empty?(~o"2026-06-15")
false

equal?(a, b, options \\ [])

@spec equal?(t(), t(), keyword()) :: boolean()

Whether the first expression covers the same instants as the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.equal?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.equal?(~o"2026-06", ~o"2026")
false

every(cadence, options \\ [])

@spec every(Tempo.Duration.t() | keyword(), keyword()) ::
  {:ok, t()} | {:error, error()}

Builds a recurrence from its cadence and what it is given: where it starts, how it ends, and what it selects in each repeat. It is what R/start/cadence/F… reads to, built from parts where a program would otherwise write that text to have it read.

Arguments

  • cadence is the length of each repeat, a Tempo.Duration.t/0 or its units: ~o"P1M", or [month: 1].

  • options is a keyword list of options.

Options

  • :from is where the recurrence starts, a t/0 value. A set here is a domain: the rule is applied in each of its members (~o"{2026Y..2030Y}"). With none the recurrence has an open start and is placed by a :within window.

  • :to is where a recurrence counted back ends, a t/0 value: its last repeat ends with it (R5/P1M/2026-06-01). A recurrence starts at :from or ends at :to, not both.

  • :count is how many occurrences it has.

  • :until is a t/0 value it runs to. A recurrence ends by one of :count and :until, or has no end.

  • :select is what it selects in each repeat: the parts of a selection, or what selection/1 returns. With none each repeat is an occurrence. A day of the week given by its name is numbered in the recurrence's calendar: day_of_week: :friday is 6K in the Hebrew calendar, whose weeks begin on Sunday.

  • :calendar is the calendar of a recurrence with no :from (R/../P1Y/FL7M1DN[u-ca=hebrew]). One with a :from is in its start's.

  • :years keeps the years of its domain that are :even, :odd, :leap or :common: of every year where there is no :from (R/..e), or of the set given as :from (R/{2017Y..}l).

  • :metadata is a map the recurrence and every occurrence carry.

Returns

  • {:ok, recurrence}, a t/0 of the :every form, in the calendar and the zone of its start.

  • {:error, exception} as for selection/1, and an ArgumentError for a cadence, a start or an end of another kind.

Examples

iex> Tempo.every(~o"P1M", from: ~o"2026-01-01", select: [day_of_week: 1, instance: 2])
{:ok, ~o"R/2026-01-01/P1M/FL1K2IN"}

iex> Tempo.every(~o"P1Y", select: [month: 7, day: 1, day_of_week: :friday], calendar: Calendrical.Hebrew)
{:ok, ~o"R/../P1Y/FL7M1D6KN[u-ca=hebrew]"}

iex> {:ok, paydays} = Tempo.every([month: 1], from: ~o"2026-01-01", count: 3, select: [day: -1])
iex> Enum.map(paydays, &Tempo.Interval.from/1)
[~o"2026-01-31", ~o"2026-02-28", ~o"2026-03-31"]

iex> {:ok, good_friday} =
...>   Tempo.every(~o"P1Y",
...>     select: [selection: [event: "easter"], window: ~o"-P7D", day_of_week: 5, instance: -1]
...>   )
iex> good_friday |> Tempo.to_interval!(within: ~o"2026") |> Tempo.IntervalSet.map(&Tempo.Interval.from/1)
[~o"2026-04-03"]

iex> Tempo.every(~o"P1Y", from: ~o"{2026Y..2028Y}", select: [month: 7, day: 4])
{:ok, ~o"R/{2026Y..2028Y}/P1Y/FL7M4DN"}

iex> {:error, %ArgumentError{}} = Tempo.every(~o"P1D", count: 3, until: ~o"2026")

every!(arg1, arg2)

@spec every!(Tempo.Duration.t() | keyword(), keyword()) :: t()

every/2, returning its answer or raising the error.

Arguments

  • The arguments are those of every/2.

Returns

  • What every/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.every!(~o"P1W", from: ~o"2026-06-01", count: 3)
~o"R3/2026-06-01/P1W"

exactly?(expression, duration)

@spec exactly?(t(), Tempo.Duration.t()) :: boolean() | {:error, error()}

Whether an expression is exactly as long as a duration.

The expression is measured as duration/1 measures it, as the time it covers; the duration is laid from the expression's start, so a month is as long as the month it starts in. An open end is longer than any duration.

Arguments

Returns

  • true or false.

  • {:error, exception} as for duration/1.

Examples

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.exactly?(meeting, ~o"PT1H")
false

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.exactly?(meeting, ~o"PT90M")
true

explain(expression)

@spec explain(term()) :: String.t()

A description of an expression in prose: what it is, the time it covers, the frame it is in, and what walking it gives.

Arguments

Returns

Examples

iex> Tempo.explain(~o"2026-06-15") |> String.split("\n") |> hd()
"A value written to the day: Jun 15, 2026."

iex> Tempo.explain(~o"1984/1986") |> String.split("\n") |> Enum.take(2)
["An interval: 1984 – 1986.", "It covers 1984Y through 1986Y."]

extend(expression, unit \\ nil)

@spec extend(t(), unit() | nil) :: {:ok, t()} | {:error, error()}

A value with its next finer unit written as the values that unit takes: a year as its months, a month as its days, a day in a zone as the hours its clock shows, a group as the values it names.

Under a value that names several, the finer unit is written from its first value to its last ({1..-1}). With a unit given, the value is extended level by level down to it.

Arguments

  • expression is a t/0.

  • unit is the unit to extend to, or nil for one level.

Returns

  • {:ok, expression}.

  • {:error, %Tempo.ConversionError{reason: :no_finer_unit}} for a value written to the second or finer.

Examples

iex> Tempo.extend(~o"2020")
{:ok, ~o"2020Y{1..12}M"}

iex> Tempo.extend(~o"2026-06")
{:ok, ~o"2026Y6M{1..30}D"}

iex> Tempo.extend(~o"2026Y{6,7}M")
{:ok, ~o"2026Y{6,7}M{1..-1}D"}

iex> Tempo.extend(~o"2026Y2G3MU")
{:ok, ~o"2026Y{4..6}M"}

iex> Tempo.extend(~o"2026-03-29[Europe/Paris]")
{:ok, ~o"2026Y3M29DT{0..1,3..23}H[Europe/Paris]"}

extend!(arg1)

@spec extend!(t()) :: t()

extend/1, returning its answer or raising the error.

Arguments

Returns

  • What extend/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.extend!(~o"2020")
~o"2020Y{1..12}M"

floating?(expression)

@spec floating?(t()) :: boolean()

Whether an expression is floating: a reading of a clock with no zone and no time shift, which names a civil time but not whose. The complement of zoned?/1.

Arguments

  • expression is a t/0.

Returns

  • true or false.

Examples

iex> Tempo.floating?(~o"2024-01-01")
true

iex> Tempo.floating?(~o"2024-01-01T10Z")
false

iex> Tempo.floating?(~o"2024-01-01[Australia/Sydney]")
false

from_date(value)

@spec from_date(Date.t()) :: t()

A Tempo value from a Date, as from_elixir/2 reads one.

Arguments

Returns

Examples

iex> Tempo.from_date(~D[2022-06-15])
~o"2022-06-15"

from_datetime(value)

@spec from_datetime(DateTime.t()) :: t()

A Tempo value from a DateTime, as from_elixir/2 reads one.

Arguments

Returns

Examples

iex> Tempo.from_datetime(~U[2022-06-15 10:30:00Z])
~o"2022-06-15T10:30:00Z"

from_elixir(value, options \\ [])

@spec from_elixir(term(), keyword()) ::
  {:ok, t() | Tempo.Duration.t()} | {:error, error()}

A Tempo value from one of Elixir's date and time structs.

A Date is a day, a Time a second, a NaiveDateTime a floating second, a DateTime a second in its zone (UTC as Z), each in the struct's calendar; a Date.Range is the span of its days; an Elixir Duration a Tempo.Duration.t/0.

Arguments

Options

  • :resolution is a unit the value is written at, as at_resolution/2 writes it.

Returns

  • {:ok, value}.

  • {:error, %Tempo.ConversionError{}} for anything else, and for a stepped or descending range.

Examples

iex> Tempo.from_elixir(~D[2022-06-15])
{:ok, ~o"2022-06-15"}

iex> Tempo.from_elixir(~N[2022-06-15 10:30:00])
{:ok, ~o"2022-06-15T10:30:00"}

iex> Tempo.from_elixir(~U[2022-06-15 10:30:00Z])
{:ok, ~o"2022-06-15T10:30:00Z"}

iex> Tempo.from_elixir(Date.range(~D[2026-07-01], ~D[2026-07-31]))
{:ok, ~o"2026-07-01/2026-07-31"}

iex> Tempo.from_elixir(~N[2022-06-15 10:30:00], resolution: :day)
{:ok, ~o"2022-06-15"}

from_elixir!(value, options \\ [])

@spec from_elixir!(term(), keyword()) :: t() | Tempo.Duration.t()

from_elixir/2, returning the value or raising.

Arguments

Options

Returns

  • What from_elixir/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.from_elixir!(~T[10:30:00])
~o"T10:30:00"

from_iso8601(text, calendar_or_options \\ [])

@spec from_iso8601(String.t(), module() | keyword()) ::
  {:ok, t() | Tempo.Duration.t()} | {:error, error()}

Reads an ISO 8601, ISO 8601-2, EDTF or RFC 9557 text into an expression.

Arguments

  • text is the text to read.

  • calendar_or_options is the calendar module a text that names none is read in, or a keyword list of options. The default is Calendar.ISO.

Options

  • :calendar is the calendar a text that names none is in. A text that names its calendar ([u-ca=hebrew]) is read in that one.

  • :profile is the kind of text the caller declares it to be: :date, :time, :datetime, :interval or :duration. A text of another kind is an error, where without a profile it would be read as the kind it is.

  • :territory is the territory a season of 21 to 24 is given its months in as it is read, as in_territory/2 takes it; with none, and no :locale, a season is read as its code.

  • :locale is a locale whose territory is used as :territory is.

  • :overflow is :refuse, the default, or :carry. ISO 8601-2's Amendment 1 (2025) lets a value be written with a part out of its range, meaning the date it carries to: with :carry, 2018Y13M is January 2019 and 2020Y2M59D is 30 March 2020. With :refuse such a value is a Tempo.InvalidDateError, so that a mistyped date is not read as another. A negative day counts back from the end of its month either way.

Returns

Examples

iex> Tempo.from_iso8601("2026-06-15")
{:ok, ~o"2026-06-15"}

iex> Tempo.from_iso8601("2026Y6M15D")
{:ok, ~o"2026-06-15"}

iex> Tempo.from_iso8601("P1Y2M")
{:ok, ~o"P1Y2M"}

iex> Tempo.from_iso8601("2026-06-15", profile: :date)
{:ok, ~o"2026-06-15"}

iex> {:error, %Tempo.ParseError{}} = Tempo.from_iso8601("2026-06-15T10:30", profile: :date)

iex> {:error, %Tempo.InvalidDateError{}} = Tempo.from_iso8601("2026-02-30")

iex> Tempo.from_iso8601("2026-02-30", overflow: :carry)
{:ok, ~o"2026-03-02"}

iex> {:error, %Tempo.ParseError{}} = Tempo.from_iso8601("tomorrow")

from_iso8601!(text, calendar_or_options \\ [])

@spec from_iso8601!(String.t(), module() | keyword()) :: t() | Tempo.Duration.t()

Reads a text as from_iso8601/2 does, returning the expression or raising.

Arguments

Options

Returns

  • What from_iso8601/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.from_iso8601!("2026-06")
~o"2026-06"

from_naive_datetime(value)

@spec from_naive_datetime(NaiveDateTime.t()) :: t()

A Tempo value from a NaiveDateTime, as from_elixir/2 reads one.

Arguments

Returns

Examples

iex> Tempo.from_naive_datetime(~N[2022-06-15 10:30:00])
~o"2022-06-15T10:30:00"

from_time(value)

@spec from_time(Time.t()) :: t()

A Tempo value from a Time, as from_elixir/2 reads one.

Arguments

Returns

Examples

iex> Tempo.from_time(~T[10:30:00])
~o"T10:30:00"

hour(expression)

@spec hour(t()) :: integer() | nil

The hour an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no hour or several.

Examples

iex> Tempo.hour(~o"2026-06-15T10:30")
10

iex> Tempo.hour(~o"2026-06-15")
nil

in_territory(other, territory)

@spec in_territory(t(), atom() | String.t()) :: {:ok, t()} | {:error, error()}

An expression with its seasons given their months in a territory. A season of 21 to 24 (spring, summer, autumn, winter) is three whole months, and which three is the hemisphere's to say: spring is March to May north of the equator and September to November south of it.

A season of a hemisphere (25 to 32) names its own and is as it was, as is an expression with no season in it.

Arguments

  • expression is a t/0: a season, an interval with a season at an end, or a set of them.

  • territory is a territory (:AU, "GB") or a locale whose territory is taken ("en-AU"), as Tempo.Territory.resolve/1 reads it.

Returns

  • {:ok, expression}, each season the interval of its months.

  • {:error, %Tempo.ConversionError{}} for a territory on no one side of the equator (Brazil, Kenya, the world).

  • {:error, %Tempo.IntervalEndpointsError{}} for an interval of seasons that ends before it begins in that hemisphere.

  • {:error, %ArgumentError{}} for what names no territory.

Examples

iex> Tempo.in_territory(~o"2026-21", :GB)
{:ok, ~o"2026-03/2026-05"}

iex> Tempo.in_territory(~o"2026-21", :AU)
{:ok, ~o"2026-09/2026-11"}

iex> Tempo.in_territory(~o"2026-22", :NZ)
{:ok, ~o"2026-12/2027-02"}

iex> {:error, %Tempo.ConversionError{}} = Tempo.in_territory(~o"2026-21", :BR)

in_territory!(expression, territory)

@spec in_territory!(t(), atom() | String.t()) :: t()

in_territory/2, returning the expression or raising.

Arguments

Returns

  • What in_territory/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.in_territory!(~o"2026-23", :AU)
~o"2026-03/2026-05"

in_zone(other, zone)

@spec in_zone(t(), String.t()) :: {:ok, t()} | {:error, error()}

A floating expression placed in a time zone, its readings unchanged: the runtime form of writing [zone] after it.

Arguments

  • expression is a t/0 with no zone and no shift.

  • zone is a time zone name the database knows, or a fixed offset as IXDTF writes one ("+02:00").

Returns

  • {:ok, zoned}.

  • {:error, %Tempo.ZoneError{}} when the zone is not known or its clock skips the reading, or {:error, %Tempo.FrameError{}} when the expression is already zoned or shifted (shift_zone/2 moves one).

Examples

iex> Tempo.in_zone(~o"2026-06-15T10", "Europe/Paris")
{:ok, ~o"2026-06-15T10[Europe/Paris]"}

iex> Tempo.in_zone(~o"2026-06-15T10", "+02:00")
{:ok, ~o"2026-06-15T10[+02:00]"}

iex> {:error, %Tempo.FrameError{}} = Tempo.in_zone(~o"2026-06-15T10Z", "Europe/Paris")

in_zone!(arg1, arg2)

@spec in_zone!(t(), String.t()) :: t()

in_zone/2, returning its answer or raising the error.

Arguments

Returns

  • What in_zone/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.in_zone!(~o"2026-06-15T10:00", "Europe/Paris")
~o"2026-06-15T10:00[Europe/Paris]"

intersection(a, b, options \\ [])

@spec intersection(t(), t() | [t()], keyword()) :: {:ok, t()} | {:error, error()}

The intersection of two expressions: every instant in both, as spans trimmed to the overlap, each carrying the first operand's metadata. A span ends where the member it comes from ends, so two members that meet are two spans; Tempo.IntervalSet.coalesce/1 joins them.

Arguments

  • a is a t/0 that has spans.

  • b is a t/0 that has spans, or a list of them, intersected with each in turn: an empty list leaves a as it is.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in. With none, an operand with no end or no year is treated as union/3 says.

  • :metadata says which member's metadata a result carries: :first (the default), :second, :merge, or a function of the two.

Returns

  • {:ok, set}, a t/0 of the :all form, empty when nothing is shared.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.intersection(~o"2026", ~o"2026-06-15")
{:ok, ~o"{2026-06-15/2026-06-15}"}

iex> Tempo.intersection(~o"2026-06-15T09/2026-06-15T17", ~o"2026-06-15T12/2026-06-15T13")
{:ok, ~o"{2026-06-15T12/2026-06-15T13}"}

iex> workday = ~o"2026-06-15T09/2026-06-15T17"
iex> alice_free = ~o"2026-06-15T10/2026-06-15T15"
iex> bob_free = ~o"2026-06-15T13/2026-06-15T18"
iex> Tempo.intersection(workday, [alice_free, bob_free])
{:ok, ~o"{2026-06-15T13/2026-06-15T15}"}

intersection!(arg1, arg2, arg3 \\ [])

@spec intersection!(t(), t() | [t()], keyword()) :: t()

intersection/3, returning its answer or raising the error.

Arguments

Returns

  • What intersection/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.intersection!(~o"2026-01/2026-03", ~o"2026-03/2026-05") |> Tempo.IntervalSet.count()
1

leap_year?(expression)

@spec leap_year?(t()) :: boolean() | {:error, error()}

Whether the year an expression names is a leap year of its calendar.

Arguments

  • expression is a t/0.

Returns

  • true or false.

  • {:error, %Tempo.ConversionError{reason: :needs_a_year}} when the expression names no year or several.

Examples

iex> Tempo.leap_year?(~o"2024")
true

iex> Tempo.leap_year?(~o"2025-06-15")
false

longer_than?(expression, duration)

@spec longer_than?(t(), Tempo.Duration.t()) :: boolean() | {:error, error()}

Whether an expression is longer than a duration.

The expression is measured as duration/1 measures it, as the time it covers; the duration is laid from the expression's start, so a month is as long as the month it starts in. An open end is longer than any duration.

Arguments

Returns

  • true or false.

  • {:error, exception} as for duration/1.

Examples

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.longer_than?(meeting, ~o"PT1H")
true

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.longer_than?(meeting, ~o"PT90M")
false

map(enumerable, fun)

@spec map(Enumerable.t(), (t() -> t() | {:ok, t()} | {:error, error()})) :: t()

try_map/2, returning the set or raising.

Arguments

Returns

  • What try_map/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> [~o"2025-07-04", ~o"2026-07-04"] |> Tempo.map(&Tempo.nearest_workday(&1, :US)) |> Tempo.IntervalSet.count()
2

members(expression, options \\ [])

@spec members(t() | Tempo.Duration.t(), keyword()) :: {:ok, [t()]} | {:error, error()}

The members of an expression (law 6): the values a value with a set, a mask or a group in a unit allows; the members of a set; the occurrences of a recurrence; a singular expression itself.

Arguments

  • expression is a t/0.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

  • :locale is the locale whose working week a rule that rolls a day to a weekday keeps to; the default is the territory in force.

Returns

  • {:ok, members}, a list of t/0.

  • {:error, %Tempo.ConversionError{}} as for to_interval/2.

Examples

iex> Tempo.members(~o"2026Y{1,3}M")
{:ok, [~o"2026-01", ~o"2026-03"]}

iex> Tempo.members(~o"{2026-06,2026-08}")
{:ok, [~o"2026-06", ~o"2026-08"]}

iex> Tempo.members(~o"2026-06")
{:ok, [~o"2026-06"]}

members!(arg1, arg2 \\ [])

@spec members!(t() | Tempo.Duration.t(), keyword()) :: [t()]

members/2, returning its answer or raising the error.

Arguments

Returns

  • What members/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.members!(~o"{2026-06-15,2026-06-17}")
[~o"2026-06-15", ~o"2026-06-17"]

members_in_exactly_one(a, b, options \\ [])

@spec members_in_exactly_one(t(), t(), keyword()) :: {:ok, t()} | {:error, error()}

The members of either expression no instant of which is in the other.

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

Returns

  • {:ok, set}, a t/0 of the :all form.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.members_in_exactly_one(~o"2020", ~o"2022")
{:ok, ~o"{2020/2020,2022/2022}"}

iex> Tempo.members_in_exactly_one(~o"2026-01/2026-06", ~o"2026-04/2026-09")
{:ok, ~o"{}"}

members_in_exactly_one!(arg1, arg2, arg3 \\ [])

@spec members_in_exactly_one!(t(), t(), keyword()) :: t()

members_in_exactly_one/3, returning its answer or raising the error.

Arguments

Returns

  • What members_in_exactly_one/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.members_in_exactly_one!(~o"{2026-06-15,2026-06-17}", ~o"{2026-06-17,2026-06-19}")
~o"{2026-06-15/2026-06-15,2026-06-19/2026-06-19}"

members_outside(a, b, options \\ [])

@spec members_outside(t(), t(), keyword()) :: {:ok, t()} | {:error, error()}

The members of the first expression no instant of which is in the second, each kept whole with its metadata.

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

Returns

  • {:ok, set}, a t/0 of the :all form.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.members_outside(~o"{2026-01,2026-03}", ~o"2026-03-15")
{:ok, ~o"{2026-01/2026-01}"}

members_outside!(arg1, arg2, arg3 \\ [])

@spec members_outside!(t(), t(), keyword()) :: t()

members_outside/3, returning its answer or raising the error.

Arguments

Returns

  • What members_outside/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.members_outside!(~o"{2026-06-15,2026-06-17}", ~o"2026-06-17T10")
~o"{2026-06-15/2026-06-15}"

members_overlapping(a, b, options \\ [])

@spec members_overlapping(t(), t(), keyword()) :: {:ok, t()} | {:error, error()}

The members of the first expression some instant of which is in the second, each kept whole with its metadata.

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

Returns

  • {:ok, set}, a t/0 of the :all form.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.members_overlapping(~o"{2026-01,2026-03}", ~o"2026-03-15")
{:ok, ~o"{2026-03/2026-03}"}

members_overlapping!(arg1, arg2, arg3 \\ [])

@spec members_overlapping!(t(), t(), keyword()) :: t()

members_overlapping/3, returning its answer or raising the error.

Arguments

Returns

  • What members_overlapping/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.members_overlapping!(~o"{2026-06-15,2026-06-17}", ~o"2026-06-17T10")
~o"{2026-06-17/2026-06-17}"

metadata(other)

@spec metadata(t()) :: map()

The metadata an expression carries: a map, empty by default.

Arguments

  • expression is a t/0.

Returns

  • The metadata, a map.

Examples

iex> Tempo.metadata(~o"2026-06-15")
%{}

iex> ~o"2026-06-15" |> Tempo.put_metadata(%{room: "A"}) |> Tempo.metadata()
%{room: "A"}

minute(expression)

@spec minute(t()) :: integer() | nil

The minute an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no minute or several.

Examples

iex> Tempo.minute(~o"2026-06-15T10:30")
30

iex> Tempo.minute(~o"2026-06-15T10")
nil

month(expression)

@spec month(t()) :: integer() | nil

The month an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no month or several.

Examples

iex> Tempo.month(~o"2026-06-15")
6

iex> Tempo.month(~o"2026")
nil

nearest_workday(value, territory \\ nil)

@spec nearest_workday(t(), term()) :: {:ok, t()} | {:error, error()}

The nearest workday to a value: the value itself when it is one, else the closer of the next and the previous, the next on a tie.

Arguments

Returns

  • {:ok, workday}, a t/0.

  • {:error, %Tempo.ConversionError{reason: :open}} when the days off never end either way from the value, and {:error, exception} for a value that names several days or a territory Localize does not know.

Examples

iex> Tempo.nearest_workday(~o"2026-06-13", :US)
{:ok, ~o"2026-06-12"}

iex> Tempo.nearest_workday(~o"2026-06-14", :US)
{:ok, ~o"2026-06-15"}

new(components)

@spec new(keyword() | map()) :: {:ok, t()} | {:error, error()}

Builds a value from its units.

Arguments

  • components is a keyword list or a map of units (:year, :month, :day, :week, :day_of_week, :day_of_year, :quarter, :season, :hour, :minute, :second, :microsecond), in any order, each holding what a written unit holds (held/0); a :microsecond holds {microseconds, digits} as Elixir's Time does, or an integer, which is six digits. A :day_of_week is numbered from the day the value's weeks begin on, or is a day's name (:friday), numbered so in the value's calendar. These keys go beside them:
    • :calendar is the calendar the value is in. The default is Calendar.ISO.

    • :zone is the name of the value's time zone, as the database knows it.

    • :shift is the time shift, [hour: 2] for Z2H, [hour: 0] for Z, and [hour: 0, minute: 0] for +00:00, a local time at no offset.

    • :qualifications marks units of the value as ISO 8601-2's ?, ~ and % do: a map of the unit to :uncertain, :approximate or :both, so %{year: :uncertain} is 2026?Y.

    • :metadata is a map the caller keeps on the value. The default is %{}.

Returns

Examples

iex> Tempo.new(year: 2026, month: 6, day: 15)
{:ok, ~o"2026-06-15"}

iex> Tempo.new(day: 15, month: 6, year: 2026)
{:ok, ~o"2026-06-15"}

iex> Tempo.new(year: 2026, week: 24, day_of_week: 3)
{:ok, ~o"2026-W24-3"}

iex> Tempo.new(day_of_week: :friday, calendar: Calendrical.Hebrew)
{:ok, ~o"6K[u-ca=hebrew]"}

iex> Tempo.new(year: 2026, month: 6, day: 15, zone: "Europe/Paris")
{:ok, ~o"2026-06-15[Europe/Paris]"}

iex> Tempo.new(hour: 10, minute: 30, second: 45, microsecond: {500_000, 1})
{:ok, ~o"T10:30:45.5"}

iex> Tempo.new(year: 2020..2024)
{:ok, ~o"{2020..2024}Y"}

iex> Tempo.new(year: 2026, month: 6, qualifications: %{month: :approximate})
{:ok, ~o"2026Y6~M"}

iex> Tempo.new(year: 2026, group: {2, [month: 3]})
{:ok, ~o"2026Y2G3MU"}

iex> {:error, %Tempo.InvalidDateError{}} = Tempo.new(year: 2026, month: 13)

iex> {:error, %ArgumentError{}} = Tempo.new(year: 2026, month: 6, week: 24)

iex> {:error, %Tempo.ZoneError{}} = Tempo.new(year: 2026, zone: "Mars/Olympus")

iex> {:ok, value} = Tempo.new(%{year: 2026, month: 6, day: 15})
iex> value
~o"2026-06-15"

new!(components)

@spec new!(keyword() | map()) :: t()

Builds a value as new/1 does, returning the value or raising.

Arguments

  • The arguments are those of new/1.

Returns

  • What new/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.new!(year: 2026, month: 6)
~o"2026-06"

next_workday(value, territory \\ nil)

@spec next_workday(t(), term()) :: {:ok, t()} | {:error, error()}

The first workday after a value.

Arguments

Returns

  • {:ok, workday}, a t/0.

  • {:error, %Tempo.ConversionError{reason: :open}} when the days off never end after the value, and {:error, exception} for a value that names several days or a territory Localize does not know.

Examples

iex> Tempo.next_workday(~o"2026-06-12", :US)
{:ok, ~o"2026-06-15"}

now(zone \\ "Etc/UTC")

@spec now(String.t()) :: {:ok, t()} | {:error, error()}

The current second in a zone, from the configured Tempo.Clock.

Arguments

  • zone is an IANA zone name; the default is "Etc/UTC".

Returns

  • {:ok, value}, a t/0 at second resolution in the zone.

  • {:error, %Tempo.ZoneError{}} for a zone the database does not know.

Examples

iex> {:ok, now} = Tempo.now("Europe/London")
iex> Tempo.resolution(now)
:second

iex> {:error, %Tempo.ZoneError{}} = Tempo.now("Continent/Imaginary")

now!(arg1 \\ "Etc/UTC")

@spec now!(String.t()) :: t()

now/1, returning its answer or raising the error.

Arguments

  • The arguments are those of now/1.

Returns

  • What now/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.now!("Europe/London") |> Tempo.resolution()
:second

on(value, other)

@spec on(t(), t()) :: {:ok, t()} | {:error, error()}

at/2 with its arguments the other way round, so that the code reads as English does: 10:30 on 4 January.

Arguments

  • value is a t/0, a time of day.

  • other is a t/0, a date.

Returns

  • What at(other, value) returns.

Examples

iex> Tempo.on(~o"T10:30", ~o"2026-01-04")
{:ok, ~o"2026-01-04T10:30"}

on!(arg1, arg2)

@spec on!(t(), t()) :: t()

on/2, returning its answer or raising the error.

Arguments

  • The arguments are those of on/2.

Returns

  • What on/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.on!(~o"T10:30", ~o"2026-01-04")
~o"2026-01-04T10:30"

one_of(members)

@spec one_of([t()]) :: {:ok, t()} | {:error, error()}

Builds a set one of whose members is meant: "in 1984, 1986 or 1988". It has readings and no spans, where the set Tempo.IntervalSet.new/2 builds is all of its members.

Arguments

  • members is a list of t/0, in one calendar and one zone.

Returns

  • {:ok, set}, a t/0 of the :one form.

  • {:error, %ArgumentError{}} for no members, or members in two calendars or two zones.

Examples

iex> Tempo.one_of([~o"1984", ~o"1986", ~o"1988"])
{:ok, ~o"[1984,1986,1988]"}

iex> {:ok, one_year} = Tempo.one_of([~o"1984", ~o"1986"])
iex> Tempo.possibly?(:before, one_year, ~o"1985")
true

one_of!(arg1)

@spec one_of!([t()]) :: t()

one_of/1, returning its answer or raising the error.

Arguments

Returns

  • What one_of/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.one_of!([~o"1984", ~o"1986"])
~o"[1984,1986]"

overlaps?(a, b, options \\ [])

@spec overlaps?(t(), t(), keyword()) :: boolean()

Whether the first expression shares an instant with the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.overlaps?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.overlaps?(~o"2026-06", ~o"2026")
true

parse(text, options \\ [])

@spec parse(String.t(), keyword()) ::
  {:ok, t() | Tempo.Duration.t()} | {:error, error()}

Reads a text written in ISO 8601 or in a locale's own words.

ISO 8601 is read first, with the whole grammar from_iso8601/2 reads. A text it does not describe is then read as the locale's words for a date, a time or a range of them ("May 16, 2026", "2:30 PM", "May 5 – May 10, 2026"), through Calendrical.parse/2, so one text field can take either form.

Arguments

  • text is the text.

  • options is a keyword list of options.

Options

  • :locale is the locale whose words are read; the default is Localize's. One named is also where a season of 21 to 24 is given its months, as for from_iso8601/2.

  • :territory is as for from_iso8601/2.

  • :calendar is the calendar the text is read in.

  • :profile is as for from_iso8601/2, for the ISO 8601 reading.

  • :reference_date is the day two-digit years and partial dates are read against.

Returns

  • {:ok, expression}.

  • {:error, %Tempo.ParseError{}} for a text that is neither.

Examples

iex> Tempo.parse("2026-06-15")
{:ok, ~o"2026-06-15"}

iex> Tempo.parse("May 16, 2026", locale: :en)
{:ok, ~o"2026-05-16"}

iex> Tempo.parse("15 juin 2026", locale: :fr)
{:ok, ~o"2026-06-15"}

iex> Tempo.parse("May 5 – May 10, 2026", locale: :en)
{:ok, ~o"2026-05-05/2026-05-10"}

iex> {:error, %Tempo.ParseError{}} = Tempo.parse("whenever")

parse!(text, options \\ [])

@spec parse!(String.t(), keyword()) :: t() | Tempo.Duration.t()

parse/2, returning the expression or raising.

Arguments

  • The arguments are those of parse/2.

Options

Returns

  • What parse/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.parse!("15 June 2026", locale: :en)
~o"2026-06-15"

parse_duration(text)

@spec parse_duration(String.t()) :: {:ok, Tempo.Duration.t()} | {:error, error()}

Reads a text that must be an ISO 8601 duration and nothing else: an iCalendar DURATION, a configured timeout. A date read where a duration was meant is an error here, where from_iso8601/2 would succeed with a value of the wrong kind.

Arguments

  • text is a string.

Returns

Examples

iex> Tempo.parse_duration("PT1H")
{:ok, ~o"PT1H"}

iex> {:error, %Tempo.ParseError{}} = Tempo.parse_duration("2026-06-15")

parse_duration!(arg1)

@spec parse_duration!(String.t()) :: Tempo.Duration.t()

parse_duration/1, returning its answer or raising the error.

Arguments

Returns

  • What parse_duration/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.parse_duration!("P1DT2H")
~o"P1DT2H"

possibly?(predicate, a, b)

@spec possibly?(Tempo.Allen.predicate() | Tempo.Allen.relation(), t(), t()) ::
  boolean()

Whether a predicate holds between two expressions for some reading of them.

Arguments

  • predicate is a predicate or relation, as for certainty/3.

  • a and b are each a t/0.

Returns

Examples

iex> Tempo.possibly?(:overlaps, ~o"2018±2Y", ~o"2020")
true

iex> Tempo.possibly?(:overlaps, ~o"2018±2Y", ~o"2030")
false

previous_workday(value, territory \\ nil)

@spec previous_workday(t(), term()) :: {:ok, t()} | {:error, error()}

The last workday before a value.

Arguments

Returns

  • {:ok, workday}, a t/0.

  • {:error, %Tempo.ConversionError{reason: :open}} when the days off never end before the value, and {:error, exception} for a value that names several days or a territory Localize does not know.

Examples

iex> Tempo.previous_workday(~o"2026-06-15", :US)
{:ok, ~o"2026-06-12"}

put_metadata(expression, metadata)

@spec put_metadata(t(), map()) :: t()

An expression with metadata merged into what it carries.

Arguments

  • expression is a t/0.

  • metadata is a map.

Returns

  • The expression with the metadata merged in, a key given replacing the one it carried.

Examples

iex> ~o"2026-06-15" |> Tempo.put_metadata(%{room: "A"}) |> Tempo.put_metadata(%{owner: "Ann"}) |> Tempo.metadata()
%{room: "A", owner: "Ann"}

qualification(other)

@spec qualification(t()) :: qualifications()

The qualifications of a value: which of its units are uncertain (?), approximate (~) or both (%), as a map from unit to qualification.

Arguments

  • value is a t/0.

Returns

  • A map from unit to :uncertain, :approximate or :both; nil when the value has none.

Examples

iex> Tempo.qualification(~o"2026-06~-15")
%{year: :approximate, month: :approximate}

iex> Tempo.qualification(~o"2026-06-15")
nil

qualification(other, unit)

@spec qualification(t(), unit()) :: :uncertain | :approximate | :both | nil

The qualification of one unit of a value: whether it is uncertain (?), approximate (~) or both (%).

Arguments

  • value is a t/0.

  • unit is a unit, such as :year or :month.

Returns

  • :uncertain, :approximate or :both; nil when the unit is neither.

Examples

iex> Tempo.qualification(~o"2026-06~-15", :month)
:approximate

iex> Tempo.qualification(~o"2026-06~-15", :day)
nil

quarter_of_year(expression)

@spec quarter_of_year(t()) :: 1..4 | nil

The quarter of the year an expression lies in, 1 to 4.

Arguments

  • expression is a t/0.

Returns

  • The quarter, 1 to 4; nil when the expression lies in no single quarter.

Examples

iex> Tempo.quarter_of_year(~o"2026-06-15")
2

iex> Tempo.quarter_of_year(~o"2026")
nil

relation(a, b)

@spec relation(t(), t()) :: Tempo.Allen.relation() | {:error, error()}

Allen's relation of one singular expression to another: one of the thirteen Tempo.Allen.relation/0 atoms.

Arguments

  • a and b are each a t/0 with one span: a value or an interval.

Returns

  • The relation.

  • {:error, %Tempo.FrameError{}} when either has several spans or none, or the two share no frame.

Examples

iex> Tempo.relation(~o"2026-06-15", ~o"2026-06-16")
:meets

iex> Tempo.relation(~o"2026-06-01/2026-06-10", ~o"2026-06-05/2026-06-15")
:overlaps

iex> Tempo.relation(~o"2026", ~o"2026-06-15")
:contains

resolution(expression)

@spec resolution(t() | Tempo.Duration.t()) :: unit() | nil

The unit an expression is written to: the finest unit of a value, of an interval's ends or a set's members. A value ending in a group is written to the group's unit.

Arguments

Returns

  • The unit, an atom; nil for an interval open at both ends, which is written to none.

Examples

iex> Tempo.resolution(~o"2022-11")
:month

iex> Tempo.resolution(~o"../..")
nil

iex> Tempo.resolution(~o"2022Y1M2G3DU")
:day

iex> Tempo.resolution(~o"2026-06-15T10/2026-06-16")
:hour

roll_to_workday(value, territory \\ nil, options \\ [])

@spec roll_to_workday(t(), term(), keyword()) :: {:ok, t()} | {:error, error()}

A value rolled to a workday by a business day convention: itself when it is a workday, else the workday the convention names.

Arguments

  • value is a t/0 within one day.

  • territory is as for workdays/2.

  • options is a keyword list of options.

Options

  • :roll is :following (the default), :preceding, :nearest (the closer of the two, the one after on a tie), or one of :modified_following, :modified_preceding and :modified_nearest; the modified conventions turn back when the roll would leave the month.

Returns

  • {:ok, workday}, a t/0.

  • {:error, %ArgumentError{}} for a convention or an option this function does not take, {:error, %Tempo.ConversionError{reason: :open}} when the days off never end the way the value is rolled, and {:error, exception} for a value that names several days or a territory Localize does not know.

Examples

iex> Tempo.roll_to_workday(~o"2026-05-30", :US)
{:ok, ~o"2026-06-01"}

iex> Tempo.roll_to_workday(~o"2026-05-30", :US, roll: :modified_following)
{:ok, ~o"2026-05-29"}

iex> Tempo.roll_to_workday(~o"2026-11-01", :US, roll: :modified_preceding)
{:ok, ~o"2026-11-02"}

round(expression, unit \\ :day)

@spec round(t(), unit()) :: {:ok, t()} | {:error, error()}

A value rounded to the nearest period of a unit: the period it starts in, or the next when its start is past the midpoint, measured in elapsed time. Half way rounds up, as Kernel.round/1 does.

Arguments

  • expression is a t/0 that is one value, or an interval or set of them, rounded end by end and member by member.

  • unit is a unit no finer than the value is written to; the default is :day.

Returns

  • {:ok, expression} at the unit's resolution.

  • {:error, %Tempo.ConversionError{}} with reason {:finer_than_written, unit} when the unit is finer than the value, and :several when the value names several.

Examples

iex> Tempo.round(~o"2022-11-21T13:30:00")
{:ok, ~o"2022-11-22"}

iex> Tempo.round(~o"2022-11-21", :month)
{:ok, ~o"2022-12"}

iex> Tempo.round(~o"2026-02-15", :month)
{:ok, ~o"2026-03"}

iex> Tempo.round(~o"T23:45", :hour)
{:ok, ~o"T0H"}

round!(arg1, arg2)

@spec round!(t(), unit()) :: t()

round/2, returning its answer or raising the error.

Arguments

  • The arguments are those of round/2.

Returns

  • What round/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.round!(~o"2026-06-15T13:30", :day)
~o"2026-06-16"

second(expression)

@spec second(t()) :: integer() | nil

The second an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no second or several.

Examples

iex> Tempo.second(~o"T10:30:45")
45

iex> Tempo.second(~o"T10:30")
nil

select(base, selector, options \\ [])

@spec select(t(), term(), keyword()) :: {:ok, t()} | {:error, error()}

The values a selector picks in each span of an expression: the Fridays of June, Christmas Day in each year of a span, half past ten in a working day, a territory's workdays in a term.

Arguments

  • base is a t/0 with spans: a value, an interval, a set, a bounded recurrence. Each span is a period the selector is applied in.

  • selector is one of: a t/0 with no year, whose units are the parts selected (~o"12M25D", ~o"5K", ~o"T02", ~o"L1K1IN"); a span with no year, placed in each period it begins in (~o"T22/T02"); a recurrence, whose occurrences within each period are selected; a Tempo.Workdays.t/0 from workdays/2, or weekends/1; a list or a range of integers, the days of the month; a list of selectors, each applied; or a function given each period that answers with a selector for it, or {:error, reason}.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window a base with no end of its own or no year is placed in.

  • :locale is the locale whose working week a selector that rolls a day to a weekday keeps to; the default is the territory in force.

Returns

  • {:ok, set}, a t/0 of the :all form holding the values picked in order, each with its period's metadata.

  • {:error, %ArgumentError{}} for a selector of no recognised shape, and {:error, exception} when the base has no spans or an open start.

Examples

iex> {:ok, days} = Tempo.select(~o"2026-02", [1, 15])
iex> Tempo.IntervalSet.map(days, &Tempo.day/1)
[1, 15]

iex> {:ok, fridays} = Tempo.select(~o"2026-06", ~o"5K")
iex> Tempo.IntervalSet.map(fridays, &Tempo.day/1)
[5, 12, 19, 26]

iex> {:ok, christmases} = Tempo.select(~o"2026/2028", ~o"12M25D")
iex> Tempo.IntervalSet.map(christmases, &Tempo.year/1)
[2026, 2027, 2028]

iex> {:ok, first_monday} = Tempo.select(~o"2026-06", ~o"L1K1IN")
iex> Tempo.IntervalSet.members(first_monday)
[~o"2026-06-01"]

iex> {:ok, workdays} = Tempo.select(~o"2026-02", Tempo.workdays(:US))
iex> Tempo.IntervalSet.count(workdays)
20

iex> clocks_go_forward = ~o"2024-03-10[America/New_York]"
iex> {:ok, skipped} = Tempo.select(clocks_go_forward, ~o"T02")
iex> Tempo.IntervalSet.count(skipped)
0

select!(base, selector, options \\ [])

@spec select!(t(), term(), keyword()) :: t()

select/3, returning the set or raising.

Arguments

Options

Returns

  • What select/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> ~o"2026-06" |> Tempo.select!(~o"5K") |> Tempo.IntervalSet.count()
4

selection(parts, options \\ [])

@spec selection(keyword(), keyword()) :: {:ok, t()} | {:error, error()}

Builds a selection from its parts: what a repeat rule picks in each of its periods, and what select/3 picks in each period of a base. It is the value ISO 8601-2 writes between L and N.

Arguments

  • parts is a keyword list of the parts, in any order, each described under Parts.

  • options is a keyword list of options.

Options

  • :calendar is the calendar the selection is in, whose weeks number its days of the week and count a part from their end: day_of_week: -1 is the last day of a week of that calendar. The default is Calendar.ISO.

Parts

  • :month, :week, :day_of_year, :day, :day_of_week, :hour, :minute and :second are each a whole number, a range, or a list of numbers and ranges. A negative number counts from the end of the period: day: -1 is its last day. :day_of_week is numbered from the day the week begins on, Monday 1 in the Gregorian calendar, or is a day's name (:monday to :sunday), or a list of them, numbered in the selection's calendar; a selection given to a recurrence or a value of another calendar names the same days there.

  • :instance picks among everything the other parts select, by position: 2 the second, -1 the last.

  • :event is the name of a day a calendar computes, one of Tempo.Event.known/0: "easter".

  • :traditional_month is a month as a lunisolar calendar names it: its number, or {5, :leap} for the leap month after the fifth. It is written 5m and 5+m.

  • :skip is :forward or :backward: what is taken for a day or a month the selection names and a year lacks, the next there is (the first of March for a 29th of February) or the last before it (the 28th). With none such a day is passed over. It is written (forward)s.

  • :roll moves each day the other parts select to a weekday, by one of the conventions of roll_to_workday/3: :modified_nearest is the weekday nearest the day and in its month. The weekdays are those of the locale the selection is asked in. It is written (modified-nearest)r.

  • :selection is a selection made first, as parts or as what this function returns; the other parts then select within what it gives. It says "the second Monday, at nine and at ten" where a position alone would count among the times as well.

  • :window is a Tempo.Duration.t/0, the length of what the nested :selection selects: ~o"P1D" for the day after, ~o"-P7D" for the week before.

  • :all is a list of selections, each as parts, whose values are taken together: [[day_of_week: 1, instance: 2], [day_of_week: 3, instance: 2]] is the second Monday and the second Wednesday. The parts written after it select within each value, and a position counts among them all. It is Tempo's own, written L{1K2I,3K2I}N: ISO 8601-2 has one rule of a kind in a selection.

Returns

Examples

iex> Tempo.selection(day_of_week: 1, instance: 2)
{:ok, ~o"L1K2IN"}

iex> Tempo.selection(day_of_week: :friday, instance: -1)
{:ok, ~o"L5K-1IN"}

iex> {:ok, christmas} = Tempo.selection(month: 12, day: 25)
iex> Tempo.select(~o"2026/2027", christmas)
{:ok, ~o"{2026-12-25,2027-12-25}"}

iex> {:ok, last_weekday} = Tempo.selection(day_of_week: 1..5, instance: -1)
iex> Tempo.select(~o"2026-06", last_weekday)
{:ok, ~o"{2026-06-30}"}

iex> {:ok, first_monday_and_last_friday} =
...>   Tempo.selection(all: [[day_of_week: 1, instance: 1], [day_of_week: 5, instance: -1]])
iex> Tempo.select(~o"2026-06", first_monday_and_last_friday)
{:ok, ~o"{2026-06-01,2026-06-26}"}

iex> Tempo.selection([day_of_week: -1], calendar: Calendrical.Hebrew)
{:ok, ~o"L-1KN[u-ca=hebrew]"}

iex> {:error, %ArgumentError{}} = Tempo.selection(fortnight: 1)

selection!(arg1, arg2 \\ [])

@spec selection!(keyword(), keyword()) :: t()

selection/2, returning its answer or raising the error.

Arguments

Returns

  • What selection/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.selection!(month: 12, day: 25)
~o"L12M25DN"

shift(expression, by, options \\ [])

@spec shift(t(), Tempo.Duration.t() | keyword() | String.t(), keyword()) ::
  {:ok, t()} | {:error, error()}

An expression moved by a duration.

A value is shifted at its own resolution, extended to the duration's finest unit where that is finer: a year a day on is its second day. Years and months are counted first, through the calendar, with the day clamped to the month landed in as Date.shift/2 clamps it; then weeks and days; then the units of the clock as elapsed time, so an hour across a change of the clocks is an hour. A value stays on its own axis: a week date a day on is a week date. An interval is moved end by end, and keeps its length when the duration has a month or a year; a set is moved member by member; a recurrence by its first span and its end.

A value with no year is shifted when the answer is the same in every year: the thirty-first of January a day on is the first of February, but a month on it has no answer, and a year on it is itself.

A value that names several (2026Y6M{1,15}D, a mask) is each value it names shifted, written as one value where its units can hold them all, and as a set of values where they cannot. A mask the shift carries is one of the values it lands on.

Arguments

  • expression is a t/0.

  • by is a Tempo.Duration.t/0, its ISO 8601 text, or a keyword list of units and amounts such as [month: 1, day: -5]; amounts may be negative.

  • options is a keyword list of options, which may ride in the keyword form of by.

Options

  • :skipping is a busy set the shift jumps over: a t/0 with spans, or a list of them. The duration is consumed from free time only, so a busy span costs nothing to cross, and an origin inside one first moves to its edge. The duration must be exact (weeks, days and units of the clock); a month or a year of free time has no fixed length. A date shifted by days or weeks steps from free day to free day.

Returns

  • {:ok, expression}, the expression moved.

  • {:error, %Tempo.ConversionError{}} with reason :needs_a_year when the answer depends on a year the value has not, :group when the value ends in a group, {:inexact, unit} when :skipping is given a month or a year, and :selection when the value holds a selection; and {:error, exception} when by is not a duration.

Examples

iex> Tempo.shift(~o"2026-06-15", ~o"P1D")
{:ok, ~o"2026-06-16"}

iex> Tempo.shift(~o"2026-01-31", month: 1)
{:ok, ~o"2026-02-28"}

iex> Tempo.shift(~o"2026", "P1D")
{:ok, ~o"2026-01-02"}

iex> Tempo.shift(~o"2026-W25", day: 1)
{:ok, ~o"2026Y25W2K"}

iex> Tempo.shift(~o"2026Y6M{1,15}D", day: 1)
{:ok, ~o"2026Y6M{2,16}D"}

iex> Tempo.shift(~o"1M31D", day: 1)
{:ok, ~o"2M1D"}

iex> {:error, %Tempo.ConversionError{reason: :needs_a_year}} = Tempo.shift(~o"1M31D", month: 1)

iex> meeting = ~o"2026-06-15T10:00/2026-06-15T11:00"
iex> Tempo.shift(~o"2026-06-15T09:30", ~o"PT1H", skipping: meeting)
{:ok, ~o"2026-06-15T11:30"}

shift!(arg1, arg2)

@spec shift!(t(), Tempo.Duration.t() | keyword() | String.t()) :: t()

shift/2, returning its answer or raising the error.

Arguments

  • The arguments are those of shift/2.

Returns

  • What shift/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.shift!(~o"2026-06-15", day: 1)
~o"2026-06-16"

shift_zone(other, zone)

@spec shift_zone(t(), String.t()) :: {:ok, t()} | {:error, error()}

A zoned expression moved to another time zone: the same instants, read on that zone's clock. A value whose span is one reading there stays a value; a day in Paris is a span from six in the evening to six in New York.

Arguments

  • expression is a t/0 with a zone or a shift.

  • zone is a time zone name the database knows.

Returns

  • {:ok, moved}, a t/0 in zone.

  • {:error, %Tempo.FrameError{}} when the expression is floating (in_zone/2 places one), or {:error, %Tempo.ZoneError{}}.

Examples

iex> Tempo.shift_zone(~o"2026-06-15T14:00[Europe/Paris]", "America/New_York")
{:ok, ~o"2026-06-15T08:00[America/New_York]"}

iex> Tempo.shift_zone(~o"2026-06-15[Europe/Paris]", "America/New_York")
{:ok, ~o"2026-06-14T18/2026-06-15T18[America/New_York]"}

iex> Tempo.shift_zone(~o"2026-06-15T14[Europe/Paris]", "Asia/Kolkata")
{:ok, ~o"2026-06-15T17:30/2026-06-15T18:30[Asia/Kolkata]"}

shift_zone!(arg1, arg2)

@spec shift_zone!(t(), String.t()) :: t()

shift_zone/2, returning its answer or raising the error.

Arguments

Returns

  • What shift_zone/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.shift_zone!(~o"2026-06-15T10:00[Europe/Paris]", "Asia/Tokyo")
~o"2026-06-15T17:00[Asia/Tokyo]"

shorter_than?(expression, duration)

@spec shorter_than?(t(), Tempo.Duration.t()) :: boolean() | {:error, error()}

Whether an expression is shorter than a duration.

The expression is measured as duration/1 measures it, as the time it covers; the duration is laid from the expression's start, so a month is as long as the month it starts in. An open end is longer than any duration.

Arguments

Returns

  • true or false.

  • {:error, exception} as for duration/1.

Examples

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.shorter_than?(meeting, ~o"PT1H")
false

iex> meeting = ~o"2026-06-15T09:00/2026-06-15T10:30"
iex> Tempo.shorter_than?(meeting, ~o"PT90M")
false

split(value)

@spec split(t()) :: {t() | nil, t() | nil} | {:error, error()}

A value split into its date and its time of day, each keeping the value's calendar, zone, qualifications and metadata, so that at/2 of the two is the value.

Arguments

  • value is a t/0 that is one value.

Returns

  • {date, time}, either nil when the value has none.

  • {:error, exception} for an expression that is not a value.

Examples

iex> Tempo.split(~o"2026-06-15T14:30:00")
{~o"2026Y6M15D", ~o"T14H30M0S"}

iex> Tempo.split(~o"2026-06-15T14:30[Europe/Paris]")
{~o"2026Y6M15D[Europe/Paris]", ~o"T14H30M[Europe/Paris]"}

iex> Tempo.split(~o"2026-06-15")
{~o"2026Y6M15D", nil}

symmetric_difference(a, b, options \\ [])

@spec symmetric_difference(t(), t(), keyword()) :: {:ok, t()} | {:error, error()}

The symmetric difference of two expressions: every instant in exactly one of them, as spans, each ending where the member it comes from ends.

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in. With none, an operand with no end or no year is treated as union/3 says.

  • :metadata says which member's metadata a result carries: :first (the default), :second, :merge, or a function of the two.

Returns

  • {:ok, set}, a t/0 of the :all form.

  • {:error, exception} as for union/3.

Examples

iex> Tempo.symmetric_difference(~o"2026-01/2026-06", ~o"2026-04/2026-09")
{:ok, ~o"{2026-01/2026-03,2026-07/2026-09}"}

symmetric_difference!(arg1, arg2, arg3 \\ [])

@spec symmetric_difference!(t(), t(), keyword()) :: t()

symmetric_difference/3, returning its answer or raising the error.

Arguments

Returns

  • What symmetric_difference/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.symmetric_difference!(~o"2026-06-15/2026-06-17", ~o"2026-06-16/2026-06-18")
~o"{2026-06-15/2026-06-15,2026-06-18/2026-06-18}"

time_zone_database()

@spec time_zone_database() :: Calendar.time_zone_database()

The time zone database Tempo asks for a zone's rules.

Tempo asks the Calendar.TimeZoneDatabase set with config :ex_tempo, :time_zone_database, else the one set for Elixir (Calendar.put_time_zone_database/1) where it is other than Elixir's own default, which knows only UTC, else the tz package's, which comes with Tempo. A library built on Tempo asks this to read a zone as Tempo reads it.

Returns

Examples

iex> Tempo.time_zone_database()
Tz.TimeZoneDatabase

to_calendar(other, calendar)

@spec to_calendar(t(), module()) :: {:ok, t()} | {:error, error()}

An expression in another calendar: the same spans, their dates written in that calendar. A date keeps its time of day; a value written to a month or a year becomes the span of dates it covers, a month of one calendar being no month of another; a set converts member by member.

A recurrence with no end is converted as a rule: it is still selected in its own calendar, and each of its spans is read in calendar. So a set holds rules of several calendars, each read in the set's: Eid al-Fitr beside National Day, both in the Gregorian. Such a rule has no ISO 8601 text, its own calendar having no place inside another's.

An expression with no year is converted where it names no date, and keeps its form: a time of day is the same in every calendar, and a day of the week is the same day, numbered as calendar numbers it (3K, Wednesday, is 4K in the Hebrew calendar, whose weeks begin on Sunday). A month, a day or a week with no year has no place in another calendar's year, and is a Tempo.ConversionError with the reason :needs_a_year.

Arguments

  • expression is a t/0 that has spans.

  • calendar is a calendar module.

Returns

  • {:ok, converted}, a t/0 in calendar.

  • {:error, %Tempo.InvalidCalendarError{}} when calendar is not one, {:error, %Tempo.ConversionError{reason: :needs_a_year}} for an expression with no year that names a date, or the error to_interval/2 gives for an expression with no spans.

Examples

iex> Tempo.to_calendar(~o"2026-06-15", Calendrical.Hebrew)
{:ok, ~o"5786-09-30[u-ca=hebrew]"}

iex> Tempo.to_calendar(~o"2026-06-15T09:30", Calendrical.Hebrew)
{:ok, ~o"5786-09-30T09:30[u-ca=hebrew]"}

iex> Tempo.to_calendar(~o"5786-09[u-ca=hebrew]", Calendar.ISO)
{:ok, ~o"2026-05-17/2026-06-15"}

iex> Tempo.to_calendar(~o"3KT10H", Calendrical.Hebrew)
{:ok, ~o"4KT10H[u-ca=hebrew]"}

iex> {:error, %Tempo.ConversionError{reason: :needs_a_year}} =
...>   Tempo.to_calendar(~o"6M15D", Calendrical.Hebrew)

iex> eid = Tempo.every!(~o"P1Y", select: [month: 10, day: 1], calendar: Calendrical.Islamic.UmmAlQura)
iex> {:ok, in_gregorian} = Tempo.to_calendar(eid, Calendar.ISO)
iex> Tempo.to_interval(in_gregorian, within: ~o"2026")
{:ok, ~o"{2026-03-20/2026-03-20}"}

to_calendar!(arg1, arg2)

@spec to_calendar!(t(), module()) :: t()

to_calendar/2, returning its answer or raising the error.

Arguments

Returns

  • What to_calendar/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_calendar!(~o"2026-06-15", Calendrical.Hebrew)
~o"5786Y9M30D[u-ca=hebrew]"

to_date(value)

@spec to_date(t()) :: {:ok, Date.t()} | {:error, error()}

The Date of a value written to the day or finer, on any axis.

Arguments

  • value is a t/0.

Returns

  • {:ok, date}, a Date.t/0.

  • {:error, %Tempo.ConversionError{}} for a value coarser than a day, a value with no date, or any other expression.

Examples

iex> Tempo.to_date(~o"2026-06-15T10:30")
{:ok, ~D[2026-06-15]}

iex> Tempo.to_date(~o"2026-W25-3")
{:ok, ~D[2026-06-17]}

to_date!(arg1)

@spec to_date!(t()) :: Date.t()

to_date/1, returning its answer or raising the error.

Arguments

Returns

  • What to_date/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_date!(~o"2026-06-15")
~D[2026-06-15]

to_datetime(value)

@spec to_datetime(t()) :: {:ok, DateTime.t()} | {:error, error()}

The DateTime of a value with a zone or a time shift.

Arguments

  • value is a t/0.

Returns

  • {:ok, datetime}, in the value's zone, or in UTC for a shift.

  • {:error, %Tempo.ConversionError{reason: :floating}} for a value with neither.

Examples

iex> {:ok, paris} = Tempo.to_datetime(~o"2026-06-15T10:30[Europe/Paris]")
iex> {paris.time_zone, paris.hour}
{"Europe/Paris", 10}

iex> {:error, %Tempo.ConversionError{reason: :floating}} = Tempo.to_datetime(~o"2026-06-15T10:30")

to_datetime!(arg1)

@spec to_datetime!(t()) :: DateTime.t()

to_datetime/1, returning its answer or raising the error.

Arguments

Returns

  • What to_datetime/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_datetime!(~o"2026-06-15T10:30Z")
~U[2026-06-15 10:30:00Z]

to_elixir(span)

@spec to_elixir(t() | Tempo.Duration.t()) :: {:ok, struct()} | {:error, error()}

The Elixir struct a value fills: a Date for a day, a Time for a time of day, a NaiveDateTime for a floating date and time, a DateTime for one with a zone or a shift, and an Elixir Duration for a duration. An interval of two dates is the Date.Range of them, which holds its last day as the interval does; one with a duration for an end is the range of the dates it runs between.

Arguments

Returns

  • {:ok, struct}, or {:ok, range} with a Date.Range.t/0 for an interval of two dates.

  • {:error, %Tempo.ConversionError{}} for a value coarser than a day, a value that names several, an interval whose ends are not both dates, or any other expression.

Examples

iex> Tempo.to_elixir(~o"2026-06-15")
{:ok, ~D[2026-06-15]}

iex> Tempo.to_elixir(~o"2026-06-15T10:30")
{:ok, ~N[2026-06-15 10:30:00]}

iex> Tempo.to_elixir(~o"2026-06-15T10:30:00Z")
{:ok, ~U[2026-06-15 10:30:00Z]}

iex> Tempo.to_elixir(~o"PT8H")
{:ok, Duration.new!(hour: 8)}

iex> Tempo.to_elixir(~o"2026-06-15/2026-06-20")
{:ok, Date.range(~D[2026-06-15], ~D[2026-06-20])}

iex> Tempo.to_elixir(~o"2026-06-15/P6D")
{:ok, Date.range(~D[2026-06-15], ~D[2026-06-20])}

iex> {:error, %Tempo.ConversionError{}} = Tempo.to_elixir(~o"2026-06")

to_elixir!(arg1)

@spec to_elixir!(t() | Tempo.Duration.t()) :: struct()

to_elixir/1, returning its answer or raising the error.

Arguments

Returns

  • What to_elixir/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_elixir!(~o"2026-06-15")
~D[2026-06-15]

to_interval(expression, options \\ [])

@spec to_interval(t() | Tempo.Duration.t(), keyword()) ::
  {:ok, t()} | {:error, error()}

The interval an expression denotes: a singular expression's one span, or a set of the spans of a plural one.

A value is the span from itself through itself when written to a calendar unit, and to the next reading when written to a clock unit (law 3). A written interval has its duration end made a value, and an open end placed by the :within window if one is given. A recurrence, a set and a value with a set, a mask or a group in a unit are a set of the spans of their members, in order.

Arguments

  • expression is a t/0.

  • options is a keyword list of options.

Options

  • :within is a singular expression whose extent bounds a sequence with no end of its own (a recurrence with no count) and places a value with no year on each of its periods, and an interval with no year from each start it holds to the next reading of its end.

  • :territory is the territory a season of 21 to 24 has its months in, as in_territory/2 takes it. With none, and no :locale, such a season keeps its code and has its dates in the territory in force.

  • :locale is a locale whose territory is used as :territory is, and whose working week a rule that rolls a day to a weekday keeps to.

Returns

  • {:ok, interval}, a t/0 of the :span form, or of the :all form for a plural expression.

  • {:error, %Tempo.ConversionError{}} for a duration, a one-of set, a recurrence with no count and no window, a value with no year and no window, or more than 10,000 members at once (the application's :max_values_at_once).

Examples

iex> Tempo.to_interval(~o"2026-06")
{:ok, ~o"2026-06/2026-06"}

iex> Tempo.to_interval(~o"2026-06-15T10")
{:ok, ~o"2026-06-15T10/2026-06-15T11"}

iex> Tempo.to_interval(~o"2026-06-01/P1M")
{:ok, ~o"2026-06-01/2026-06-30"}

iex> Tempo.to_interval(~o"2026Y{1,3}M")
{:ok, ~o"{2026-01/2026-01,2026-03/2026-03}"}

iex> Tempo.to_interval(~o"R3/2026-01-31/P1M")
{:ok, ~o"{2026-01-31/2026-02-27,2026-02-28/2026-03-30,2026-03-31/2026-04-29}"}

iex> Tempo.to_interval(~o"6M15D", within: ~o"2026")
{:ok, ~o"2026-06-15/2026-06-15"}

iex> night_shift = ~o"T22/T06"
iex> Tempo.to_interval(night_shift, within: ~o"2026-06-01/2026-06-02")
{:ok, ~o"{2026-06-01T22/2026-06-02T06,2026-06-02T22/2026-06-03T06}"}

iex> {:error, %Tempo.ConversionError{reason: :bare_duration}} = Tempo.to_interval(~o"P1M")

iex> {:error, %Tempo.ConversionError{reason: :unbounded}} = Tempo.to_interval(~o"R/2026-01-01/P1Y")

to_interval!(expression, options \\ [])

@spec to_interval!(t(), keyword()) :: t()

to_interval/2, returning the interval or set or raising.

Arguments

Options

Returns

  • What to_interval/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_interval!(~o"2026-06-15")
~o"2026-06-15/2026-06-15"

to_iso8601(expression)

@spec to_iso8601(t() | Tempo.Duration.t()) :: {:ok, String.t()} | {:error, error()}

Writes an expression or a duration as ISO 8601 text, in the explicit form of ISO 8601-2 (2026Y6M15D) that every expression has.

A span is written as it was read (law 9): an end that was abbreviated is abbreviated again, and a date end is the date the interval runs through.

Arguments

Returns

  • {:ok, text}.

  • {:error, %Tempo.Iso8601EncodeError{}} for a struct built by hand with parts in two calendars or two zones, which has no one text.

Examples

iex> Tempo.to_iso8601(~o"2026-06-15")
{:ok, "2026Y6M15D"}

iex> Tempo.to_iso8601(~o"2026-06-15T10:30Z")
{:ok, "2026Y6M15DT10H30MZ"}

iex> Tempo.to_iso8601(~o"2026-06-01/2026-06-30")
{:ok, "2026Y6M1D/30D"}

to_iso8601!(expression)

@spec to_iso8601!(t() | Tempo.Duration.t()) :: String.t()

Writes an expression as to_iso8601/1 does, returning the text or raising.

Arguments

Returns

  • What to_iso8601/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_iso8601!(~o"P1Y2M")
"P1Y2M"

to_naive_datetime(value)

@spec to_naive_datetime(t()) :: {:ok, NaiveDateTime.t()} | {:error, error()}

The NaiveDateTime of a value written to the day or finer; units it does not write are zero, and a zone is dropped.

Arguments

  • value is a t/0.

Returns

  • {:ok, datetime}, a NaiveDateTime.t/0.

  • {:error, %Tempo.ConversionError{}} for a value coarser than a day, a value with no date, or any other expression.

Examples

iex> Tempo.to_naive_datetime(~o"2026-06-15")
{:ok, ~N[2026-06-15 00:00:00]}

to_naive_datetime!(arg1)

@spec to_naive_datetime!(t()) :: NaiveDateTime.t()

to_naive_datetime/1, returning its answer or raising the error.

Arguments

Returns

  • What to_naive_datetime/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_naive_datetime!(~o"2026-06-15T10:30")
~N[2026-06-15 10:30:00]

to_relative_string(expression, options \\ [])

@spec to_relative_string(t(), keyword()) :: {:ok, String.t()} | {:error, error()}

A locale's words for where an expression starts relative to another: yesterday, in 3 hours, next year.

Where no unit is asked for, the difference is counted in the largest unit of which a whole one lies between the two starts, and never in one finer than the expression is written to: two minutes across midnight are two minutes, 1 February is tomorrow from 31 January, and the year a day is in is this year. A year, a quarter, a month, a week and a day are counted by the calendar's own on the expression's clock, and an hour, a minute and a second by those that pass.

Arguments

  • expression is a t/0 with a year.

  • options is a keyword list of options.

Options

  • :from is the t/0 the words are relative to; the default is utc_now/0.

  • :unit is the unit counted in: :year, :quarter, :month, :week, :day, :hour, :minute or :second.

  • :format is :standard (the default), :short or :narrow, and :locale a CLDR locale.

  • :numeric is :auto (the default), which takes a locale's word for a difference it has one for (yesterday, next year), or :always, which counts it (1 day ago, in 1 year).

A :from of another calendar is brought to the expression's, so a Hebrew date is counted in Hebrew months and years from a Gregorian one.

A floating expression, one with no zone, is counted from a zoned :from on that zone's clock, and a zoned one from a floating :from on its own: the day after the day a clock shows is tomorrow, whichever of the two the clock belongs to. This is the one place a floating value stands beside a zoned one.

Returns

  • {:ok, words}.

  • {:error, %Tempo.ConversionError{reason: :needs_a_year}} when either has no year, and :open when the expression has no start.

  • {:error, %ArgumentError{}} for an option it does not take or a unit it does not count in.

Examples

iex> now = ~o"2026-06-15T12:00:00Z"
iex> Tempo.to_relative_string(~o"2026-06-14T12:00:00Z", from: now)
{:ok, "yesterday"}

iex> now = ~o"2026-06-15T12:00:00Z"
iex> Tempo.to_relative_string(~o"2026-06-15T15:00:00Z", from: now)
{:ok, "in 3 hours"}

iex> Tempo.to_relative_string(~o"2026-02-01", from: ~o"2026-01-31")
{:ok, "tomorrow"}

iex> Tempo.to_relative_string(~o"2026-02-01", from: ~o"2026-01-31", unit: :month)
{:ok, "next month"}

iex> Tempo.to_relative_string(~o"2026", from: ~o"2026-06-15")
{:ok, "this year"}

iex> Tempo.to_relative_string(~o"2026-06-16", from: ~o"2026-06-15T23:30[Europe/Paris]")
{:ok, "tomorrow"}

iex> Tempo.to_relative_string(~o"2026-01-01T00:01", from: ~o"2025-12-31T23:59")
{:ok, "in 2 minutes"}

iex> {:error, %Tempo.ConversionError{reason: :needs_a_year}} = Tempo.to_relative_string(~o"T10:30", from: ~o"2026-06-15")

to_relative_string!(arg1, arg2)

@spec to_relative_string!(t(), keyword()) :: String.t()

to_relative_string/2, returning its answer or raising the error.

Arguments

Returns

  • What to_relative_string/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_relative_string!(~o"2027", from: ~o"2026-07-01")
"next year"

to_string(expression, options \\ [])

@spec to_string(t() | Tempo.Duration.t(), keyword()) ::
  {:ok, String.t()} | {:error, error()}

A locale's words for an expression, through Localize and CLDR.

An expression is shown as the time it covers. A value written to the day is that day, one written coarser runs from the first to the last of the next unit down (a year its months, a month or a week its days), and one written to a clock unit is its date and time. An interval runs from its first end to its last as the finer of the two is written. An expression of several spans is their list, and a one-of set its alternatives. A duration is its units. A year before 1 is shown with its era.

Arguments

Options

  • :locale is a CLDR locale; the default is Localize's.

  • :format is a CLDR format (:short, :medium, :long, :full) or a skeleton (:yMMMd).

  • :within is a t/0, the window an expression with no year or no end is placed in.

  • :number_system and :prefer are Localize's: the digits a number is written in, and which of a locale's variants of a format is taken.

  • :except is a list of the units a duration's words leave out.

Returns

  • {:ok, words}.

  • {:error, exception}: a Tempo.ConversionError with reason :open for an interval with an open end and :needs_a_year for an expression with no year that the units alone cannot show, an ArgumentError for an option it does not take, or Localize's own error.

Examples

iex> Tempo.to_string(~o"2026-06-15")
{:ok, "Jun 15, 2026"}

iex> Tempo.to_string(~o"2026-06")
{:ok, "Jun 1 –30, 2026" |> String.replace("–30", "– 30")}

iex> Tempo.to_string(~o"2026-06-15", format: :long, locale: "fr")
{:ok, "15 juin 2026"}

iex> Tempo.to_string(~o"2026/2028")
{:ok, "2026 – 2028"}

iex> Tempo.to_string(~o"R3/2026-06-15/P1D")
{:ok, "Jun 15, 2026, Jun 16, 2026, and Jun 17, 2026"}

iex> Tempo.to_string(~o"[2026-06-15,2026-07-04]")
{:ok, "Jun 15, 2026 or Jul 4, 2026"}

iex> Tempo.to_string(~o"-0044-03-15")
{:ok, "Mar 15, 45 BC"}

iex> Tempo.to_string(~o"P1Y6M")
{:ok, "1 year, 6 months"}

iex> {:error, %Tempo.ConversionError{reason: :open}} = Tempo.to_string(~o"2026-06-15/..")

to_string!(expression, options \\ [])

@spec to_string!(t() | Tempo.Duration.t(), keyword()) :: String.t()

to_string/2, returning the words or raising.

Arguments

Options

Returns

  • What to_string/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_string!(~o"2026-06-15")
"Jun 15, 2026"

to_time(value)

@spec to_time(t()) :: {:ok, Time.t()} | {:error, error()}

The Time of a value with a time of day; units it does not write are zero.

Arguments

  • value is a t/0.

Returns

  • {:ok, time}, a Time.t/0.

  • {:error, %Tempo.ConversionError{}} for a value with no time of day, or any other expression.

Examples

iex> Tempo.to_time(~o"2026-06-15T10:30")
{:ok, ~T[10:30:00]}

to_time!(arg1)

@spec to_time!(t()) :: Time.t()

to_time/1, returning its answer or raising the error.

Arguments

Returns

  • What to_time/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_time!(~o"T10:30")
~T[10:30:00]

to_week_start(other, day)

@spec to_week_start(t(), 1..7) :: {:ok, t()} | {:error, error()}

An expression with its weeks begun on another day: the same days, in the Gregorian calendar whose weeks begin on the day given. It is written with the suffix [u-fw=sun], BCP 47's first day of the week, and is what an RRULE's WKST reads to.

Week 1 of a year is its first week with four days in it, as in ISO 8601, whatever day begins it, and a day of the week is counted from that day, in a week date (2026-W25-1) as on its own (decision 80). A week is of one start, so a week converted is the span of its days.

Arguments

  • expression is a t/0 in the Gregorian calendar.

  • day is the day a week begins on, 1 (Monday) to 7 (Sunday).

Returns

  • {:ok, expression}.

  • {:error, %ArgumentError{}} for a day that is none, or an expression of a calendar that is not the Gregorian, which has its own weeks.

Examples

iex> Tempo.to_week_start(~o"2026-06-21", 7)
{:ok, ~o"2026-06-21[u-fw=sun]"}

iex> {:ok, saturday} = Tempo.to_week_start(~o"2026-06-20", 7)
iex> Tempo.week(saturday)
24
iex> Tempo.week(~o"2026-06-20")
25

iex> ~o"2026-W25[u-fw=sun]" |> Tempo.to_interval!() |> Tempo.to_calendar!(Calendar.ISO)
~o"2026-06-21/2026-06-27"

to_week_start!(arg1, arg2)

@spec to_week_start!(t(), 1..7) :: t()

to_week_start/2, returning its answer or raising the error.

Arguments

Returns

  • What to_week_start/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.to_week_start!(~o"2026-06-21", 7)
~o"2026-06-21[u-fw=sun]"

today(zone \\ "Etc/UTC")

@spec today(String.t()) :: {:ok, t()} | {:error, error()}

Today's date in a zone: a floating day.

Arguments

  • zone is an IANA zone name; the default is "Etc/UTC".

Returns

  • {:ok, value}, a t/0 at day resolution with no zone.

  • {:error, %Tempo.ZoneError{}} for a zone the database does not know.

Examples

iex> {:ok, today} = Tempo.today("Australia/Sydney")
iex> Tempo.resolution(today)
:day

today!(arg1 \\ "Etc/UTC")

@spec today!(String.t()) :: t()

today/1, returning its answer or raising the error.

Arguments

  • The arguments are those of today/1.

Returns

  • What today/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.today!("Australia/Sydney") |> Tempo.resolution()
:day

trunc(expression, unit \\ :day)

@spec trunc(t(), unit()) :: {:ok, t()} | {:error, error()}

An expression written to a coarser unit: its units through that one, so a date and time truncated to the day is the date.

A unit the value is not written in names the period of it the value starts in: a date truncated to the week is its week, written as a week date. The ends of an interval and the members of a set are truncated each. A unit finer than the value is written to leaves it as it is.

Arguments

  • expression is a t/0.

  • unit is a unit; the default is :day.

Returns

  • {:ok, expression}.

  • {:error, %Tempo.ConversionError{}} when a unit the value is not written in is asked of a value that names several periods, or has no year, and with reason {:not_written_in, unit} for a season or a traditional month the value is not written in, which a date names none of.

  • {:error, %ArgumentError{}} for a unit that is not one.

Examples

iex> Tempo.trunc(~o"2022-11-21T09:30:00")
{:ok, ~o"2022-11-21"}

iex> Tempo.trunc(~o"2022-11-21T09:30:00", :minute)
{:ok, ~o"2022-11-21T09:30"}

iex> Tempo.trunc(~o"2026-08-16T10:00:00", :week)
{:ok, ~o"2026-W33"}

iex> Tempo.trunc(~o"2026Y6M{1,15}D", :month)
{:ok, ~o"2026-06"}

trunc!(arg1, arg2)

@spec trunc!(t(), unit()) :: t()

trunc/2, returning its answer or raising the error.

Arguments

  • The arguments are those of trunc/2.

Returns

  • What trunc/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.trunc!(~o"2026-06-15T10:30", :day)
~o"2026-06-15"

try_map(enumerable, fun)

@spec try_map(Enumerable.t(), (t() -> t() | {:ok, t()} | {:error, error()})) ::
  {:ok, t()} | {:error, error()}

A function applied to each member of an enumerable of expressions, the results gathered into a set: Enum.map/2 that answers a t/0.

The function may answer a t/0 or {:ok, t}, as the functions of this module do, so they map directly.

Arguments

  • enumerable is an enumerable of t/0: a list, a set, a value walked.

  • fun is a function of one member.

Returns

  • {:ok, set}, a t/0 of the :all form holding the results in order.

  • {:error, exception}, the first error the function answers.

  • {:error, %ArgumentError{}} when enumerable is not enumerable or fun is not a function of one argument.

Examples

iex> independence_days = [~o"2025-07-04", ~o"2026-07-04", ~o"2027-07-04"]
iex> {:ok, observed} = Tempo.try_map(independence_days, &Tempo.nearest_workday(&1, :US))
iex> Tempo.IntervalSet.map(observed, &Tempo.day/1)
[4, 3, 5]

iex> {:error, %Tempo.ConversionError{}} = Tempo.try_map([~o"2026"], &Tempo.nearest_workday(&1, :US))

union(a, b, options \\ [])

@spec union(t(), t() | [t()], keyword()) :: {:ok, t()} | {:error, error()}

The union of two expressions: every member of either, kept whole with its own metadata, in order of start. A member both hold, the same in its span, its form and its metadata, is one member; two members that cover one time otherwise stay two, and Tempo.IntervalSet.coalesce/1 merges them.

Arguments

  • a is a t/0 that has spans: a value, an interval, a recurrence, a set.

  • b is a t/0 that has spans, or a list of them, each united with a in turn: an empty list leaves a as it is.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window the operation works in: a value with no year is placed on each of its periods, and a recurrence with no end is bounded by it. Required when one operand has a year and the other none, but for the operations that place it as the next section says.

An operand with no year

With no window, an intersection/3, and a difference/3 from an operand with a year and an end, place an operand with no year (~o"{6,7}K", the weekend; ~o"12M25D"; ~o"T09/T17") on that operand's stretch, as :within would: their answer lies within it. A union/3, a symmetric_difference/3 and a difference/3 from the operand with no year have no first member and no last, and need a window.

An operand with no end

With no window, an operation on an operand with no end (a recurrence with no count, or a set with no last member) is as bounded as it makes its answer. An intersection/3, and a difference/3 from an operand that has an end, lie within that operand, and the other is taken within it. A union/3, a symmetric_difference/3 and a difference/3 from an operand with no end have no end themselves: the answer is a set with no last member, in the order of time, which :within bounds wherever it is asked for. Of two operands with no end only the union is answered; any other operation on them needs a window.

Returns

  • {:ok, set}, a t/0 of the :all form, with no last member where the answer has no end.

  • {:error, %Tempo.FrameError{}} when the operands share no frame: one zoned and one not, one with a year and one without and no window where the operation needs one, two with no year on different cycles.

  • {:error, %Tempo.ConversionError{}} for a duration, a one-of set, an open end, more than 10,000 spans (the application's :max_values_at_once), or two operands with no end in an operation other than a union.

Examples

iex> Tempo.union(~o"2026-01", ~o"2026-03")
{:ok, ~o"{2026-01/2026-01,2026-03/2026-03}"}

iex> {:ok, every_week_and_a_day} = Tempo.union(~o"R/2026-06-01/P1W", ~o"2026-06-10")
iex> Tempo.bounded?(every_week_and_a_day)
false
iex> Tempo.members(every_week_and_a_day, within: ~o"2026-06-01/2026-06-14")
{:ok, [~o"2026-06-01/2026-06-07", ~o"2026-06-08/2026-06-14", ~o"2026-06-10/2026-06-10"]}

iex> {:ok, both} = Tempo.union(~o"2026-01", ~o"2026-02")
iex> Tempo.IntervalSet.count(both)
2

iex> {:ok, quarter} = Tempo.union(~o"2026-01", [~o"2026-02", ~o"2026-03"])
iex> Tempo.IntervalSet.count(quarter)
3

union!(arg1, arg2, arg3 \\ [])

@spec union!(t(), t() | [t()], keyword()) :: t()

union/3, returning its answer or raising the error.

Arguments

  • The arguments are those of union/3.

Returns

  • What union/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.union!(~o"2026-01", ~o"2026-03") |> Tempo.IntervalSet.count()
2

utc_now()

@spec utc_now() :: t()

The current second in UTC.

Returns

  • The current second, a t/0 in Etc/UTC.

Examples

iex> Tempo.zoned?(Tempo.utc_now())
true

utc_today()

@spec utc_today() :: t()

Today's date in UTC.

Returns

  • Today's date in UTC, a t/0 at day resolution with no zone.

Examples

iex> Tempo.floating?(Tempo.utc_today())
true

walk(expression, unit, options \\ [])

@spec walk(t(), unit() | {:microsecond, 1..6}, keyword()) ::
  {:ok, Enumerable.t()} | {:error, error()}

An expression walked at a unit: the values of that unit its extent holds, lazily and in order, half-open. A calendar unit steps on the day scale through the calendar's periods; a clock unit on the tick scale, each tick shown as a wall reading of the zone, so a day across a change of the clock has 23 or 25 hours. A plural expression is each member walked in turn.

Arguments

  • expression is a t/0.

  • unit is a unit/0: :year, :month, :week, :day, :hour, :minute, :second, or {:microsecond, precision} for a part of a second.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

Returns

  • {:ok, stream}, an Enumerable of t/0 values.

  • {:error, %Tempo.ConversionError{}} for an expression with an open start, no year, or no spans.

Examples

iex> {:ok, days} = Tempo.walk(~o"2026-06", :day)
iex> Enum.count(days)
30

iex> {:ok, hours} = Tempo.walk(~o"2026-03-29[Europe/Paris]", :hour)
iex> Enum.count(hours)
23

iex> {:ok, weeks} = Tempo.walk(~o"2026-06-01/2026-06-30", :week)
iex> Enum.map(weeks, &Tempo.to_iso8601!/1)
["2026Y23W", "2026Y24W", "2026Y25W", "2026Y26W", "2026Y27W"]

walk!(arg1, arg2, arg3 \\ [])

@spec walk!(t(), unit() | {:microsecond, 1..6}, keyword()) :: Enumerable.t()

walk/3, returning its answer or raising the error.

Arguments

  • The arguments are those of walk/3.

Returns

  • What walk/3 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.walk!(~o"2026-06-15", :hour) |> Enum.count()
24

week(expression)

@spec week(t()) :: integer() | nil

The week an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no week or several.

Examples

iex> Tempo.week(~o"2026-W32")
32

iex> Tempo.week(~o"2026-06")
nil

week_start(other)

@spec week_start(t()) :: 1..7 | nil

The day an expression's weeks begin on, 1 (Monday) to 7 (Sunday): Monday unless it was written or built with another. Nil for an expression of a calendar that is not the Gregorian, whose weeks are its own.

Arguments

  • expression is a t/0.

Returns

  • The day, 1 to 7, or nil for a calendar whose weeks are its own.

Examples

iex> Tempo.week_start(~o"2026-W25")
1

iex> Tempo.week_start(~o"2026-W25[u-fw=sun]")
7

weekend?(value, territory \\ nil)

@spec weekend?(t(), term()) :: boolean()

Whether a value, a day or finer, falls on the weekend of a territory.

Arguments

Returns

  • true or false.

  • Raises a Tempo.ConversionError for a value that names several days and the error workdays/2 returns for a territory Localize does not know, as a predicate raises rather than answers an error that Enum.filter/2 would read as true.

Examples

iex> Tempo.weekend?(~o"2026-06-13", :US)
true

iex> Tempo.weekend?(~o"2026-06-15", :US)
false

weekends(territory \\ nil)

@spec weekends(term()) :: {:ok, t()} | {:error, error()}

The weekend of a territory as a selector: the days of the week its weekend falls on.

Arguments

  • territory is a territory code or nil, as for workdays/2.

Returns

  • {:ok, selector}, a t/0 naming the days of the week.

  • {:error, exception} for a territory Localize does not know.

Examples

iex> {:ok, weekends} = Tempo.weekends(:US)
iex> {:ok, days} = Tempo.select(~o"2026-06-15/2026-06-21", weekends)
iex> Tempo.IntervalSet.map(days, &Tempo.day/1)
[20, 21]

weekends!(arg1)

@spec weekends!(term()) :: t()

weekends/1, returning its answer or raising the error.

Arguments

Returns

  • What weekends/1 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> Tempo.weekends!(:US)
~o"{6,7}K"

within?(a, b, options \\ [])

@spec within?(t(), t(), keyword()) :: boolean()

Whether the first expression covers no instant outside the second. Both are read as the time they cover, so either may be plural; for two single spans this is a set of Allen's relations (Tempo.Allen.predicate/1).

Arguments

  • a and b are each a t/0 that has spans.

  • options is a keyword list of options.

Options

  • :within is a t/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.

An operand with no end

With no window, a recurrence with no count or a set with no last member is answered for where the answer follows from the ends: it is within nothing that has an end, equals it never and is before it never; whether it overlaps a span, contains it or begins after it is answered within that span. Two operands with no end need a window.

Returns

  • true or false; raises the error union/3 would return for operands that share no frame or have no spans, and a Tempo.ConversionError for two operands with no end and no window.

Examples

iex> Tempo.within?(~o"2026-01", ~o"2026-03")
false

iex> Tempo.within?(~o"2026-06", ~o"2026")
true

workday?(value, territory \\ nil)

@spec workday?(t(), term()) :: boolean()

Whether a value, a day or finer, falls on a workday of a territory: a weekday that is not a day off.

Arguments

Returns

  • true or false.

  • Raises a Tempo.ConversionError for a value that names several days and the error workdays/2 returns for a territory Localize does not know, as a predicate raises rather than answers an error that Enum.filter/2 would read as true.

Examples

iex> Tempo.workday?(~o"2026-06-13", :US)
false

iex> Tempo.workday?(~o"2026-06-15", :US)
true

workdays(territory \\ nil, options \\ [])

@spec workdays(term(), keyword()) :: {:ok, Tempo.Workdays.t()} | {:error, error()}

The workdays of a territory, less any days off: a selector for select/3 and the calendar the workday functions step through.

Arguments

  • territory is a territory code (:US, :SA, :IN), whose weekdays and weekend come from CLDR through Localize; nil is the configured :default_territory, or the world's when none is configured.

  • options is a keyword list of options.

Options

  • :except is a t/0 with spans, the days off: a set of dates, a recurrence, a holiday set.

Returns

  • {:ok, %Tempo.Workdays{}}.

  • {:error, %ArgumentError{}} for a territory Localize does not know, an option this function does not take, or days off that are not a t/0.

Examples

iex> {:ok, workdays} = Tempo.workdays(:SA)
iex> workdays.weekend
[5, 6]

iex> {:ok, school_days} = Tempo.workdays(:AU, except: ~o"{2026-01-26,2026-04-25}")
iex> Tempo.count_workdays(~o"2026-01-19/2026-02-01", school_days)
{:ok, 9}

workdays!(territory \\ nil, options \\ [])

@spec workdays!(term(), keyword()) :: Tempo.Workdays.t()

workdays/2, returning the workdays or raising: for a territory written in the code, as in a module attribute.

Arguments

Options

Returns

  • What workdays/2 returns as {:ok, answer}, the answer alone. The error it returns as {:error, exception} is raised.

Examples

iex> saudi_arabia = Tempo.workdays!(:SA)
iex> Tempo.workday?(~o"2026-06-19", saudi_arabia)
false

iex> observed = Tempo.workdays!(:US, except: ~o"2026-07-03")
iex> Tempo.workday?(~o"2026-07-03", observed)
false

year(expression)

@spec year(t()) :: integer() | nil

The year an expression names, as a number.

A unit the value is written in is read as written. One the value is not written in, but lies within one period of, is read through the calendar: the day of the month of a week date, the week of a date. An interval or a set answers when every end or member answers the same.

Arguments

  • expression is a t/0.

Returns

  • The number, or nil when the expression names no year or several.

Examples

iex> Tempo.year(~o"2026-06-15")
2026

iex> Tempo.year(~o"202XY")
nil

zoned?(expression)

@spec zoned?(t()) :: boolean()

Whether an expression carries a zone or a time shift, so a place on the universal time line. The complement of floating?/1.

Arguments

  • expression is a t/0.

Returns

  • true or false.

Examples

iex> Tempo.zoned?(~o"2024-01-01[Australia/Sydney]")
true

iex> Tempo.zoned?(~o"2024-01-01")
false