cron-parser
cron-parser turns a crontab string into something you can ask questions of. Give it "0 9 * * 1-5" and it hands back an object whose next() returns the next weekday at nine, whose prev() walks backwards, and whose take(5) gives you the next five occurrences. It understands the standard five-field syntax plus an optional leading seconds field, the Quartz-style extras L and #, the @daily family of shortcuts, and Jenkins-style H for spreading load with jitter. Timezones and daylight saving transitions go through Luxon, so an expression evaluated in Europe/London does the right thing on the two days a year when local time jumps. What it does not do is run anything. There is no scheduler, no timer, no job queue; it only answers when would this fire.
If you are computing fire times rather than running jobs, this is the reference implementation and the timezone handling alone justifies the Luxon dependency. If you also want something to actually invoke your function on schedule, croner covers both jobs in one smaller package.
Use it if
- You are building a scheduler and need to compute the next fire time yourself, for example to store a nextRunAt column and poll it, rather than keeping timers alive in a process that restarts
- You want to show users when their job will actually run next, or validate a cron expression they typed before saving it
- Your schedules are timezone-sensitive and you need the daylight saving edges handled instead of drifting by an hour twice a year
- You need the Quartz-flavoured extras: last day of month with L, the nth weekday with 1#1, or a seconds field for sub-minute schedules
- You actually want to run jobs. This computes dates and stops there; croner, node-cron, or a queue like BullMQ own the timer, the overlap handling, and the retries
- Bundle size matters in the browser. Luxon is the dependency here and it dominates the roughly 29 kB gzipped total, so if you already ship date-fns or Day.js you are now carrying two date libraries
- You only need to describe a cron expression in English. cronstrue does that in a fraction of the size and none of the iteration machinery
- You are on v4 and expecting a quiet upgrade. v5 replaced the top-level parseExpression function with CronExpressionParser.parse, so nearly every code sample and answer written before 2025 fails on the current release
- You expect crontab-identical semantics by default. With both day-of-month and day-of-week restricted, real cron ORs them and this library allows the combination without warning unless you turn on strict mode
- You need to trust every published patch. 5.6.2 was published from a stale dist directory, shipped the 5.6.1 code, and had to be deprecated on npm in favour of 5.7.0
Setup reality
npm install cron-parser gets you a CommonJS build with bundled TypeScript declarations and a single runtime dependency on Luxon, no native code and no peer dependencies. Node 18 or newer is required, and TypeScript 5 if you are compiling. The real setup cost is the v5 API rename: imports are named exports now, so `const parser = require("cron-parser")` followed by parser.parseExpression(...) is gone and you want CronExpressionParser.parse instead. After that, watch two behaviours. Passing endDate makes next() throw "Out of the time span range" once iteration passes it, so scheduling loops need a try/catch or a hasNext() guard. And an expression that can never match, like February 30th, now throws a loop-limit error rather than quietly returning a wrong date, which is an improvement but a new failure mode if you were not catching.
Patterns
Get the next fire timenext-occurrence
import { CronExpressionParser } from "cron-parser";
const interval = CronExpressionParser.parse("0 9 * * 1-5");
const next = interval.next();
console.log(next.toISOString());
const asDate = next.toDate();next() returns a CronDate, not a Date. Call toDate() before handing it to code that does instanceof checks or to a database driver.
List the next several runsnext-n-occurrences
const interval = CronExpressionParser.parse("*/15 * * * *");
const upcoming = interval.take(5).map((d) => d.toISOString());
// negative counts walk backwards
const recent = CronExpressionParser.parse("*/15 * * * *").take(-3);take() advances the internal cursor, so calling next() afterwards continues from where take left off. Call reset() if you want to start over from the original current date.
Evaluate an expression in a specific timezonetimezone-aware
const interval = CronExpressionParser.parse("0 3 * * *", {
currentDate: "2026-03-28T00:00:00Z",
tz: "Europe/London",
});
console.log(interval.next().toString());Without tz the expression is evaluated against local time of the running process, which is how staging and production end up on different schedules. Daylight saving gaps are resolved by Luxon.
Check a user-supplied expressionvalidate-expression
function isValidCron(input: string) {
try {
CronExpressionParser.parse(input);
return true;
} catch (err) {
return (err as Error).message;
}
}Parsing is the only validation API; there is no separate validate function. An empty string is accepted outside strict mode and silently means every second.
Reject ambiguous expressionsstrict-mode
CronExpressionParser.parse("0 0 12 1-31 * 1", { strict: true });
// Error: Cannot use both dayOfMonth and dayOfWeek together in strict mode!
CronExpressionParser.parse("0 20 15 * *", { strict: true });
// Error: Invalid cron expression, expected 6 fieldsStrict mode also demands all six fields, seconds included, so it is not a drop-in for validating five-field crontab lines. Use it when your UI owns the whole expression.
Iterate only inside a date windowbounded-range
const interval = CronExpressionParser.parse("0 0 * * *", {
currentDate: "2026-12-31T00:00:00Z",
endDate: "2027-01-01T00:00:00Z",
});
while (interval.hasNext()) {
console.log(interval.next().toISOString());
}Calling next() past endDate throws "Out of the time span range" rather than returning null, so guard with hasNext() or wrap in try/catch. A currentDate outside the window is clamped to startDate automatically.
Schedule below the minuteseconds-field
const interval = CronExpressionParser.parse("*/30 * * * * *");
console.log(interval.take(3).map((d) => d.toISOString()));
// every 30 secondsSix fields means the first one is seconds, which is a Quartz convention rather than POSIX crontab. Paste a six-field expression into a real crontab and it will be rejected.
Last day of month and nth weekdaylast-day-and-nth-weekday
CronExpressionParser.parse("0 0 L * *"); // midnight, last day of month
CronExpressionParser.parse("0 0 0 * * 1L"); // last Monday of the month
CronExpressionParser.parse("0 0 * * 1#1"); // first Monday of the monthL in the day-of-month field means the last day; L after a weekday number means the last occurrence of that weekday. Both are Quartz extensions that vanilla cron does not understand.
Spread load with H and a stable seedhash-jitter
const interval = CronExpressionParser.parse("H 23 * * 1-5", {
hashSeed: "nightly-backup",
});
console.log(interval.stringify()); // e.g. "12 23 * * 1-5"Without hashSeed the minute is random per parse, so two calls disagree and a job appears to move. Seed it with the job name to get the same offset every time the process restarts.
Walk occurrences with a for loopiterate-with-for-of
const interval = CronExpressionParser.parse("0 */2 * * *");
for (const date of interval) {
if (date.getTime() > deadline) break;
schedule(date.toDate());
}The iterator is unbounded, so you must break out yourself or set endDate. It shares the same cursor as next(), which means mixing the two continues rather than restarts.
Edit an expression programmaticallymodify-fields
import { CronExpressionParser, CronFieldCollection } from "cron-parser";
const interval = CronExpressionParser.parse("0 7 * * 1-5");
const modified = CronFieldCollection.from(interval.fields, {
hour: [8],
minute: [30],
dayOfWeek: [1, 3, 5],
});
console.log(modified.stringify()); // "30 8 * * 1,3,5"Better than string surgery when a UI edits one field at a time. Fields you leave out are carried over unchanged from the original collection.
Read a whole crontab fileparse-crontab-file
import { CronFileParser } from "cron-parser";
const result = await CronFileParser.parseFile("/etc/crontab");
console.log(result.variables); // MAILTO=..., PATH=...
console.log(result.expressions); // parsed CronExpression instances
console.log(result.errors); // per-line parse failuresBad lines land in errors rather than throwing, so check that array before trusting expressions. There is a parseFileSync variant if you are in startup code.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| croner | npm | You want both the parsing and an actual scheduler that fires callbacks, with no dependencies and browser support. |
| cron | npm | Classic Node job scheduling where you want a CronJob object with start and stop and do not need to compute dates yourself. |
| cronstrue | npm | You only need to turn an expression into a human-readable sentence for a settings screen. |