Tempo.Cron (Tempo v0.5.0)

Copy Markdown View Source

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 an UNTIL limit; 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 — every S starting from the field's minimum.

  • N-M/S — every S within the range N..M.

Day-of-week accepts SUNSAT (case-insensitive) as synonyms for 06. Sunday is both 0 and 7 (cron convention); internally converted to RFC 5545's 7 (Sunday last).

Month accepts JANDEC (case-insensitive) as synonyms for 112.

Shortcut aliases

Standard cron aliases are supported:

AliasExpands to
@yearly, @annually0 0 1 1 *
@monthly0 0 1 * *
@weekly0 0 * * 0
@daily, @midnight0 0 * * *
@hourly0 * * * *

@reboot is not supported — it is a system-startup hook, not a time expression.

Vixie-cron extensions

  • L as day-of-month — last day of the month → bymonthday: [-1].

  • NL as day-of-week (e.g. 5L) — last weekday-N of the month → byday: [{-1, N}].

  • N#K as 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. 15W meaning "nearest weekday to the 15th". RFC 5545 has no equivalent and the Tempo.RRule.Rule AST does not model this pattern. Parsing W returns {:error, %Tempo.CronError{reason: :unsupported_w}}.

  • Year lists — e.g. "0 0 1 1 * 2025,2027,2029". A single concrete year becomes until; multi-year lists have no direct AST field (byyear does not exist) and the year constraint is effectively dropped. The caller should use a :bound at the materialisation site instead.

  • POSIX day-of-month vs day-of-week OR semantics — when both dom and dow are 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 a bysetpos-style predicate approximation. Exact OR semantics would require a union operator in the AST.

  • @reboot — not a time expression.

Summary

Functions

Parse a cron expression into a Tempo.RRule.Rule.t/0.

Raising version of parse/1.

Functions

parse(expression)

@spec parse(String.t()) :: {:ok, Tempo.RRule.Rule.t()} | {:error, Exception.t()}

Parse a cron expression into a Tempo.RRule.Rule.t/0.

Arguments

  • expression is a cron string (5, 6, or 7 fields, or an alias).

Returns

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")

parse!(expression)

@spec parse!(String.t()) :: Tempo.RRule.Rule.t()

Raising version of parse/1.

Examples

iex> Tempo.Cron.parse!("@hourly").freq
:hour