npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cron-fast

v3.0.0

Published

Fast and tiny JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.

Downloads

4,815

Readme

cron-fast

npm version npm provenance JSR JSR Score CI codecov npm bundle size Snyk License: MIT

Fast and tiny JavaScript/TypeScript cron parser with timezone support. Works everywhere: Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.

Features

  • Parse & validate - Convert cron expressions to structured data and check validity
  • Get execution times - Calculate next, previous, or multiple scheduled runs
  • Match dates - Check if a date matches a cron expression
  • Describe - Convert cron expressions to human-readable text (e.g., "Every 5 minutes")
  • Timezone support - Full IANA timezone support using native Intl API

Why cron-fast?

  • Universal - Works in Node.js, Deno, Bun, Cloudflare Workers, and browsers
  • Zero dependencies - Lightweight and secure
  • Fast - Optimal field increment algorithm with 10x+ performance vs alternatives
  • Tree-shakeable - Import only what you need
  • Modern - ESM + CJS, TypeScript-first
  • Fully tested - Comprehensive test coverage across all runtimes
  • ISO 8601 compatible - Works with all standard date formats

Installation

# Node.js (npm)
npm install cron-fast

# Node.js (pnpm)
pnpm add cron-fast

# Node.js (yarn)
yarn add cron-fast

# Deno (JSR)
deno add jsr:@kbilkis/cron-fast

# Bun
bun add cron-fast

# Any runtime (JSR)
npx jsr add @kbilkis/cron-fast

Quick Start

import { nextRun, previousRun, isValid, describe } from "cron-fast";

// Get next execution time (UTC)
const next = nextRun("0 9 * * *");
console.log(next); // Next 9:00 AM UTC

// With timezone
const nextNY = nextRun("0 9 * * *", { timezone: "America/New_York" });
console.log(nextNY); // Next 9:00 AM Eastern Time

// Get previous execution
const prev = previousRun("*/15 * * * *");

// Validate expression
if (isValid("0 9 * * *")) {
  console.log("Valid cron expression!");
}

// Get human-readable description
console.log(describe("*/5 * * * *")); // "Every 5 minutes"

API

nextRun(expression: string, options?: CronOptions): Date

Get the next execution time for a cron expression. Throws if the expression or timezone is invalid.

nextRun("0 9 * * *"); // Next 9:00 AM UTC
nextRun("0 9 * * *", { timezone: "Europe/London" }); // Next 9:00 AM London time
nextRun("0 9 * * *", { from: new Date("2026-03-15") }); // Next after Mar 15, 2026

previousRun(expression: string, options?: CronOptions): Date

Get the previous execution time. Throws if the expression or timezone is invalid.

previousRun("0 9 * * *"); // Last 9:00 AM UTC
previousRun("0 9 * * *", { timezone: "Asia/Tokyo" });

nextRuns(expression: string, count: number, options?: CronOptions): Date[]

Get next N execution times. Throws if the expression or timezone is invalid.

nextRuns("0 9 * * *", 5); // Next 5 occurrences

isValid(expression: string): boolean

Validate a cron expression.

isValid("0 9 * * *"); // true
isValid("invalid"); // false

isMatch(expression: string, date: Date, options?: CronOptions): boolean

Check if a date matches the cron expression. Throws if the expression or timezone is invalid.

isMatch("0 9 * * *", new Date("2026-03-15T09:00:00Z")); // true

parse(expression: string): ParsedCron

Parse a cron expression into its components. Throws if the expression is invalid.

parse("0 9 * * 1-5");
// Returns: { minute: [0], hour: [9], day: [1, 2, ..., 31], month: [0, 1, 2, ..., 11], weekday: [1,2,3,4,5] }

describe(expression: string): string

Get a human-readable description of a cron expression. Returns "Invalid cron expression" if the expression is invalid.

describe("*/5 * * * *"); // "Every 5 minutes"
describe("0 9 * * 1-5"); // "At 9:00 AM, on weekdays"
describe("*/15 3,4 1-4 */3 6"); // Every 15 minutes, at 3 AM or 4 AM, on the 1st through 4th or on Saturdays every 3 months
describe("invalid"); // "Invalid cron expression"

Types

interface CronOptions {
  timezone?: string; // IANA timezone string (e.g., 'America/New_York')
  from?: Date; // Reference date (defaults to now)
}

interface ParsedCron {
  minute: number[]; // 0-59
  hour: number[]; // 0-23
  day: number[]; // 1-31
  month: number[]; // 0-11 (0 = January)
  weekday: number[]; // 0-6 (0 = Sunday)
  dayIsWildcard: boolean;
  weekdayIsWildcard: boolean;
}

Cron Expression Format

* * * * *
│ │ │ │ │
│ │ │ │ └─ Day of Week (0-7, SUN-SAT)
│ │ │ └─── Month (1-12, JAN-DEC)
│ │ └───── Day of Month (1-31)
│ └─────── Hour (0-23)
└───────── Minute (0-59)

Supported Special Characters

  • * - Any value
  • , - Value list (e.g., 1,3,5)
  • - - Range (e.g., 1-5)
  • / - Step values (e.g., */5)

ISO 8601 Date Support

cron-fast fully supports ISO 8601 date formats for input:

// All these formats work:
nextRun("0 9 * * *", { from: new Date("2026-03-15T14:30:00Z") }); // UTC
nextRun("0 9 * * *", { from: new Date("2026-03-15T09:30:00-05:00") }); // With offset
nextRun("0 9 * * *", { from: new Date("2026-03-15T14:30:00.500Z") }); // With milliseconds

