Skip to content

didrod205/croniq

croniq

Parse cron expressions, compute the next/previous run times, and describe them in plain English — in ~2 KB, with zero dependencies.

npm version bundle size CI types license

"When does 0 9 * * 1-5 run next?" is a question you should never answer by hand — and definitely shouldn't trust an LLM to guess. It's exact calendar arithmetic (ranges, steps, month lengths, leap years, the day-of-month/day-of-week union rule). croniq does it deterministically, and tells you in English.

import { parse } from "@didrod2539/croniq";

const cron = parse("0 9 * * 1-5");      // 09:00, weekdays
cron.next();                            // → Date of the next weekday 09:00
cron.describe();                        // "At 09:00, Monday through Friday"
cron.matches(new Date());               // is right now a run time?

Why croniq?

  • 🎯 Correct, not approximate. Leap days, month lengths, and the Vixie-cron day-of-month OR day-of-week rule are all handled. Verified against known schedules in tests.
  • Fast. Field-jumping (not minute-by-minute scanning), so sparse schedules like 0 0 29 2 * resolve in microseconds.
  • 🗣️ Human-readable. describe() turns an expression into a sentence — great for dashboards and confirmations.
  • 🧩 Full syntax. 5 fields or 6 (with seconds), ranges 1-5, lists 1,3,5, steps */15, names JAN/MON, ?, and macros @daily/@hourly/@weekly/…
  • 🕒 UTC or local. One flag.
  • 🪶 ~2 KB gzipped, zero dependencies. Node 18+, Deno, Bun, Workers and the browser.

Install

npm install @didrod2539/croniq
# or: pnpm add @didrod2539/croniq  /  yarn add @didrod2539/croniq

Published under the @didrod2539 npm scope (the unscoped name croniq was blocked by npm for being too close to cron). The import name matches the package name; everything else is identical.

Ships ESM and CommonJS:

import { parse, isValid } from "@didrod2539/croniq";        // ESM / TypeScript
const { parse, isValid } = require("@didrod2539/croniq");   // CommonJS

CLI

npx @didrod2539/croniq "0 9 * * 1"             # describe + the next runs
npx @didrod2539/croniq describe "*/15 * * * *" # "Every 15 minutes"
npx @didrod2539/croniq next "0 0 1 * *" --iso -n 5
npx @didrod2539/croniq valid "61 * * * *"      # exit 1 if invalid

The bundled command is croniq. Commands: describe, next, prev, valid. Options: -n/--count, --utc, --iso. valid sets the exit code.

Usage

Next & previous runs

const cron = parse("*/15 * * * *");       // every 15 minutes

cron.next();                              // next run after now
cron.next(new Date("2026-03-15T12:07Z")); // → 2026-03-15T12:15:00Z
cron.prev();                              // previous run before now
cron.nextN(5);                            // next five runs (Date[])

Validate

isValid("0 9 * * 1-5"); // true
isValid("99 * * * *");  // false

Describe

parse("0 9 * * 1-5").describe();   // "At 09:00, Monday through Friday"
parse("*/15 * * * *").describe();  // "Every 15 minutes"
parse("@hourly").describe();       // "At 0 minutes past every hour"
parse("0 0 1 1 *").describe();     // "At 00:00, on day-of-month 1, in January"

UTC vs local

parse("0 0 * * *", { utc: true }).next();  // midnights in UTC
parse("0 0 * * *").next();                 // midnights in the host's local time

Seconds field

A 6-field expression adds a leading seconds column:

parse("*/30 * * * * *").next(); // every 30 seconds

Supported syntax

Field Values Allowed
Second (opt.) 0–59 * , - /
Minute 0–59 * , - /
Hour 0–23 * , - /
Day of month 1–31 * , - / ?
Month 1–12 or JANDEC * , - /
Day of week 0–7 or SUNSAT (0 & 7 = Sunday) * , - / ?

Macros: @yearly / @annually, @monthly, @weekly, @daily / @midnight, @hourly. When both day-of-month and day-of-week are restricted, a date matches if either matches (standard cron behavior).

L, W, and # modifiers aren't supported yet — open an issue if you need them.

API

Member Description
parse(expr, opts?) Parse into a Cron (throws on invalid input).
isValid(expr, opts?) true/false without throwing.
cron.next(from?) / cron.prev(from?) Next/previous run Date.
cron.nextN(n, from?) / cron.prevN(n, from?) Arrays of run times.
cron.matches(date?) Does the date satisfy the expression?
cron.describe() Plain-English description.
cron.minutes / .hours / .daysOfMonth / .months / .daysOfWeek / .seconds Parsed value arrays.

Comparison

croniq hand-rolled date math heavier cron libs
next / prev computation ⚠️
Plain-English describe ⚠️ (separate lib)
Validation
Zero dependencies ⚠️
~2 KB gzipped

Contributing

Contributions are very welcome! Please read CONTRIBUTING.md and our Code of Conduct.

git clone https://github.com/didrod205/croniq.git
cd croniq
npm install
npm test

💖 Sponsor

croniq is free and MIT-licensed, built and maintained in spare time. If it saved you from debugging a misfiring schedule, please consider supporting it — every bit helps keep the project healthy.

  • Star this repo — the simplest, free way to help others discover it.
  • 🍋 Sponsor via Lemon Squeezy — one-time or recurring support.

Sponsoring? Open an issue and we'll add your name/logo here. Thank you! 🙏

License

MIT © croniq contributors

About

Tiny zero-dependency cron toolkit — parse, compute next/previous run times (fast field-jumping), validate, and describe in plain English. ~2KB.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors