# parseCron _(no submodules)_ ## Purpose `parseCron` is the top-level orchestrator that consumes a raw cron expression string and returns a frozen `CronExpression` whose `nextRun`, `nextRuns`, `previousRun`, and `matches` methods drive every scheduling query. The orchestrator macro-expands the input first, then dispatches the resulting tokens through `parseField` per position, applies optional Vixie strict-DoW semantics, and finally binds the result to the search engine in `next-run.ts`. `isValidCron` is the non-throwing sibling: it returns a boolean and guarantees no exception escape for any string input, as mandated by the spec. ## Features - 5-field default; 6-field opt-in via `seconds: true`; 7-field Quartz opt-in via both `seconds` and `years` true. - Macro expansion (`@daily`, `@hourly`, ...) handled before tokenization. - `strictDoW` option enforces "either dom or dow, never both" Vixie semantics. - `nextRuns(count)` produces a strictly monotonic array. - `matches(when, tz)` is a constant-time membership check against the parsed field sets. ## Usage: Basic Example ```typescript import { parseCron, isValidCron } from "@suaveplan/cron"; const expr = parseCron("*/15 * * * *"); console.log(expr.fields.minutes); // [0, 15, 30, 45] console.log(expr.nextRun(Date.UTC(2026, 4, 11, 10, 7, 0), "UTC")); // Date.UTC(2026, 4, 11, 10, 15, 0) console.log(isValidCron("not a cron expression")); // false console.log(isValidCron("0 9 * * MON-FRI")); // true ``` ## Usage: Advanced Example ```typescript import { parseCron } from "@suaveplan/cron"; const quartz = parseCron("0 0 12 * * MON-FRI 2026-2030", { seconds: true, years: true, }); const runs = quartz.nextRuns(3, Date.UTC(2026, 0, 1), "America/New_York"); console.log(runs.map((t) => new Date(t).toISOString())); const strict = parseCron("0 12 15 * 1", { strictDoW: true }); // Rejects expressions where dom AND dow are both restricted. console.log(strict.matches(Date.UTC(2026, 0, 15, 12, 0, 0), "UTC")); ``` ## API Reference - `parseCron(expr: string, options?: ParseOptions): CronExpression` — parses and returns the frozen expression handle. - `isValidCron(expr: string, options?: ParseOptions): boolean` — never throws. - `interface CronExpression { expr; fields; nextRun; nextRuns; previousRun; matches }`. - `interface ParseOptions { seconds?; years?; strictDoW? }`. - `interface CronFields { seconds?; minutes; hours; daysOfMonth; months; daysOfWeek; years? }`. ## Implementation Notes The orchestrator first invokes `expandMacro` so the rest of the pipeline only ever sees canonical 5/6/7-field strings. Tokenization is whitespace- based with the field count strictly validated against `ParseOptions`. Each field is independently parsed via the shared `parseField` tokenizer, which returns a sorted, deduplicated `readonly number[]`. `strictDoW` enforcement runs after tokenization so the error context carries the actual parsed sets for debug visibility. The returned `CronExpression` is `Object.freeze`d and binds its methods over the captured fields, so destructuring (`const { nextRun } = parseCron(...)`) is safe. `isValidCron` is a tight try/catch around the same code path so the two functions stay in lock-step with no semantic drift. All parse calls flow through `recordParse` so an outcome attribute lands on every invocation regardless of success or failure.