Files
skills/docs/foundation/timing/cron/src/parse/parse-cron.md
2026-05-29 08:20:56 +01:00

3.4 KiB

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

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

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.freezed 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.