cronp 0.2.0

A blazing fast no-std, no-alloc cron expression parser with multiple dialects support
Documentation

A blazing fast no-std, no-alloc cron expression parser, with multiple dialects support.

Installation

[dependencies]
cronp = "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 cronp::{ParseError, Quartz, Schedule, Vixie};

fn example() -> Result<(), ParseError> {
    // Five fields, the shape crontab(5) takes.
    let nightly: Schedule<Vixie> = Schedule::parse("30 2 * * 1-5")?;

    // Six or seven, a leading seconds field, and `?` in one of the two day fields.
    let quartz = Schedule::<Quartz>::parse("0 15 10 ? * MON-FRI")?;

    // Five fields is not Quartz, and the error says so.
    assert!(Schedule::<Quartz>::parse("30 2 * * 1-5").is_err());

    let _ = (nightly, quartz);
    Ok(())
}

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.