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; year lists or ranges are left as expansion-bound limits (see the "not supported" section).
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).
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}].
Not supported (AST gaps)
W(nearest weekday) — e.g.15Wmeaning "nearest weekday to the 15th". RFC 5545 has no equivalent and theTempo.RRule.RuleAST does not model this pattern. ParsingWreturns{:error, %Tempo.CronError{reason: :unsupported_w}}.Year lists — e.g.
"0 0 1 1 * 2025,2027,2029". A single concrete year becomesuntil; multi-year lists have no direct AST field (byyeardoes not exist) and the year constraint is effectively dropped. The caller should use a:boundat the materialisation site instead.POSIX day-of-month vs day-of-week OR semantics — when both
domanddoware non-*, POSIX cron matches the union (either condition true). The AST AND-composes BY rules, so in this case the dow list is used and the dom list is folded into abysetpos-style predicate approximation. Exact OR semantics would require a union operator in the AST.@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