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.
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.
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.
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.
members_in_exactly_one/3, returning its answer or raising the error.
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
@type error() :: Exception.t()
The error every function returns in an {:error, reason} tuple.
@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 conditionsTempo.RecurrenceSet.keep_when/2andmove_when/2express.
@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.
@type qualifications() :: %{optional(unit()) => :uncertain | :approximate | :both} | nil
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.
@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.
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.
Arguments
valueis at/0within one day.countis the number of workdays to move, an integer.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
{:ok, workday}, at/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"}
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
Whether an expression has a year, so a place on the time line. A value
with an unspecified year (X*Y) has none.
Arguments
expressionis at/0.
Returns
trueorfalse.
Examples
iex> Tempo.anchored?(~o"2022")
true
iex> Tempo.anchored?(~o"2M")
false
iex> Tempo.anchored?(~o"X*Y2M")
false
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
valueandotherare each at/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}, at/0.{:error, exception}when both have a year, both hold a selection, either is not at/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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
at/2.
Returns
- What
at/2returns 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"
@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
expressionis at/0.durationis aTempo.Duration.t/0.
Returns
trueorfalse.{:error, exception}as forduration/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
@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
expressionis at/0.durationis aTempo.Duration.t/0.
Returns
trueorfalse.{:error, exception}as forduration/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
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
expressionis at/0.unitis a unit.
Returns
{:ok, expression}, or{:error, exception}as fortrunc/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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
at_resolution/2.
Returns
- What
at_resolution/2returns 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"
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
@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
expressionis at/0or aTempo.Duration.t/0.
Returns
trueorfalse. 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
@spec certainly?(Tempo.Allen.predicate() | Tempo.Allen.relation(), t(), t()) :: boolean()
Whether a predicate holds between two expressions for every reading of them.
Arguments
predicateis a predicate or relation, as forcertainty/3.aandbare each at/0.
Returns
truewhencertainty/3is:certain, otherwisefalse. The errorcertainty/3returns is raised.
Examples
iex> Tempo.certainly?(:before, ~o"2018±2Y", ~o"2021")
true
iex> Tempo.certainly?(:before, ~o"2018±2Y", ~o"2020")
false
@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
predicateis an everyday predicate (:before,:after,:adjacent,:within,:contains,:overlaps,:disjoint,:equal) or one of Allen's relations (Tempo.Allen.relation/0).aandbare each at/0.
Returns
:certain,:possibleor:impossible.{:error, %ArgumentError{}}for a predicate that is none ofTempo.Allen.predicates/0orTempo.Allen.relations/0, and{:error, exception}as forrelation/2.
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
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
aandbare each at/0on one frame.optionsis a keyword list of options.
Options
:withinis at/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.:byis 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
:lt,:eqor:gt; raises aTempo.FrameErrorfor two expressions that share no frame, and anArgumentErrorfor an option it does not take.
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"]
The complement of an expression within a window: every instant of the window not covered by it.
Arguments
ais at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/0, the window the complement is taken in. It is required.:metadatasays which member's metadata a result carries::first(the default),:second,:merge, or a function of the two.
Returns
{:ok, set}, at/0of the:allform.{:error, exception}as forunion/3, and aTempo.ConversionErrorwith:unboundedwhen no window is given.
Examples
iex> Tempo.complement(~o"2026-06", within: ~o"2026")
{:ok, ~o"{2026/2026-05,2026-07/2026}"}
complement/2, returning its answer or raising the error.
Arguments
- The arguments are those of
complement/2.
Returns
- What
complement/2returns 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}"
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
@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
Options
:withinis at/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 errorwalk/3returns.
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}
@spec count_workdays(t(), term()) :: {:ok, non_neg_integer()} | {:error, error()}
How many workdays an expression's days hold.
Arguments
expressionis at/0with an end: a value, an interval, a set.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
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}
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
expressionis at/0.
Returns
- The number, or
nilwhen the expression names no day or several.
Examples
iex> Tempo.day(~o"2026-06-15")
15
iex> Tempo.day(~o"2026-06")
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
expressionis at/0: a date, a week date, or an interval or set within one day.starting_onis the day numbered 1::default(the day the expression's weeks begin on), or a day named,:monday,:tuesday,:wednesday,:thursday,:friday,:saturdayor:sunday.
Returns
- The day of the week, or
nilwhen 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
@spec day_of_year(t()) :: pos_integer() | nil
The day of the year an expression names, counted from the year's first day.
Arguments
expressionis at/0.
Returns
- The day of the year, from 1;
nilwhen 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
@spec days_in_month(t()) :: {:ok, pos_integer()} | {:error, error()}
The number of days in the month an expression names, in its calendar.
Arguments
expressionis at/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}
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
ais at/0that has spans.bis at/0that has spans, or a list of them, taken away one after another: an empty list leavesaas it is.optionsis a keyword list of options.
Options
:withinis at/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 asunion/3says.:metadatasays which member's metadata a result carries::first(the default),:second,:merge, or a function of the two.
Returns
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/3, returning its answer or raising the error.
Arguments
- The arguments are those of
difference/3.
Returns
- What
difference/3returns 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
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
@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
expressionis at/0.
Returns
A
Tempo.Duration.t/0, or:infinityfor an open end or a recurrence with none.{:error, %Tempo.ConversionError{}}with reason:needs_a_yearfor an interval or set of dates with no year, whose length is its year's, and:bare_durationfor 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"
@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
fromandtoare each at/0with one span, on one frame,toafterfrom.Or
expression, at/0, andoptions, a keyword list.
Options
:leap_secondsistrueto count the leap seconds the expression spans (Tempo.LeapSeconds.spanned/1) into its length, a second for each. The default isfalse: a length on the clock, in which every minute is sixty seconds.
Returns
{:ok, duration}for two values.A
Tempo.Duration.t/0,:infinityor{:error, exception}for an expression and options, asduration/1returns them.{:error, %Tempo.IntervalEndpointsError{}}whentois not afterfrom, 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"
@spec duration!(t(), t()) :: Tempo.Duration.t()
duration/2, returning its answer or raising the error.
Arguments
- The arguments are those of
duration/2.
Returns
- What
duration/2returns 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"
Whether an expression covers no time: a set with no members.
Arguments
expressionis at/0.
Returns
trueorfalse.
Examples
iex> Tempo.empty?(~o"{}")
true
iex> Tempo.empty?(~o"2026-06-15")
false
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
@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
cadenceis the length of each repeat, aTempo.Duration.t/0or its units:~o"P1M", or[month: 1].optionsis a keyword list of options.
Options
:fromis where the recurrence starts, at/0value. 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:withinwindow.:tois where a recurrence counted back ends, at/0value: its last repeat ends with it (R5/P1M/2026-06-01). A recurrence starts at:fromor ends at:to, not both.:countis how many occurrences it has.:untilis at/0value it runs to. A recurrence ends by one of:countand:until, or has no end.:selectis what it selects in each repeat: the parts of a selection, or whatselection/1returns. 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: :fridayis6Kin the Hebrew calendar, whose weeks begin on Sunday.:calendaris the calendar of a recurrence with no:from(R/../P1Y/FL7M1DN[u-ca=hebrew]). One with a:fromis in its start's.:yearskeeps the years of its domain that are:even,:odd,:leapor:common: of every year where there is no:from(R/..e), or of the set given as:from(R/{2017Y..}l).:metadatais a map the recurrence and every occurrence carry.
Returns
{:ok, recurrence}, at/0of the:everyform, in the calendar and the zone of its start.{:error, exception}as forselection/1, and anArgumentErrorfor 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")
@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/2returns 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"
@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
expressionis at/0.durationis aTempo.Duration.t/0.
Returns
trueorfalse.{:error, exception}as forduration/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
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
expressionis at/0or aTempo.Duration.t/0.
Returns
- A string of several lines;
Tempo.Explain.lines/1gives them one by one.
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."]
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
expressionis at/0.unitis the unit to extend to, ornilfor 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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
extend/1.
Returns
- What
extend/1returns 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"
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
expressionis at/0.
Returns
trueorfalse.
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
A Tempo value from a Date, as from_elixir/2 reads one.
Arguments
valueis aDate.t/0.
Returns
- The value, a
t/0. Anything but aDateraises aTempo.ConversionError.
Examples
iex> Tempo.from_date(~D[2022-06-15])
~o"2022-06-15"
@spec from_datetime(DateTime.t()) :: t()
A Tempo value from a DateTime, as from_elixir/2 reads one.
Arguments
valueis aDateTime.t/0.
Returns
- The value, a
t/0. Anything but aDateTimeraises aTempo.ConversionError.
Examples
iex> Tempo.from_datetime(~U[2022-06-15 10:30:00Z])
~o"2022-06-15T10:30:00Z"
@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
valueis aDate.t/0,Time.t/0,NaiveDateTime.t/0,DateTime.t/0,Date.Range.t/0orDuration.t/0.optionsis a keyword list of options.
Options
:resolutionis a unit the value is written at, asat_resolution/2writes 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"}
@spec from_elixir!(term(), keyword()) :: t() | Tempo.Duration.t()
from_elixir/2, returning the value or raising.
Arguments
- The arguments are those of
from_elixir/2.
Options
- The options of
from_elixir/2.
Returns
- What
from_elixir/2returns 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"
@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
textis the text to read.calendar_or_optionsis the calendar module a text that names none is read in, or a keyword list of options. The default isCalendar.ISO.
Options
:calendaris the calendar a text that names none is in. A text that names its calendar ([u-ca=hebrew]) is read in that one.:profileis the kind of text the caller declares it to be::date,:time,:datetime,:intervalor:duration. A text of another kind is an error, where without a profile it would be read as the kind it is.:territoryis the territory a season of 21 to 24 is given its months in as it is read, asin_territory/2takes it; with none, and no:locale, a season is read as its code.:localeis a locale whose territory is used as:territoryis.:overflowis: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,2018Y13Mis January 2019 and2020Y2M59Dis 30 March 2020. With:refusesuch a value is aTempo.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
{:ok, expression}, at/0, or aTempo.Duration.t/0for a duration.{:error, exception}when the text cannot be read (Tempo.ParseError), names a date the calendar does not have (Tempo.InvalidDateError), a zone the database does not know (Tempo.ZoneError), or an interval whose ends are out of order (Tempo.IntervalEndpointsError).
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")
@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
- The arguments are those of
from_iso8601/2.
Options
- The options of
from_iso8601/2.
Returns
- What
from_iso8601/2returns 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"
@spec from_naive_datetime(NaiveDateTime.t()) :: t()
A Tempo value from a NaiveDateTime, as from_elixir/2 reads one.
Arguments
valueis aNaiveDateTime.t/0.
Returns
- The value, a
t/0. Anything but aNaiveDateTimeraises aTempo.ConversionError.
Examples
iex> Tempo.from_naive_datetime(~N[2022-06-15 10:30:00])
~o"2022-06-15T10:30:00"
A Tempo value from a Time, as from_elixir/2 reads one.
Arguments
valueis aTime.t/0.
Returns
- The value, a
t/0. Anything but aTimeraises aTempo.ConversionError.
Examples
iex> Tempo.from_time(~T[10:30:00])
~o"T10:30:00"
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
expressionis at/0.
Returns
- The number, or
nilwhen 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
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
expressionis at/0: a season, an interval with a season at an end, or a set of them.territoryis a territory (:AU,"GB") or a locale whose territory is taken ("en-AU"), asTempo.Territory.resolve/1reads 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/2, returning the expression or raising.
Arguments
- The arguments are those of
in_territory/2.
Returns
- What
in_territory/2returns 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"
A floating expression placed in a time zone, its readings unchanged: the
runtime form of writing [zone] after it.
Arguments
expressionis at/0with no zone and no shift.zoneis 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/2moves 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
in_zone/2.
Returns
- What
in_zone/2returns 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]"
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
ais at/0that has spans.bis at/0that has spans, or a list of them, intersected with each in turn: an empty list leavesaas it is.optionsis a keyword list of options.
Options
:withinis at/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 asunion/3says.:metadatasays which member's metadata a result carries::first(the default),:second,:merge, or a function of the two.
Returns
{:ok, set}, at/0of the:allform, empty when nothing is shared.{:error, exception}as forunion/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/3, returning its answer or raising the error.
Arguments
- The arguments are those of
intersection/3.
Returns
- What
intersection/3returns 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
Whether the year an expression names is a leap year of its calendar.
Arguments
expressionis at/0.
Returns
trueorfalse.{: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
@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
expressionis at/0.durationis aTempo.Duration.t/0.
Returns
trueorfalse.{:error, exception}as forduration/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
try_map/2, returning the set or raising.
Arguments
- The arguments are those of
try_map/2.
Returns
- What
try_map/2returns 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
@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
expressionis at/0.optionsis a keyword list of options.
Options
:withinis at/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.:localeis 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 oft/0.{:error, %Tempo.ConversionError{}}as forto_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"]}
@spec members!(t() | Tempo.Duration.t(), keyword()) :: [t()]
members/2, returning its answer or raising the error.
Arguments
- The arguments are those of
members/2.
Returns
- What
members/2returns 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"]
The members of either expression no instant of which is in the other.
Arguments
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.
Returns
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/3, returning its answer or raising the error.
Arguments
- The arguments are those of
members_in_exactly_one/3.
Returns
- What
members_in_exactly_one/3returns 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}"
The members of the first expression no instant of which is in the second, each kept whole with its metadata.
Arguments
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.
Returns
Examples
iex> Tempo.members_outside(~o"{2026-01,2026-03}", ~o"2026-03-15")
{:ok, ~o"{2026-01/2026-01}"}
members_outside/3, returning its answer or raising the error.
Arguments
- The arguments are those of
members_outside/3.
Returns
- What
members_outside/3returns 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}"
The members of the first expression some instant of which is in the second, each kept whole with its metadata.
Arguments
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/0, the window an expression with no end of its own (a recurrence with no count) or no year is placed in.
Returns
Examples
iex> Tempo.members_overlapping(~o"{2026-01,2026-03}", ~o"2026-03-15")
{:ok, ~o"{2026-03/2026-03}"}
members_overlapping/3, returning its answer or raising the error.
Arguments
- The arguments are those of
members_overlapping/3.
Returns
- What
members_overlapping/3returns 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}"
The metadata an expression carries: a map, empty by default.
Arguments
expressionis at/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"}
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
expressionis at/0.
Returns
- The number, or
nilwhen 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
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
expressionis at/0.
Returns
- The number, or
nilwhen the expression names no month or several.
Examples
iex> Tempo.month(~o"2026-06-15")
6
iex> Tempo.month(~o"2026")
nil
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
valueis at/0within one day.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
{:ok, workday}, at/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"}
Builds a value from its units.
Arguments
componentsis 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:microsecondholds{microseconds, digits}as Elixir'sTimedoes, or an integer, which is six digits. A:day_of_weekis 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::calendaris the calendar the value is in. The default isCalendar.ISO.:zoneis the name of the value's time zone, as the database knows it.:shiftis the time shift,[hour: 2]forZ2H,[hour: 0]forZ, and[hour: 0, minute: 0]for+00:00, a local time at no offset.:qualificationsmarks units of the value as ISO 8601-2's?,~and%do: a map of the unit to:uncertain,:approximateor:both, so%{year: :uncertain}is2026?Y.:metadatais a map the caller keeps on the value. The default is%{}.
Returns
{:ok, value}, at/0whose units are ordered coarsest first.{:error, exception}when a unit is not one a value has or two units name the same thing (anArgumentError), when the calendar does not have the date (Tempo.InvalidDateError), when the calendar is not one (Tempo.InvalidCalendarError), or when the zone is not known (Tempo.ZoneError).
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"
Builds a value as new/1 does, returning the value or raising.
Arguments
- The arguments are those of
new/1.
Returns
- What
new/1returns 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"
The first workday after a value.
Arguments
valueis at/0within one day.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
{:ok, workday}, at/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"}
The current second in a zone, from the configured Tempo.Clock.
Arguments
zoneis an IANA zone name; the default is"Etc/UTC".
Returns
{:ok, value}, at/0at 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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
now/1.
Returns
- What
now/1returns as{:ok, answer}, the answer alone. The error it returns as{:error, exception}is raised.
Examples
iex> Tempo.now!("Europe/London") |> Tempo.resolution()
:second
at/2 with its arguments the other way round, so that the code reads as
English does: 10:30 on 4 January.
Arguments
Returns
- What
at(other, value)returns.
Examples
iex> Tempo.on(~o"T10:30", ~o"2026-01-04")
{:ok, ~o"2026-01-04T10:30"}
on/2, returning its answer or raising the error.
Arguments
- The arguments are those of
on/2.
Returns
- What
on/2returns 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"
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
membersis a list oft/0, in one calendar and one zone.
Returns
{:ok, set}, at/0of the:oneform.{: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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
one_of/1.
Returns
- What
one_of/1returns 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]"
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
@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
textis the text.optionsis a keyword list of options.
Options
:localeis 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 forfrom_iso8601/2.:territoryis as forfrom_iso8601/2.:calendaris the calendar the text is read in.:profileis as forfrom_iso8601/2, for the ISO 8601 reading.:reference_dateis 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")
@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
- The options of
parse/2.
Returns
- What
parse/2returns 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"
@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
textis a string.
Returns
{:ok, duration}, aTempo.Duration.t/0.{:error, %Tempo.ParseError{}}for any other text.
Examples
iex> Tempo.parse_duration("PT1H")
{:ok, ~o"PT1H"}
iex> {:error, %Tempo.ParseError{}} = Tempo.parse_duration("2026-06-15")
@spec parse_duration!(String.t()) :: Tempo.Duration.t()
parse_duration/1, returning its answer or raising the error.
Arguments
- The arguments are those of
parse_duration/1.
Returns
- What
parse_duration/1returns as{:ok, answer}, the answer alone. The error it returns as{:error, exception}is raised.
Examples
iex> Tempo.parse_duration!("P1DT2H")
~o"P1DT2H"
@spec possibly?(Tempo.Allen.predicate() | Tempo.Allen.relation(), t(), t()) :: boolean()
Whether a predicate holds between two expressions for some reading of them.
Arguments
predicateis a predicate or relation, as forcertainty/3.aandbare each at/0.
Returns
truewhencertainty/3is:certainor:possible, otherwisefalse. The errorcertainty/3returns is raised.
Examples
iex> Tempo.possibly?(:overlaps, ~o"2018±2Y", ~o"2020")
true
iex> Tempo.possibly?(:overlaps, ~o"2018±2Y", ~o"2030")
false
The last workday before a value.
Arguments
valueis at/0within one day.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
{:ok, workday}, at/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"}
An expression with metadata merged into what it carries.
Arguments
expressionis at/0.metadatais 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"}
@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
valueis at/0.
Returns
- A map from unit to
:uncertain,:approximateor:both;nilwhen the value has none.
Examples
iex> Tempo.qualification(~o"2026-06~-15")
%{year: :approximate, month: :approximate}
iex> Tempo.qualification(~o"2026-06-15")
nil
The qualification of one unit of a value: whether it is uncertain (?),
approximate (~) or both (%).
Arguments
valueis at/0.unitis a unit, such as:yearor:month.
Returns
:uncertain,:approximateor:both;nilwhen the unit is neither.
Examples
iex> Tempo.qualification(~o"2026-06~-15", :month)
:approximate
iex> Tempo.qualification(~o"2026-06~-15", :day)
nil
@spec quarter_of_year(t()) :: 1..4 | nil
The quarter of the year an expression lies in, 1 to 4.
Arguments
expressionis at/0.
Returns
- The quarter, 1 to 4;
nilwhen 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
@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
aandbare each at/0with 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
@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
expressionis at/0or aTempo.Duration.t/0.
Returns
- The unit, an atom;
nilfor 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
A value rolled to a workday by a business day convention: itself when it is a workday, else the workday the convention names.
Arguments
valueis at/0within one day.territoryis as forworkdays/2.optionsis a keyword list of options.
Options
:rollis:following(the default),:preceding,:nearest(the closer of the two, the one after on a tie), or one of:modified_following,:modified_precedingand:modified_nearest; the modified conventions turn back when the roll would leave the month.
Returns
{:ok, workday}, at/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"}
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
expressionis at/0that is one value, or an interval or set of them, rounded end by end and member by member.unitis 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:severalwhen 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
round/2.
Returns
- What
round/2returns 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"
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
expressionis at/0.
Returns
- The number, or
nilwhen the expression names no second or several.
Examples
iex> Tempo.second(~o"T10:30:45")
45
iex> Tempo.second(~o"T10:30")
nil
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
baseis at/0with spans: a value, an interval, a set, a bounded recurrence. Each span is a period the selector is applied in.selectoris one of: at/0with 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; aTempo.Workdays.t/0fromworkdays/2, orweekends/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}.optionsis a keyword list of options.
Options
:withinis at/0, the window a base with no end of its own or no year is placed in.:localeis 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}, at/0of the:allform 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/3, returning the set or raising.
Arguments
- The arguments are those of
select/3.
Options
- The options of
select/3.
Returns
- What
select/3returns 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
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
partsis a keyword list of the parts, in any order, each described under Parts.optionsis a keyword list of options.
Options
:calendaris the calendar the selection is in, whose weeks number its days of the week and count a part from their end:day_of_week: -1is the last day of a week of that calendar. The default isCalendar.ISO.
Parts
:month,:week,:day_of_year,:day,:day_of_week,:hour,:minuteand:secondare each a whole number, a range, or a list of numbers and ranges. A negative number counts from the end of the period:day: -1is its last day.:day_of_weekis numbered from the day the week begins on, Monday 1 in the Gregorian calendar, or is a day's name (:mondayto: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.:instancepicks among everything the other parts select, by position:2the second,-1the last.:eventis the name of a day a calendar computes, one ofTempo.Event.known/0:"easter".:traditional_monthis a month as a lunisolar calendar names it: its number, or{5, :leap}for the leap month after the fifth. It is written5mand5+m.:skipis:forwardor: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.:rollmoves each day the other parts select to a weekday, by one of the conventions ofroll_to_workday/3::modified_nearestis 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.:selectionis 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.:windowis aTempo.Duration.t/0, the length of what the nested:selectionselects:~o"P1D"for the day after,~o"-P7D"for the week before.:allis 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, writtenL{1K2I,3K2I}N: ISO 8601-2 has one rule of a kind in a selection.
Returns
{:ok, selection}, at/0with no year.{:error, exception}: anArgumentErrorfor a part a selection does not have, and aTempo.InvalidDateErrorfor an event no calendar knows.
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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
selection/2.
Returns
- What
selection/2returns 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"
@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
expressionis at/0.byis aTempo.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.optionsis a keyword list of options, which may ride in the keyword form ofby.
Options
:skippingis a busy set the shift jumps over: at/0with 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_yearwhen the answer depends on a year the value has not,:groupwhen the value ends in a group,{:inexact, unit}when:skippingis given a month or a year, and:selectionwhen the value holds a selection; and{:error, exception}whenbyis 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"}
@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/2returns 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"
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
expressionis at/0with a zone or a shift.zoneis a time zone name the database knows.
Returns
{:ok, moved}, at/0inzone.{:error, %Tempo.FrameError{}}when the expression is floating (in_zone/2places 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
shift_zone/2.
Returns
- What
shift_zone/2returns 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]"
@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
expressionis at/0.durationis aTempo.Duration.t/0.
Returns
trueorfalse.{:error, exception}as forduration/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
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
valueis at/0that is one value.
Returns
{date, time}, eithernilwhen 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}
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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 asunion/3says.:metadatasays which member's metadata a result carries::first(the default),:second,:merge, or a function of the two.
Returns
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/3, returning its answer or raising the error.
Arguments
- The arguments are those of
symmetric_difference/3.
Returns
- What
symmetric_difference/3returns 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}"
@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
- The module of the
Calendar.TimeZoneDatabasebehaviour that Tempo asks.
Examples
iex> Tempo.time_zone_database()
Tz.TimeZoneDatabase
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
expressionis at/0that has spans.calendaris a calendar module.
Returns
{:ok, converted}, at/0incalendar.{:error, %Tempo.InvalidCalendarError{}}whencalendaris not one,{:error, %Tempo.ConversionError{reason: :needs_a_year}}for an expression with no year that names a date, or the errorto_interval/2gives 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
to_calendar/2.
Returns
- What
to_calendar/2returns 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]"
The Date of a value written to the day or finer, on any axis.
Arguments
valueis at/0.
Returns
{:ok, date}, aDate.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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
to_date/1.
Returns
- What
to_date/1returns 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]
@spec to_datetime(t()) :: {:ok, DateTime.t()} | {:error, error()}
The DateTime of a value with a zone or a time shift.
Arguments
valueis at/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")
@spec to_datetime!(t()) :: DateTime.t()
to_datetime/1, returning its answer or raising the error.
Arguments
- The arguments are those of
to_datetime/1.
Returns
- What
to_datetime/1returns 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]
@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
valueis at/0or aTempo.Duration.t/0.
Returns
{:ok, struct}, or{:ok, range}with aDate.Range.t/0for 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")
@spec to_elixir!(t() | Tempo.Duration.t()) :: struct()
to_elixir/1, returning its answer or raising the error.
Arguments
- The arguments are those of
to_elixir/1.
Returns
- What
to_elixir/1returns 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]
@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
expressionis at/0.optionsis a keyword list of options.
Options
:withinis 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.:territoryis the territory a season of 21 to 24 has its months in, asin_territory/2takes it. With none, and no:locale, such a season keeps its code and has its dates in the territory in force.:localeis a locale whose territory is used as:territoryis, and whose working week a rule that rolls a day to a weekday keeps to.
Returns
{:ok, interval}, at/0of the:spanform, or of the:allform 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/2, returning the interval or set or raising.
Arguments
- The arguments are those of
to_interval/2.
Options
- The options of
to_interval/2.
Returns
- What
to_interval/2returns 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"
@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
expressionis at/0or aTempo.Duration.t/0.
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"}
@spec to_iso8601!(t() | Tempo.Duration.t()) :: String.t()
Writes an expression as to_iso8601/1 does, returning the text or raising.
Arguments
- The arguments are those of
to_iso8601/1.
Returns
- What
to_iso8601/1returns as{:ok, answer}, the answer alone. The error it returns as{:error, exception}is raised.
Examples
iex> Tempo.to_iso8601!(~o"P1Y2M")
"P1Y2M"
@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
valueis at/0.
Returns
{:ok, datetime}, aNaiveDateTime.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]}
@spec to_naive_datetime!(t()) :: NaiveDateTime.t()
to_naive_datetime/1, returning its answer or raising the error.
Arguments
- The arguments are those of
to_naive_datetime/1.
Returns
- What
to_naive_datetime/1returns 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]
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
expressionis at/0with a year.optionsis a keyword list of options.
Options
:fromis thet/0the words are relative to; the default isutc_now/0.:unitis the unit counted in::year,:quarter,:month,:week,:day,:hour,:minuteor:second.:formatis:standard(the default),:shortor:narrow, and:localea CLDR locale.:numericis: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:openwhen 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
to_relative_string/2.
Returns
- What
to_relative_string/2returns 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"
@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
expressionis at/0or aTempo.Duration.t/0.optionsis a keyword list of options.
Options
:localeis a CLDR locale; the default is Localize's.:formatis a CLDR format (:short,:medium,:long,:full) or a skeleton (:yMMMd).:withinis at/0, the window an expression with no year or no end is placed in.:number_systemand:preferare Localize's: the digits a number is written in, and which of a locale's variants of a format is taken.:exceptis a list of the units a duration's words leave out.
Returns
{:ok, words}.{:error, exception}: aTempo.ConversionErrorwith reason:openfor an interval with an open end and:needs_a_yearfor an expression with no year that the units alone cannot show, anArgumentErrorfor 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/..")
@spec to_string!(t() | Tempo.Duration.t(), keyword()) :: String.t()
to_string/2, returning the words or raising.
Arguments
- The arguments are those of
to_string/2.
Options
- The options of
to_string/2.
Returns
- What
to_string/2returns 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"
The Time of a value with a time of day; units it does not write are zero.
Arguments
valueis at/0.
Returns
{:ok, time}, aTime.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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
to_time/1.
Returns
- What
to_time/1returns 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]
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
expressionis at/0in the Gregorian calendar.dayis 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
to_week_start/2.
Returns
- What
to_week_start/2returns 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's date in a zone: a floating day.
Arguments
zoneis an IANA zone name; the default is"Etc/UTC".
Returns
{:ok, value}, at/0at 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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
today/1.
Returns
- What
today/1returns as{:ok, answer}, the answer alone. The error it returns as{:error, exception}is raised.
Examples
iex> Tempo.today!("Australia/Sydney") |> Tempo.resolution()
:day
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
expressionis at/0.unitis 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/2, returning its answer or raising the error.
Arguments
- The arguments are those of
trunc/2.
Returns
- What
trunc/2returns 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"
@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
enumerableis an enumerable oft/0: a list, a set, a value walked.funis a function of one member.
Returns
{:ok, set}, at/0of the:allform holding the results in order.{:error, exception}, the first error the function answers.{:error, %ArgumentError{}}whenenumerableis not enumerable orfunis 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))
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
ais at/0that has spans: a value, an interval, a recurrence, a set.bis at/0that has spans, or a list of them, each united withain turn: an empty list leavesaas it is.optionsis a keyword list of options.
Options
:withinis at/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}, at/0of the:allform, 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/3, returning its answer or raising the error.
Arguments
- The arguments are those of
union/3.
Returns
- What
union/3returns 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
@spec utc_now() :: t()
The current second in UTC.
Returns
- The current second, a
t/0inEtc/UTC.
Examples
iex> Tempo.zoned?(Tempo.utc_now())
true
@spec utc_today() :: t()
Today's date in UTC.
Returns
- Today's date in UTC, a
t/0at day resolution with no zone.
Examples
iex> Tempo.floating?(Tempo.utc_today())
true
@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
expressionis at/0.unitis aunit/0::year,:month,:week,:day,:hour,:minute,:second, or{:microsecond, precision}for a part of a second.optionsis a keyword list of options.
Options
:withinis at/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}, anEnumerableoft/0values.{: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"]
@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/3returns 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
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
expressionis at/0.
Returns
- The number, or
nilwhen the expression names no week or several.
Examples
iex> Tempo.week(~o"2026-W32")
32
iex> Tempo.week(~o"2026-06")
nil
@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
expressionis at/0.
Returns
- The day, 1 to 7, or
nilfor 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
Whether a value, a day or finer, falls on the weekend of a territory.
Arguments
valueis at/0within one day.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
trueorfalse.Raises a
Tempo.ConversionErrorfor a value that names several days and the errorworkdays/2returns for a territory Localize does not know, as a predicate raises rather than answers an error thatEnum.filter/2would read as true.
Examples
iex> Tempo.weekend?(~o"2026-06-13", :US)
true
iex> Tempo.weekend?(~o"2026-06-15", :US)
false
The weekend of a territory as a selector: the days of the week its weekend falls on.
Arguments
territoryis a territory code ornil, as forworkdays/2.
Returns
{:ok, selector}, at/0naming 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/1, returning its answer or raising the error.
Arguments
- The arguments are those of
weekends/1.
Returns
- What
weekends/1returns as{:ok, answer}, the answer alone. The error it returns as{:error, exception}is raised.
Examples
iex> Tempo.weekends!(:US)
~o"{6,7}K"
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
aandbare each at/0that has spans.optionsis a keyword list of options.
Options
:withinis at/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
trueorfalse; raises the errorunion/3would return for operands that share no frame or have no spans, and aTempo.ConversionErrorfor 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
Whether a value, a day or finer, falls on a workday of a territory: a weekday that is not a day off.
Arguments
valueis at/0within one day.territoryis a territory code, aTempo.Workdays.t/0ornil, as forworkdays/2.
Returns
trueorfalse.Raises a
Tempo.ConversionErrorfor a value that names several days and the errorworkdays/2returns for a territory Localize does not know, as a predicate raises rather than answers an error thatEnum.filter/2would read as true.
Examples
iex> Tempo.workday?(~o"2026-06-13", :US)
false
iex> Tempo.workday?(~o"2026-06-15", :US)
true
@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
territoryis a territory code (:US,:SA,:IN), whose weekdays and weekend come from CLDR through Localize;nilis the configured:default_territory, or the world's when none is configured.optionsis a keyword list of options.
Options
:exceptis at/0with 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 at/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}
@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
- The arguments are those of
workdays/2.
Options
- The options of
workdays/2.
Returns
- What
workdays/2returns 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
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
expressionis at/0.
Returns
- The number, or
nilwhen the expression names no year or several.
Examples
iex> Tempo.year(~o"2026-06-15")
2026
iex> Tempo.year(~o"202XY")
nil
Whether an expression carries a zone or a time shift, so a place on the
universal time line. The complement of floating?/1.
Arguments
expressionis at/0.
Returns
trueorfalse.
Examples
iex> Tempo.zoned?(~o"2024-01-01[Australia/Sydney]")
true
iex> Tempo.zoned?(~o"2024-01-01")
false