cron-expressions
Parse, validate, match, and iterate over standard cron expressions as pure functions.
Supports 5-field cron format with Vixie extensions (L, W, #) and correct semantics
for every edge case that libraries disagree on.
Design principles
- Pure functions only — every function takes explicit inputs; no global state.
- UTC throughout — all datetime math uses UTC. No timezone handling.
- Vixie cron semantics — follows Vixie cron 4.1 conventions, documented with
provenance for every design decision.
- Clarity over performance — reference code prioritizes readability.
Minute-scanning is used for next-occurrence rather than optimized field-walking.
Input
$ARGUMENTS accepts:
help: Interactive guide to choosing the right nodes and language for your use case
- Nodes: space-separated node names to generate (or
all for the full library)
- --lang <language>: target language (default:
typescript). Supported: python, rust, go, typescript
Examples:
help — walk through choosing which nodes you need
matcher — generate matcher + dependencies in TypeScript
next-occurrence --lang python — generate next-occurrence + dependencies in Python
all --lang rust — generate the full library in Rust
Handling help
When $ARGUMENTS is help, read HELP.md and use it to guide the user through
node and language selection. The help guide contains a decision tree and common
use-case recipes. Walk through it interactively, asking the user about their
requirements, then recommend specific nodes and a target language.
Node Graph
cron-types ────────────────┬──► tokenizer ──► parser ──┐
(leaf) │ (leaf) (internal) │
│ │
field-range ───────────────┤──────────────► matcher ────┤
(leaf) │ (internal) │
│ │ │
│ next-occurrence ──┤
│ (internal) │
│ │ │
│ iterator ─────┤
│ (internal) │
│ │
└───────────► cron-schedule ─┘
(root)
Nodes
| Node |
Type |
Depends On |
Description |
cron-types |
leaf |
— |
CronFieldEntry, CronField, CronExpression type definitions and factories |
field-range |
leaf |
— |
Valid ranges per field, month/day-of-week aliases, last-day-of-month calculation |
tokenizer |
leaf |
— |
Splits cron string into 5 field strings |
parser |
internal |
cron-types, field-range, tokenizer |
Parses field strings into CronExpression AST |
matcher |
internal |
cron-types, field-range |
Tests whether a UTC datetime matches a CronExpression |
next-occurrence |
internal |
cron-types, matcher |
Finds next/previous datetime matching a CronExpression |
iterator |
internal |
cron-types, next-occurrence |
Lazy iteration over matching datetimes; nextN convenience |
cron-schedule |
root |
cron-types, parser, matcher, next-occurrence, iterator |
Public API: parse, match, next, prev, nextN, iterate |
Subset Extraction
- Parse only:
cron-types + field-range + tokenizer + parser
- Match a datetime:
cron-types + field-range + matcher (+ parser if starting from string)
- Find next occurrence: add
next-occurrence to the match subset
- Iterate over occurrences: add
iterator to the next-occurrence subset
- Full library: all 8 nodes via
cron-schedule
Key Design Decisions
Day-of-month / day-of-week interaction (THE critical decision)
@provenance Vixie cron 4.1, crontab(5) man page
When both day-of-month and day-of-week are restricted (not wildcard), the match
uses union (OR) — matching either field is sufficient. This is the Vixie cron
convention, which differs from what most people expect (intersection/AND).
| Expression |
Matches |
Rule |
0 0 15 * 5 |
15th of any month OR any Friday |
Union (both restricted) |
0 0 15 * * |
15th of any month |
Only DoM restricted |
0 0 * * 5 |
Every Friday |
Only DoW restricted |
Sunday representation
@provenance POSIX.1-2017 crontab(5), Vixie cron 4.1
| Input |
Normalized |
Notes |
0 |
0 (Sunday) |
POSIX standard |
7 |
0 (Sunday) |
Vixie extension — both 0 and 7 mean Sunday |
SUN |
0 (Sunday) |
Case-insensitive alias |
Field modifiers
| Modifier |
Valid In |
Meaning |
Source |
L |
dayOfMonth |
Last day of month |
Quartz, spring-cron |
nL |
dayOfWeek |
Last nth-day of month (e.g., 5L = last Friday) |
Quartz |
n#n |
dayOfWeek |
Nth weekday of month (e.g., 5#3 = third Friday) |
Quartz |
nW |
dayOfMonth |
Nearest weekday to nth day (never crosses month boundary) |
Quartz |
Nearest weekday (W) boundary rules
@provenance Quartz scheduler W modifier semantics
| Scenario |
Resolution |
| Target is a weekday |
Use target as-is |
| Target is Saturday, not 1st |
Use Friday (target - 1) |
| 1st is Saturday |
Use Monday the 3rd (can't go to previous month) |
| Target is Sunday, not last day |
Use Monday (target + 1) |
| Last day is Sunday |
Use Friday (target - 2, can't go to next month) |
Process
- If
$ARGUMENTS is help, read HELP.md and guide the user interactively
- Read this file for the node graph and design decisions
- For each requested node (in dependency order), read
nodes/<name>/spec.md
- Read
nodes/<name>/to-<lang>.md for target-language translation hints
- Generate implementation + tests
- If the spec is ambiguous, consult
reference/src/<name>.ts (track what you consulted and why)
- Run tests — all must pass before proceeding to the next node
Error Handling
tokenize throws on empty/whitespace input or wrong field count (not 5)
parseCron throws on: out-of-range values, invalid step values (0 or negative),
unrecognized tokens, invalid nth values (#0 or #6+)
matchesCron is a total function (no error cases)
nextOccurrence / prevOccurrence return null if no match within ~1 year
cronSchedule throws on invalid expressions (delegates to parseCron)
Reference
The TypeScript reference implementation is in reference/src/. It is the
authoritative source — consult it when specs are ambiguous, but prefer the
spec and translation hints as primary sources.
All reference code has 100% line and function coverage via bun test --coverage.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: cron-expressions3description: Generate native cron expression parsing, matching, and scheduling — recurring time patterns, crontab semantics, next-occurrence calculation — from a verified TypeScript reference Use when this capability is needed.4---56# cron-expressions78Parse, validate, match, and iterate over standard cron expressions as pure functions.9Supports 5-field cron format with Vixie extensions (L, W, #) and correct semantics10for every edge case that libraries disagree on.1112## Design principles1314- **Pure functions only** — every function takes explicit inputs; no global state.15- **UTC throughout** — all datetime math uses UTC. No timezone handling.16- **Vixie cron semantics** — follows Vixie cron 4.1 conventions, documented with17 provenance for every design decision.18- **Clarity over performance** — reference code prioritizes readability.19 Minute-scanning is used for next-occurrence rather than optimized field-walking.2021## Input2223`$ARGUMENTS` accepts:24- **`help`**: Interactive guide to choosing the right nodes and language for your use case25- **Nodes**: space-separated node names to generate (or `all` for the full library)26- **--lang \<language\>**: target language (default: `typescript`). Supported: `python`, `rust`, `go`, `typescript`2728Examples:29- `help` — walk through choosing which nodes you need30- `matcher` — generate matcher + dependencies in TypeScript31- `next-occurrence --lang python` — generate next-occurrence + dependencies in Python32- `all --lang rust` — generate the full library in Rust3334## Handling `help`3536When `$ARGUMENTS` is `help`, read `HELP.md` and use it to guide the user through37node and language selection. The help guide contains a decision tree and common38use-case recipes. Walk through it interactively, asking the user about their39requirements, then recommend specific nodes and a target language.4041## Node Graph4243```44cron-types ────────────────┬──► tokenizer ──► parser ──┐45 (leaf) │ (leaf) (internal) │46 │ │47field-range ───────────────┤──────────────► matcher ────┤48 (leaf) │ (internal) │49 │ │ │50 │ next-occurrence ──┤51 │ (internal) │52 │ │ │53 │ iterator ─────┤54 │ (internal) │55 │ │56 └───────────► cron-schedule ─┘57 (root)58```5960### Nodes6162| Node | Type | Depends On | Description |63|------|------|-----------|-------------|64| `cron-types` | leaf | — | CronFieldEntry, CronField, CronExpression type definitions and factories |65| `field-range` | leaf | — | Valid ranges per field, month/day-of-week aliases, last-day-of-month calculation |66| `tokenizer` | leaf | — | Splits cron string into 5 field strings |67| `parser` | internal | cron-types, field-range, tokenizer | Parses field strings into CronExpression AST |68| `matcher` | internal | cron-types, field-range | Tests whether a UTC datetime matches a CronExpression |69| `next-occurrence` | internal | cron-types, matcher | Finds next/previous datetime matching a CronExpression |70| `iterator` | internal | cron-types, next-occurrence | Lazy iteration over matching datetimes; nextN convenience |71| `cron-schedule` | root | cron-types, parser, matcher, next-occurrence, iterator | Public API: parse, match, next, prev, nextN, iterate |7273### Subset Extraction7475- **Parse only**: `cron-types` + `field-range` + `tokenizer` + `parser`76- **Match a datetime**: `cron-types` + `field-range` + `matcher` (+ parser if starting from string)77- **Find next occurrence**: add `next-occurrence` to the match subset78- **Iterate over occurrences**: add `iterator` to the next-occurrence subset79- **Full library**: all 8 nodes via `cron-schedule`8081## Key Design Decisions8283### Day-of-month / day-of-week interaction (THE critical decision)8485@provenance Vixie cron 4.1, crontab(5) man page8687When **both** day-of-month and day-of-week are restricted (not wildcard), the match88uses **union (OR)** — matching either field is sufficient. This is the Vixie cron89convention, which differs from what most people expect (intersection/AND).9091| Expression | Matches | Rule |92|------------|---------|------|93| `0 0 15 * 5` | 15th of any month **OR** any Friday | Union (both restricted) |94| `0 0 15 * *` | 15th of any month | Only DoM restricted |95| `0 0 * * 5` | Every Friday | Only DoW restricted |9697### Sunday representation9899@provenance POSIX.1-2017 crontab(5), Vixie cron 4.1100101| Input | Normalized | Notes |102|-------|-----------|-------|103| `0` | `0` (Sunday) | POSIX standard |104| `7` | `0` (Sunday) | Vixie extension — both 0 and 7 mean Sunday |105| `SUN` | `0` (Sunday) | Case-insensitive alias |106107### Field modifiers108109| Modifier | Valid In | Meaning | Source |110|----------|---------|---------|--------|111| `L` | dayOfMonth | Last day of month | Quartz, spring-cron |112| `nL` | dayOfWeek | Last nth-day of month (e.g., `5L` = last Friday) | Quartz |113| `n#n` | dayOfWeek | Nth weekday of month (e.g., `5#3` = third Friday) | Quartz |114| `nW` | dayOfMonth | Nearest weekday to nth day (never crosses month boundary) | Quartz |115116### Nearest weekday (W) boundary rules117118@provenance Quartz scheduler W modifier semantics119120| Scenario | Resolution |121|----------|-----------|122| Target is a weekday | Use target as-is |123| Target is Saturday, not 1st | Use Friday (target - 1) |124| 1st is Saturday | Use Monday the 3rd (can't go to previous month) |125| Target is Sunday, not last day | Use Monday (target + 1) |126| Last day is Sunday | Use Friday (target - 2, can't go to next month) |127128## Process1291301. If `$ARGUMENTS` is `help`, read `HELP.md` and guide the user interactively1312. Read this file for the node graph and design decisions1323. For each requested node (in dependency order), read `nodes/<name>/spec.md`1334. Read `nodes/<name>/to-<lang>.md` for target-language translation hints1345. Generate implementation + tests1356. If the spec is ambiguous, consult `reference/src/<name>.ts` (track what you consulted and why)1367. Run tests — all must pass before proceeding to the next node137138## Error Handling139140- `tokenize` throws on empty/whitespace input or wrong field count (not 5)141- `parseCron` throws on: out-of-range values, invalid step values (0 or negative),142 unrecognized tokens, invalid nth values (#0 or #6+)143- `matchesCron` is a total function (no error cases)144- `nextOccurrence` / `prevOccurrence` return `null` if no match within ~1 year145- `cronSchedule` throws on invalid expressions (delegates to parseCron)146147## Reference148149The TypeScript reference implementation is in `reference/src/`. It is the150authoritative source — consult it when specs are ambiguous, but prefer the151spec and translation hints as primary sources.152153All reference code has 100% line and function coverage via `bun test --coverage`.154155---156> Converted and distributed by [TomeVault](https://tomevault.io/claim/caryden) — claim your Tome and manage your conversions.157<!-- tomevault:4.0:skill_md:2026-04-13 -->