A blazing fast no-std, no-alloc cron expression parser, with multiple dialects support.
Installation
[]
= "0.2"
What it does
Parses a cron expression into a fixed-size value that never allocates, and answers whether a caller-supplied instant matches it. No clock, no calendar arithmetic, and nothing behind a feature on the default path.
The dialect is a type parameter, not a runtime tag, because the dialects disagree about what a stored schedule means and not merely about what text they accept:
use ;
Dialects
| fields | Sunday is | both day fields restricted | extras | |
|---|---|---|---|---|
Vixie |
5 | 0, and 7 too |
union | nicknames, @reboot |
Quartz |
6 or 7 | 1 |
rejected; one must be ? |
L, LW, L-n, nW, n#m |
Robfig |
6 | 0 |
union | nicknames, @every <duration> |
The same digit means different days in the first two, so a crontab written with digits is not portable between them and one written with names is.
The year range is on the type
Schedule<D> is Schedule<D, 1>, whose years are one u128 over a 1970 epoch:
1970..=2097. That is short of both reference implementations on purpose — the range is
something the type states rather than something this crate quietly decided — and a year
beyond it is rejected by name:
year field at 12..16: 2098 is legal cron but this schedule represents only up to 2097;
instantiate it with N = 2
Benchmarks
Parse only — text in, a schedule out, dropped inside the timed region. No matching, no
next-occurrence computation. Criterion, aarch64-apple-darwin (M4 Pro), stable 1.97.1,
against saffron 0.1, cron 0.17 and croner 3.
A library appears in a row only where it accepts that shape. saffron is five-field
only, and cron does not take a bare five-field expression, so neither is in every row —
timing a parse against a rejection would compare a built schedule with an error return.
Bold is the fastest cell in the row, not cronp's column.
| cronp | saffron | cron | croner | |
|---|---|---|---|---|
5-field 30 2 * * 1-5 |
35.1 ns | 39.2 ns | — | 8.70 µs |
| 5-field lists, steps, names | 89.2 ns | 152.4 ns | — | 9.31 µs |
6-field 0 30 2 * * 1-5 |
39.9 ns | — | 568.4 ns | 8.72 µs |
| 7-field Quartz with year | 87.8 ns | — | 792.5 ns | 1.49 µs |
nickname @daily |
9.81 ns | — | 100.3 ns | 8.64 µs |
rejected 0 0 * * 99 |
37.3 ns | 39.2 ns | 517.2 ns | 1.01 µs |
Each cell is the mean of two runs; repeated runs on this machine scatter by a few percent, so a difference of that size between two columns is not one.
The closest comparison is saffron, which is also a fixed-size five-field parser with no
allocation: 1.12× on the plain expression, 1.71× where the fields carry lists, steps and
names, and 1.05× on the rejection path. The wider margins against cron and croner are
mostly an allocation difference and should be read as such rather than as a statement about
their grammars.
The rejection row is a rejection-path measurement and is not comparable with the rows above it; every parser in it stops at the first error.
cargo bench reproduces the table — the three comparison parsers are dev-dependencies and
every row's expression is a named constant at the top of benches/parse.rs. That file
asserts what each parser accepts, and what the blank cells cannot take, before it times
anything: a dependency bump that changed one of those answers fails the bench instead of
quietly reporting an error path as a parse. The third-party parsers double as controls, and
a run whose control cells drift out of range is discarded rather than reported.
Features
| feature | effect |
|---|---|
| (default) | no_std, no alloc: parse, represent, match |
alloc, std |
reserved for owned diagnostics; nothing yet reaches them |
jiff |
calendar computation — next and previous occurrence. Still no_std. |
License
cronp is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 Al Liu.