Parses cron expressions into recurring Tempo.Interval.t/0
values — the same first-class recurrence that Tempo.RRule.parse/2
and native ISO 8601 repeating intervals produce.
Lets Tempo consume any cron-configured schedule (Oban, Quantum,
system crontab) without rewriting it in another vocabulary. Once
parsed, materialise it like any other recurrence — Tempo.to_interval/2
with a :bound — or supply :from to anchor the occurrences.
Supported formats
5-field POSIX:
"minute hour day-of-month month day-of-week".6-field (seconds-first):
"second minute hour day-of-month month day-of-week". This is the variant used by Quantum and other Elixir schedulers.7-field (with year):
"second minute hour day-of-month month day-of-week year". A single concrete year is converted into anUNTILlimit; a year list or range (2025,2027,2029) becomes abymonthday-stylebyyearfilter bounded one past the last listed year.
Field grammar
Each field accepts:
*— every value in the field's range.N— a single integer.N,M,O— a list.N-M— an inclusive range.*/S— everySstarting from the field's minimum.N-M/S— everySwithin the rangeN..M.
Day-of-week accepts SUN–SAT (case-insensitive) as synonyms for
0–6. Sunday is both 0 and 7 (cron convention); internally
converted to RFC 5545's 7 (Sunday last). A day-of-week step
(N/S, */S) is expanded in cron numbering — Sunday = 0, the
week start — then mapped to RFC, so 0/3 is Sun, Wed, Sat and
*/2 is Sun, Tue, Thu, Sat. A bare-day step runs to the end of
the week and does not wrap (5/2 is Fri, Sun — not Tue).
Month accepts JAN–DEC (case-insensitive) as synonyms for 1–12.
Shortcut aliases
Standard cron aliases are supported:
| Alias | Expands to |
|---|---|
@yearly, @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily, @midnight | 0 0 * * * |
@hourly | 0 * * * * |
@reboot is not supported — it is a system-startup hook, not a
time expression.
Vixie-cron extensions
Las day-of-month — last day of the month →bymonthday: [-1].NLas day-of-week (e.g.5L) — last weekday-N of the month →byday: [{-1, N}].N#Kas day-of-week (e.g.5#2) — Kth weekday-N of the month →byday: [{K, N}].Was day-of-month (e.g.15W,LW) — the nearest weekday to that day, never crossing a month boundary →bymonthday_nearest. A Saturday snaps back to Friday, a Sunday forward to Monday, and1Won a weekend clamps forward into the same month. Only valid on a single day, never a list or range.
POSIX day-of-month OR day-of-week
When both dom and dow are restricted to plain lists (13 * 5 —
"the 13th or any Friday"), POSIX cron matches the union, not
the intersection. Tempo honours this via a daily cadence carrying a
bymonthday_or_byday union filter, so 13 * 5 fires on every 13th
and every Friday — not only Friday-the-13ths. A Quartz extension in
either field (an ordinal day-of-week 5#2/5L, or a nearest-weekday
15W) opts out and keeps the AND-composing interpretation.
Not supported (AST gaps)
@reboot— not a time expression.
Summary
Functions
@spec parse( String.t(), keyword() ) :: {:ok, Tempo.Interval.t()} | {:error, Exception.t()}
Parse a cron expression into a recurring Tempo.Interval.t/0.
A cron schedule, an RFC 5545 RRULE, and a native ISO 8601 repeating interval all become the same kind of first-class Tempo value, so a parsed cron entry composes and materialises exactly like any other recurrence — no intermediate rule struct to manage.
Arguments
expressionis a cron string (5, 6, or 7 fields, or an alias).
Options
:from— aTempo.t/0anchor for the recurrence. Optional; supply it to enumerate or materialise concrete occurrences.
Returns
{:ok, interval}— a recurringTempo.Interval.t/0, materialised withTempo.to_interval/2given a:bound.{:error, exception}— typically aTempo.CronError.t/0.
Examples
iex> {:ok, monthly} = Tempo.Cron.parse("0 0 15 * *", from: ~o"2025-01-15")
iex> {:ok, occurrences} = Tempo.to_interval(monthly, bound: ~o"2025")
iex> Tempo.IntervalSet.count(occurrences)
12
iex> {:error, %Tempo.CronError{}} = Tempo.Cron.parse("not a cron")
@spec parse!( String.t(), keyword() ) :: Tempo.Interval.t()
Raising version of parse/2.
Examples
iex> Tempo.Cron.parse!("@hourly").recurrence
:infinity