// Different representations of the same moment produce identical results
const utc = new Date("2026-03-15T14:30:00Z");
const est = new Date("2026-03-15T09:30:00-05:00"); // Same moment
nextRun("0 9 * * *", { from: utc }).getTime() === nextRun("0 9 * * *", { from: est }).getTime(); // true

Note: All returned Date objects are in UTC (ending with Z in .toISOString()). Use .toLocaleString() to display in any timezone.

Bundle Size

cron-fast is extremely lightweight and fully tree-shakeable. Here are the actual bundle sizes for different import scenarios (tested with v3.0.0):

| Import | Raw | Minified | Gzipped | | ------------------------------------------------------ | -------- | -------- | ----------- | | Full bundle (all exports) | 21.47 KB | 9.88 KB | 3.55 KB | | nextRun only | 12.73 KB | 5.79 KB | 2.14 KB | | previousRun only | 12.73 KB | 5.79 KB | 2.14 KB | | nextRuns only | 13.11 KB | 5.94 KB | 2.21 KB | | isValid only | 4.44 KB | 2.22 KB | 984 B | | parse only | 4.32 KB | 2.18 KB | 961 B | | describe only | 11.54 KB | 5.57 KB | 2.11 KB | | isMatch only | 6.04 KB | 2.96 KB | 1.26 KB | | Validation only (isValid + parse) | 4.45 KB | 2.23 KB | 986 B | | Scheduling only (nextRun + previousRun + nextRuns) | 13.51 KB | 6.12 KB | 2.22 KB |

Import only what you need:

// Small bundle - only validation
import { isValid } from "cron-fast";

// Medium bundle - one function + dependencies
import { nextRun } from "cron-fast";

// Full bundle - everything
import * as cron from "cron-fast";

Advanced Usage

Working with Timezones

// Cron expression is interpreted in the specified timezone
const next = nextRun("0 9 * * *", { timezone: "America/New_York" });

// The returned Date is always UTC internally
console.log(next.toISOString()); // "2026-03-15T13:00:00.000Z" (9 AM EDT = 1 PM UTC)

// Display in any timezone
console.log(next.toLocaleString("en-US", { timeZone: "America/New_York" }));
// "3/15/2026, 9:00:00 AM"

Multiple Executions

// Get next 10 runs
const runs = nextRuns("0 */6 * * *", 10); // Every 6 hours

// With timezone
const runsNY = nextRuns("0 9 * * 1-5", 5, { timezone: "America/New_York" });
// Next 5 weekday mornings in New York

Validation and Parsing

// Functions throw on invalid input, but you can pre-validate user input
if (!isValid(userInput)) {
  console.log("Invalid cron expression");
  return;
}

// Or use try/catch
try {
  const next = nextRun(userInput);
} catch (e) {
  console.log("Invalid cron expression");
}

// Parse to see what it means
const parsed = parse("*/15 9-17 * * 1-5");
console.log(parsed);
// {
//   minute: [0, 15, 30, 45],
//   hour: [9, 10, 11, 12, 13, 14, 15, 16, 17],
//   day: [1-31],
//   month: [1-12],
//   weekday: [1, 2, 3, 4, 5]
// }

Check if Date Matches

const now = new Date();

if (isMatch("0 9 * * 1-5", now)) {
  console.log("It's 9 AM on a weekday!");
}

// With timezone
if (isMatch("0 9 * * *", now, { timezone: "America/New_York" })) {
  console.log("It's 9 AM in New York!");
}

Tips & Gotchas

  • Invalid input throws: Most functions (nextRun, previousRun, nextRuns, isMatch, parse) throw an error for invalid cron expressions. nextRun, previousRun, nextRuns, and isMatch also throw for invalid timezones. Use isValid() to pre-validate user input, or wrap calls in try/catch. Note: describe() returns "Invalid cron expression" instead of throwing.
  • 5-field format only: cron-fast does not support 6-field cron (with seconds). Use standard 5-field format.
  • Month indexing: Input uses cron convention (1-12), but ParsedCron.month uses JavaScript convention (0-11)
  • Impossible dates are invalid: Expressions like 0 0 31 2 * (February 31st) are treated as invalid since they can never match.
  • Timezone handling: The cron expression is interpreted in the timezone you specify, but the returned Date is always in UTC
  • Daylight saving time: Use IANA timezone names (like "America/New_York") instead of abbreviations (like "EST")
  • Day 0 and 7: Both represent Sunday in the day-of-week field
  • Ranges are inclusive: 1-5 includes both 1 and 5

Performance

cron-fast is designed for speed and efficiency. Here's how it compares to popular alternatives:

Tested with cron-fast v3.0.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0 on Node.js v22.18.0

| Operation | cron-fast | croner | cron-parser | cron-schedule | | ------------ | --------------- | --------- | ----------- | ------------- | | Next run | 911k ops/s | 31k ops/s | 34k ops/s | 352k ops/s | | Previous run | 1003k ops/s | 32k ops/s | 39k ops/s | 399k ops/s | | Validation | 1958k ops/s | 34k ops/s | 97k ops/s | 462k ops/s | | Parsing | 1982k ops/s | 35k ops/s | 98k ops/s | 469k ops/s |

See detailed benchmarks and feature comparison (including Deno and Bun runtimes) for more information.

Run benchmarks yourself: pnpm benchmark

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

Contributors

License

MIT - see LICENSE for details.