# `Tempo.Cron`
[🔗](https://github.com/kipcole9/tempo/blob/v0.20.0/lib/tempo/cron.ex#L1)

Parses cron expressions into recurring `t: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 an `UNTIL` limit; a
  year list or range (`2025,2027,2029`) becomes a `bymonthday`-style
  `byyear` filter 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` — every `S` starting from the field's minimum.

* `N-M/S` — every `S` within the range `N..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

* `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}]`.

* `W` as 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, and
  `1W` on 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.

# `parse`

```elixir
@spec parse(
  String.t(),
  keyword()
) :: {:ok, Tempo.Interval.t()} | {:error, Exception.t()}
```

Parse a cron expression into a recurring `t: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

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

### Options

* `:from` — a `t:Tempo.t/0` anchor for the recurrence. Optional;
  supply it to enumerate or materialise concrete occurrences.

### Returns

* `{:ok, interval}` — a recurring `t:Tempo.Interval.t/0`, materialised
  with `Tempo.to_interval/2` given a `:bound`.

* `{:error, exception}` — typically a `t:Tempo.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")

# `parse!`

```elixir
@spec parse!(
  String.t(),
  keyword()
) :: Tempo.Interval.t()
```

Raising version of `parse/2`.

### Examples

    iex> Tempo.Cron.parse!("@hourly").recurrence
    :infinity

---

*Consult [api-reference.md](api-reference.md) for complete listing*
