Parser for cron expressions, producing the same
Tempo.RRule.Rule.t/0 AST that the ISO 8601 / RFC 5545 RRULE
parser produces.
Lets Tempo consume any cron-configured schedule (Oban, Quantum,
system crontab) without rewriting the rule in another
vocabulary. Once parsed, the rule can be materialised the same
way any RRULE can — Tempo.RRule.Expander.expand/2,3 or
Tempo.to_interval/2 with a :bound.
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()) :: {:ok, Tempo.RRule.Rule.t()} | {:error, Exception.t()}
Parse a cron expression into a Tempo.RRule.Rule.t/0.
Arguments
expressionis a cron string (5, 6, or 7 fields, or an alias).
Returns
{:ok, rule}whereruleis aTempo.RRule.Rule.t/0.{:error, exception}— typically aTempo.CronError.t/0.
Examples
iex> {:ok, rule} = Tempo.Cron.parse("0 9 * * 1-5")
iex> rule.freq
:week
iex> rule.byhour
[9]
iex> rule.byminute
[0]
iex> rule.byday
[{nil, 1}, {nil, 2}, {nil, 3}, {nil, 4}, {nil, 5}]
iex> {:ok, rule} = Tempo.Cron.parse("@daily")
iex> rule.freq
:day
iex> {rule.byhour, rule.byminute}
{[0], [0]}
iex> {:ok, rule} = Tempo.Cron.parse("*/15 * * * *")
iex> {rule.freq, rule.interval}
{:minute, 15}
iex> {:ok, rule} = Tempo.Cron.parse("* * * * *")
iex> {rule.freq, rule.interval}
{:minute, 1}
iex> {:error, %Tempo.CronError{}} = Tempo.Cron.parse("not a cron")
@spec parse!(String.t()) :: Tempo.RRule.Rule.t()
Raising version of parse/1.
Examples
iex> Tempo.Cron.parse!("@hourly").freq
:hour