Fast and tiny JavaScript/TypeScript cron parser with timezone support
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

cli tool, readme updates

+487 -186
+42 -42
README.md
··· 10 10 11 11 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 12 12 13 - **Fast and tiny JavaScript/TypeScript cron parser with timezone support.** Works everywhere: Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies. 13 + **10x+ faster than the alternatives. 3.5KB gzipped. Zero dependencies.** 14 + 15 + Fast and tiny JavaScript/TypeScript cron parser with timezone support. Works everywhere: Node.js, Deno, Bun, Cloudflare Workers, and browsers. 14 16 15 17 ## Features 16 18 ··· 19 21 - **Match dates** - Check if a date matches a cron expression 20 22 - **Describe** - Convert cron expressions to human-readable text (e.g., "Every 5 minutes") 21 23 - **Timezone support** - Full IANA timezone support using native `Intl` API 24 + - **CLI included** - Validate, preview, and describe expressions from the terminal 22 25 23 26 ## Why cron-fast? 24 27 25 - - **Universal** - Works in Node.js, Deno, Bun, Cloudflare Workers, and browsers 26 - - **Zero dependencies** - Lightweight and secure 27 - - **Fast** - Optimal field increment algorithm with 10x+ performance vs alternatives 28 - - **Tree-shakeable** - Import only what you need 29 - - **Modern** - ESM + CJS, TypeScript-first 28 + - **10x+ faster** than other popular cron libraries on scheduling operations 29 + - **1/8 the bundle size** of the next most popular alternative (3.55 KB vs 28.2 KB gzipped) 30 + - **Zero dependencies** — nothing to audit, nothing to break 31 + - **Universal runtime** — same code in Node.js, Deno, Bun, Cloudflare Workers, and browsers 32 + - **Tree-shakeable** — `import { isValid }` adds < 1 KB to your bundle 33 + - **TypeScript-first** — strict types, no `@ts-ignore` required 30 34 - **Fully tested** - Comprehensive test coverage across all runtimes 31 35 - **ISO 8601 compatible** - Works with all standard date formats 32 36 37 + ## Performance 38 + 39 + cron-fast is designed for speed and efficiency. Here's how it compares to popular alternatives: 40 + 41 + > Tested with cron-fast v3.1.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0 on Node.js v22.18.0 42 + 43 + | Operation | cron-fast | croner | cron-parser | cron-schedule | 44 + | ------------ | --------------- | --------- | ----------- | ------------- | 45 + | Next run | **671k ops/s** | 25k ops/s | 28k ops/s | 287k ops/s | 46 + | Previous run | **856k ops/s** | 31k ops/s | 35k ops/s | 332k ops/s | 47 + | Validation | **1785k ops/s** | 33k ops/s | 90k ops/s | 452k ops/s | 48 + | Parsing | **1765k ops/s** | 32k ops/s | 90k ops/s | 427k ops/s | 49 + 50 + See [detailed benchmarks](docs/benchmark-comparison.md) (including Deno and Bun runtimes) for more information. 51 + 52 + Run benchmarks yourself: `pnpm benchmark` 53 + 33 54 ## Installation 34 55 35 56 ```bash 36 - # Node.js (npm) 37 57 npm install cron-fast 38 58 39 - # Node.js (pnpm) 40 59 pnpm add cron-fast 41 60 42 - # Node.js (yarn) 43 - yarn add cron-fast 44 - 45 - # Deno (JSR) 46 61 deno add jsr:@kbilkis/cron-fast 47 62 48 - # Bun 49 63 bun add cron-fast 50 64 51 65 # Any runtime (JSR) ··· 201 215 202 216 ## Bundle Size 203 217 204 - cron-fast is extremely lightweight and fully tree-shakeable. Here are the actual bundle sizes for different import scenarios (tested with v3.0.0): 218 + cron-fast is extremely lightweight and fully tree-shakeable. Here are the actual bundle sizes for different import scenarios (tested with v3.1.0): 205 219 206 220 | Import | Raw | Minified | Gzipped | 207 221 | ------------------------------------------------------ | -------- | -------- | ----------- | ··· 229 243 import * as cron from "cron-fast"; 230 244 ``` 231 245 246 + ## CLI 247 + 248 + `npx cron-fast <expression> [options]` 249 + 250 + Available flags: `--next <n>`, `--prev <n>`, `--tz <zone>`, `--from <date>`, `--describe`, `--validate`, `--match <date>`, `--json`, `--help`. 251 + 252 + ```bash 253 + npx cron-fast "0 9 * * 1-5" --next 5 --tz America/New_York 254 + npx cron-fast "*/15 * * * *" --describe 255 + npx cron-fast "0 0 * * *" --validate && echo "Valid!" 256 + npx cron-fast "0 9 * * *" --match 2026-03-16T09:00:00Z 257 + npx cron-fast "0 0 1 * *" --next 5 --json | jq '.runs[]' 258 + ``` 259 + 232 260 ## Advanced Usage 233 261 234 262 ### Working with Timezones ··· 243 271 // Display in any timezone 244 272 console.log(next.toLocaleString("en-US", { timeZone: "America/New_York" })); 245 273 // "3/15/2026, 9:00:00 AM" 246 - ``` 247 - 248 - ### Multiple Executions 249 - 250 - ```typescript 251 - // Get next 10 runs 252 - const runs = nextRuns("0 */6 * * *", 10); // Every 6 hours 253 - 254 - // With timezone 255 - const runsNY = nextRuns("0 9 * * 1-5", 5, { timezone: "America/New_York" }); 256 - // Next 5 weekday mornings in New York 257 274 ``` 258 275 259 276 ### Validation and Parsing ··· 309 326 - **Daylight saving time**: Use IANA timezone names (like "America/New_York") instead of abbreviations (like "EST") 310 327 - **Day 0 and 7**: Both represent Sunday in the day-of-week field 311 328 - **Ranges are inclusive**: `1-5` includes both 1 and 5 312 - 313 - ## Performance 314 - 315 - cron-fast is designed for speed and efficiency. Here's how it compares to popular alternatives: 316 - 317 - > 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 318 - 319 - | Operation | cron-fast | croner | cron-parser | cron-schedule | 320 - | ------------ | --------------- | --------- | ----------- | ------------- | 321 - | Next run | **911k ops/s** | 31k ops/s | 34k ops/s | 352k ops/s | 322 - | Previous run | **1003k ops/s** | 32k ops/s | 39k ops/s | 399k ops/s | 323 - | Validation | **1958k ops/s** | 34k ops/s | 97k ops/s | 462k ops/s | 324 - | Parsing | **1982k ops/s** | 35k ops/s | 98k ops/s | 469k ops/s | 325 - 326 - See [detailed benchmarks](docs/benchmark-comparison.md) (including Deno and Bun runtimes) for more information. 327 - 328 - Run benchmarks yourself: `pnpm benchmark` 329 329 330 330 ## Contributing 331 331
+47 -47
docs/benchmark-comparison-bun.md
··· 1 1 # Benchmark 2 2 3 - > Tested with bun v1.3.9, cron-fast v3.0.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 3 + > Tested with bun v1.3.9, cron-fast v3.1.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 4 4 > Tested on MacBook M1 pro 5 5 6 6 ## Performance Benchmarks ··· 11 11 12 12 | Library | Avg ops/sec | vs cron-fast | 13 13 | ------------- | ----------- | ------------ | 14 - | **cron-fast** | ~655k | baseline | 15 - | cron-schedule | ~304k | 2.2x faster | 16 - | croner | ~55k | 11.8x faster | 17 - | cron-parser | ~36k | 18.2x faster | 14 + | **cron-fast** | ~636k | baseline | 15 + | cron-schedule | ~298k | 2.1x faster | 16 + | croner | ~51k | 12.5x faster | 17 + | cron-parser | ~35k | 18.0x faster | 18 18 19 19 ### Previous Execution Time 20 20 21 21 | Library | Avg ops/sec | vs cron-fast | 22 22 | ------------- | ----------- | ------------ | 23 - | **cron-fast** | ~616k | baseline | 24 - | cron-schedule | ~311k | 2.0x faster | 25 - | croner | ~53k | 11.5x faster | 26 - | cron-parser | ~38k | 16.0x faster | 23 + | **cron-fast** | ~588k | baseline | 24 + | cron-schedule | ~302k | 1.9x faster | 25 + | croner | ~52k | 11.3x faster | 26 + | cron-parser | ~38k | 15.3x faster | 27 27 28 28 ### Validation 29 29 30 30 | Library | Avg ops/sec | vs cron-fast | 31 31 | ------------- | ----------- | ------------ | 32 - | **cron-fast** | ~814k | baseline | 33 - | cron-validate | ~941k | 1.2x slower | 34 - | cron-schedule | ~345k | 2.4x faster | 35 - | cron-parser | ~117k | 6.9x faster | 36 - | croner | ~58k | 14.1x faster | 32 + | **cron-fast** | ~736k | baseline | 33 + | cron-validate | ~923k | 1.3x slower | 34 + | cron-schedule | ~336k | 2.2x faster | 35 + | cron-parser | ~114k | 6.5x faster | 36 + | croner | ~55k | 13.4x faster | 37 37 38 38 ### Parsing 39 39 40 40 | Library | Avg ops/sec | vs cron-fast | 41 41 | ------------- | ----------- | ------------ | 42 - | **cron-fast** | ~862k | baseline | 43 - | cron-validate | ~943k | 1.1x slower | 44 - | cron-schedule | ~349k | 2.5x faster | 45 - | cron-parser | ~118k | 7.3x faster | 46 - | croner | ~61k | 14.1x faster | 42 + | **cron-fast** | ~841k | baseline | 43 + | cron-validate | ~875k | 1.0x slower | 44 + | cron-schedule | ~331k | 2.5x faster | 45 + | cron-parser | ~113k | 7.4x faster | 46 + | croner | ~53k | 15.7x faster | 47 47 48 48 Run benchmarks yourself: `pnpm benchmark:bun` 49 49 ··· 53 53 54 54 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 55 55 | --------------------------- | --------: | ------------: | -----: | ----------: | 56 - | Every minute | ~1216k | ~159k ✓ | ~60k ✓ | ~37k ✓ | 57 - | Sparse: First of month | ~610k | ~415k ✓ | ~58k ✓ | ~21k ✓ | 58 - | Sparse: 31st (skips months) | ~600k | ~420k ✓ | ~56k ✓ | ~9k ✓ | 59 - | Step: Every 15 minutes | ~564k | ~203k ✓ | ~58k ✓ | ~62k ✓ | 60 - | Specific: 9 AM daily | ~699k | ~259k ✓ | ~60k ✓ | ~44k ✓ | 61 - | OR-mode: 15th OR Monday | ~431k | ~413k | ~49k ✓ | ~38k ✓ | 62 - | Weekdays: Mon-Fri 9 AM | ~467k | ~258k ✓ | ~45k ✓ | ~41k ✓ | 56 + | Every minute | ~1168k | ~149k ✓ | ~52k ✓ | ~35k ✓ | 57 + | Sparse: First of month | ~595k | ~399k ✓ | ~50k ✓ | ~21k ✓ | 58 + | Sparse: 31st (skips months) | ~533k | ~390k ✓ | ~49k ✓ | ~7k ✓ | 59 + | Step: Every 15 minutes | ~511k | ~203k ✓ | ~54k ✓ | ~61k ✓ | 60 + | Specific: 9 AM daily | ~722k | ~243k ✓ | ~55k ✓ | ~41k ✓ | 61 + | OR-mode: 15th OR Monday | ~450k | ~454k | ~50k ✓ | ~39k ✓ | 62 + | Weekdays: Mon-Fri 9 AM | ~474k | ~246k ✓ | ~45k ✓ | ~42k ✓ | 63 63 64 64 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 65 65 ··· 67 67 68 68 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 69 69 | --------------------------- | --------: | ------------: | -----: | ----------: | 70 - | Every minute | ~697k | ~146k ✓ | ~57k ✓ | ~35k ✓ | 71 - | Sparse: First of month | ~649k | ~408k ✓ | ~55k ✓ | ~11k ✓ | 72 - | Sparse: 31st (skips months) | ~594k | ~398k ✓ | ~52k ✓ | ~10k ✓ | 73 - | Step: Every 15 minutes | ~530k | ~206k ✓ | ~51k ✓ | ~58k ✓ | 74 - | Specific: 9 AM daily | ~642k | ~279k ✓ | ~51k ✓ | ~47k ✓ | 75 - | OR-mode: 15th OR Monday | ~674k | ~478k ✓ | ~59k ✓ | ~61k ✓ | 76 - | Weekdays: Mon-Fri 9 AM | ~525k | ~259k ✓ | ~49k ✓ | ~47k ✓ | 70 + | Every minute | ~495k | ~145k ✓ | ~53k ✓ | ~36k ✓ | 71 + | Sparse: First of month | ~724k | ~412k ✓ | ~52k ✓ | ~10k ✓ | 72 + | Sparse: 31st (skips months) | ~541k | ~398k ✓ | ~52k ✓ | ~10k ✓ | 73 + | Step: Every 15 minutes | ~534k | ~205k ✓ | ~55k ✓ | ~62k ✓ | 74 + | Specific: 9 AM daily | ~631k | ~251k ✓ | ~52k ✓ | ~45k ✓ | 75 + | OR-mode: 15th OR Monday | ~658k | ~451k ✓ | ~53k ✓ | ~57k ✓ | 76 + | Weekdays: Mon-Fri 9 AM | ~529k | ~254k ✓ | ~46k ✓ | ~48k ✓ | 77 77 78 78 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 79 79 ··· 81 81 82 82 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 83 83 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 84 - | \* \* \* \* \* | ~432k | ~165k ✓ | ~48k ✓ | ~59k ✓ | ~949k ✗ | 85 - | 0 0 1 \* \* | ~1156k | ~441k ✓ | ~148k ✓ | ~60k ✓ | ~963k ✓ | 86 - | 0 12 31 \* \* | ~972k | ~453k ✓ | ~159k ✓ | ~57k ✓ | ~905k | 87 - | _/15 _ \* \* \* | ~611k | ~208k ✓ | ~84k ✓ | ~55k ✓ | ~946k ✗ | 88 - | 0 9 \* \* \* | ~917k | ~291k ✓ | ~105k ✓ | ~58k ✓ | ~915k | 89 - | 0 9 15 \* 1 | ~858k | ~564k ✓ | ~169k ✓ | ~59k ✓ | ~931k | 90 - | 0 9 \* \* 1-5 | ~749k | ~295k ✓ | ~109k ✓ | ~57k ✓ | ~979k ✗ | 84 + | \* \* \* \* \* | ~384k | ~154k ✓ | ~52k ✓ | ~58k ✓ | ~915k ✗ | 85 + | 0 0 1 \* \* | ~961k | ~441k ✓ | ~148k ✓ | ~53k ✓ | ~927k | 86 + | 0 12 31 \* \* | ~958k | ~446k ✓ | ~152k ✓ | ~55k ✓ | ~898k | 87 + | _/15 _ \* \* \* | ~571k | ~209k ✓ | ~77k ✓ | ~55k ✓ | ~963k ✗ | 88 + | 0 9 \* \* \* | ~818k | ~288k ✓ | ~105k ✓ | ~56k ✓ | ~919k ✗ | 89 + | 0 9 15 \* 1 | ~935k | ~536k ✓ | ~155k ✓ | ~52k ✓ | ~915k | 90 + | 0 9 \* \* 1-5 | ~522k | ~277k ✓ | ~107k ✓ | ~55k ✓ | ~926k ✗ | 91 91 92 92 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 93 93 ··· 95 95 96 96 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 97 97 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 98 - | \* \* \* \* \* | ~997k | ~162k ✓ | ~51k ✓ | ~59k ✓ | ~913k | 99 - | 0 0 1 \* \* | ~864k | ~447k ✓ | ~151k ✓ | ~61k ✓ | ~957k | 100 - | 0 12 31 \* \* | ~966k | ~455k ✓ | ~153k ✓ | ~63k ✓ | ~918k | 101 - | _/15 _ \* \* \* | ~618k | ~221k ✓ | ~86k ✓ | ~61k ✓ | ~997k ✗ | 102 - | 0 9 \* \* \* | ~964k | ~296k ✓ | ~107k ✓ | ~62k ✓ | ~914k | 103 - | 0 9 15 \* 1 | ~870k | ~562k ✓ | ~171k ✓ | ~59k ✓ | ~950k | 104 - | 0 9 \* \* 1-5 | ~756k | ~299k ✓ | ~107k ✓ | ~62k ✓ | ~951k ✗ | 98 + | \* \* \* \* \* | ~1087k | ~157k ✓ | ~52k ✓ | ~57k ✓ | ~914k ✓ | 99 + | 0 0 1 \* \* | ~960k | ~434k ✓ | ~155k ✓ | ~57k ✓ | ~948k | 100 + | 0 12 31 \* \* | ~932k | ~434k ✓ | ~154k ✓ | ~55k ✓ | ~831k ✓ | 101 + | _/15 _ \* \* \* | ~639k | ~202k ✓ | ~80k ✓ | ~56k ✓ | ~929k ✗ | 102 + | 0 9 \* \* \* | ~804k | ~280k ✓ | ~101k ✓ | ~55k ✓ | ~885k | 103 + | 0 9 15 \* 1 | ~879k | ~531k ✓ | ~156k ✓ | ~56k ✓ | ~699k ✓ | 104 + | 0 9 \* \* 1-5 | ~582k | ~281k ✓ | ~94k ✓ | ~39k ✓ | ~920k ✗ | 105 105 106 106 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower)
+47 -47
docs/benchmark-comparison-deno.md
··· 1 1 # Benchmark 2 2 3 - > Tested with deno v2.6.8, cron-fast v3.0.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 3 + > Tested with deno v2.6.8, cron-fast v3.1.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 4 4 > Tested on MacBook M1 pro 5 5 6 6 ## Performance Benchmarks ··· 11 11 12 12 | Library | Avg ops/sec | vs cron-fast | 13 13 | ------------- | ----------- | ------------ | 14 - | **cron-fast** | ~870k | baseline | 15 - | cron-schedule | ~398k | 2.2x faster | 16 - | croner | ~31k | 28.5x faster | 17 - | cron-parser | ~33k | 26.0x faster | 14 + | **cron-fast** | ~803k | baseline | 15 + | cron-schedule | ~352k | 2.3x faster | 16 + | croner | ~31k | 26.1x faster | 17 + | cron-parser | ~33k | 24.1x faster | 18 18 19 19 ### Previous Execution Time 20 20 21 21 | Library | Avg ops/sec | vs cron-fast | 22 22 | ------------- | ----------- | ------------ | 23 - | **cron-fast** | ~974k | baseline | 24 - | cron-schedule | ~414k | 2.4x faster | 25 - | croner | ~31k | 31.9x faster | 26 - | cron-parser | ~38k | 25.7x faster | 23 + | **cron-fast** | ~920k | baseline | 24 + | cron-schedule | ~380k | 2.4x faster | 25 + | croner | ~31k | 29.9x faster | 26 + | cron-parser | ~38k | 24.0x faster | 27 27 28 28 ### Validation 29 29 30 30 | Library | Avg ops/sec | vs cron-fast | 31 31 | ------------- | ----------- | ------------ | 32 - | **cron-fast** | ~1841k | baseline | 33 - | cron-validate | ~626k | 2.9x faster | 34 - | cron-schedule | ~463k | 4.0x faster | 35 - | cron-parser | ~97k | 18.9x faster | 36 - | croner | ~33k | 55.4x faster | 32 + | **cron-fast** | ~1865k | baseline | 33 + | cron-validate | ~624k | 3.0x faster | 34 + | cron-schedule | ~475k | 3.9x faster | 35 + | cron-parser | ~97k | 19.2x faster | 36 + | croner | ~33k | 56.3x faster | 37 37 38 38 ### Parsing 39 39 40 40 | Library | Avg ops/sec | vs cron-fast | 41 41 | ------------- | ----------- | ------------ | 42 - | **cron-fast** | ~1873k | baseline | 43 - | cron-validate | ~634k | 3.0x faster | 44 - | cron-schedule | ~472k | 4.0x faster | 45 - | cron-parser | ~98k | 19.2x faster | 46 - | croner | ~34k | 55.3x faster | 42 + | **cron-fast** | ~1788k | baseline | 43 + | cron-validate | ~607k | 2.9x faster | 44 + | cron-schedule | ~471k | 3.8x faster | 45 + | cron-parser | ~96k | 18.6x faster | 46 + | croner | ~33k | 54.7x faster | 47 47 48 48 Run benchmarks yourself: `pnpm benchmark:deno` 49 49 ··· 53 53 54 54 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 55 55 | --------------------------- | --------: | ------------: | -----: | ----------: | 56 - | Every minute | ~1357k | ~148k ✓ | ~32k ✓ | ~32k ✓ | 57 - | Sparse: First of month | ~805k | ~536k ✓ | ~30k ✓ | ~18k ✓ | 58 - | Sparse: 31st (skips months) | ~737k | ~536k ✓ | ~31k ✓ | ~7k ✓ | 59 - | Step: Every 15 minutes | ~934k | ~275k ✓ | ~33k ✓ | ~55k ✓ | 60 - | Specific: 9 AM daily | ~997k | ~364k ✓ | ~31k ✓ | ~42k ✓ | 61 - | OR-mode: 15th OR Monday | ~486k | ~581k ✗ | ~28k ✓ | ~36k ✓ | 62 - | Weekdays: Mon-Fri 9 AM | ~778k | ~342k ✓ | ~29k ✓ | ~43k ✓ | 56 + | Every minute | ~1367k | ~143k ✓ | ~32k ✓ | ~32k ✓ | 57 + | Sparse: First of month | ~711k | ~470k ✓ | ~32k ✓ | ~18k ✓ | 58 + | Sparse: 31st (skips months) | ~638k | ~473k ✓ | ~31k ✓ | ~7k ✓ | 59 + | Step: Every 15 minutes | ~870k | ~260k ✓ | ~33k ✓ | ~56k ✓ | 60 + | Specific: 9 AM daily | ~934k | ~340k ✓ | ~32k ✓ | ~43k ✓ | 61 + | OR-mode: 15th OR Monday | ~427k | ~468k | ~30k ✓ | ~33k ✓ | 62 + | Weekdays: Mon-Fri 9 AM | ~675k | ~308k ✓ | ~26k ✓ | ~44k ✓ | 63 63 64 64 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 65 65 ··· 67 67 68 68 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 69 69 | --------------------------- | --------: | ------------: | -----: | ----------: | 70 - | Every minute | ~1418k | ~181k ✓ | ~31k ✓ | ~35k ✓ | 71 - | Sparse: First of month | ~988k | ~564k ✓ | ~30k ✓ | ~9k ✓ | 72 - | Sparse: 31st (skips months) | ~725k | ~508k ✓ | ~31k ✓ | ~8k ✓ | 73 - | Step: Every 15 minutes | ~911k | ~272k ✓ | ~32k ✓ | ~54k ✓ | 74 - | Specific: 9 AM daily | ~1017k | ~363k ✓ | ~30k ✓ | ~48k ✓ | 75 - | OR-mode: 15th OR Monday | ~944k | ~648k ✓ | ~30k ✓ | ~62k ✓ | 76 - | Weekdays: Mon-Fri 9 AM | ~816k | ~358k ✓ | ~29k ✓ | ~49k ✓ | 70 + | Every minute | ~1408k | ~182k ✓ | ~32k ✓ | ~36k ✓ | 71 + | Sparse: First of month | ~909k | ~509k ✓ | ~30k ✓ | ~8k ✓ | 72 + | Sparse: 31st (skips months) | ~624k | ~446k ✓ | ~29k ✓ | ~8k ✓ | 73 + | Step: Every 15 minutes | ~862k | ~264k ✓ | ~32k ✓ | ~57k ✓ | 74 + | Specific: 9 AM daily | ~960k | ~347k ✓ | ~32k ✓ | ~49k ✓ | 75 + | OR-mode: 15th OR Monday | ~894k | ~573k ✓ | ~31k ✓ | ~65k ✓ | 76 + | Weekdays: Mon-Fri 9 AM | ~786k | ~339k ✓ | ~28k ✓ | ~45k ✓ | 77 77 78 78 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 79 79 ··· 81 81 82 82 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 83 83 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 84 - | \* \* \* \* \* | ~2158k | ~198k ✓ | ~45k ✓ | ~33k ✓ | ~582k ✓ | 85 - | 0 0 1 \* \* | ~2065k | ~602k ✓ | ~125k ✓ | ~34k ✓ | ~624k ✓ | 86 - | 0 12 31 \* \* | ~1737k | ~599k ✓ | ~126k ✓ | ~33k ✓ | ~636k ✓ | 87 - | _/15 _ \* \* \* | ~1660k | ~285k ✓ | ~66k ✓ | ~34k ✓ | ~664k ✓ | 88 - | 0 9 \* \* \* | ~2176k | ~399k ✓ | ~87k ✓ | ~33k ✓ | ~608k ✓ | 89 - | 0 9 15 \* 1 | ~1736k | ~759k ✓ | ~142k ✓ | ~33k ✓ | ~661k ✓ | 90 - | 0 9 \* \* 1-5 | ~1357k | ~402k ✓ | ~90k ✓ | ~32k ✓ | ~604k ✓ | 84 + | \* \* \* \* \* | ~2213k | ~197k ✓ | ~46k ✓ | ~34k ✓ | ~599k ✓ | 85 + | 0 0 1 \* \* | ~2125k | ~625k ✓ | ~127k ✓ | ~34k ✓ | ~645k ✓ | 86 + | 0 12 31 \* \* | ~1775k | ~620k ✓ | ~126k ✓ | ~33k ✓ | ~612k ✓ | 87 + | _/15 _ \* \* \* | ~1581k | ~288k ✓ | ~67k ✓ | ~33k ✓ | ~672k ✓ | 88 + | 0 9 \* \* \* | ~2194k | ~409k ✓ | ~88k ✓ | ~33k ✓ | ~621k ✓ | 89 + | 0 9 15 \* 1 | ~1789k | ~786k ✓ | ~141k ✓ | ~34k ✓ | ~629k ✓ | 90 + | 0 9 \* \* 1-5 | ~1377k | ~398k ✓ | ~84k ✓ | ~31k ✓ | ~592k ✓ | 91 91 92 92 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 93 93 ··· 95 95 96 96 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 97 97 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 98 - | \* \* \* \* \* | ~2128k | ~200k ✓ | ~46k ✓ | ~33k ✓ | ~605k ✓ | 99 - | 0 0 1 \* \* | ~2130k | ~606k ✓ | ~128k ✓ | ~34k ✓ | ~643k ✓ | 100 - | 0 12 31 \* \* | ~1777k | ~616k ✓ | ~125k ✓ | ~34k ✓ | ~637k ✓ | 101 - | _/15 _ \* \* \* | ~1693k | ~285k ✓ | ~68k ✓ | ~34k ✓ | ~675k ✓ | 102 - | 0 9 \* \* \* | ~2241k | ~406k ✓ | ~89k ✓ | ~34k ✓ | ~615k ✓ | 103 - | 0 9 15 \* 1 | ~1767k | ~786k ✓ | ~137k ✓ | ~34k ✓ | ~652k ✓ | 104 - | 0 9 \* \* 1-5 | ~1377k | ~405k ✓ | ~90k ✓ | ~33k ✓ | ~614k ✓ | 98 + | \* \* \* \* \* | ~1912k | ~192k ✓ | ~43k ✓ | ~33k ✓ | ~580k ✓ | 99 + | 0 0 1 \* \* | ~1855k | ~611k ✓ | ~125k ✓ | ~31k ✓ | ~616k ✓ | 100 + | 0 12 31 \* \* | ~1756k | ~624k ✓ | ~125k ✓ | ~33k ✓ | ~599k ✓ | 101 + | _/15 _ \* \* \* | ~1650k | ~286k ✓ | ~68k ✓ | ~33k ✓ | ~639k ✓ | 102 + | 0 9 \* \* \* | ~2225k | ~402k ✓ | ~86k ✓ | ~33k ✓ | ~599k ✓ | 103 + | 0 9 15 \* 1 | ~1762k | ~782k ✓ | ~139k ✓ | ~33k ✓ | ~628k ✓ | 104 + | 0 9 \* \* 1-5 | ~1357k | ~403k ✓ | ~89k ✓ | ~31k ✓ | ~588k ✓ | 105 105 106 106 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower)
+47 -47
docs/benchmark-comparison-node.md
··· 1 1 # Benchmark 2 2 3 - > Tested with node v22.18.0, cron-fast v3.0.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 3 + > Tested with node v22.18.0, cron-fast v3.1.0, croner v10.0.1, cron-parser v5.5.0, cron-schedule v6.0.0, cron-validate v1.5.3 4 4 > Tested on MacBook M1 pro 5 5 6 6 ## Performance Benchmarks ··· 11 11 12 12 | Library | Avg ops/sec | vs cron-fast | 13 13 | ------------- | ----------- | ------------ | 14 - | **cron-fast** | ~911k | baseline | 15 - | cron-schedule | ~352k | 2.6x faster | 16 - | croner | ~31k | 29.6x faster | 17 - | cron-parser | ~34k | 27.1x faster | 14 + | **cron-fast** | ~671k | baseline | 15 + | cron-schedule | ~287k | 2.3x faster | 16 + | croner | ~25k | 26.5x faster | 17 + | cron-parser | ~28k | 24.3x faster | 18 18 19 19 ### Previous Execution Time 20 20 21 21 | Library | Avg ops/sec | vs cron-fast | 22 22 | ------------- | ----------- | ------------ | 23 - | **cron-fast** | ~1003k | baseline | 24 - | cron-schedule | ~399k | 2.5x faster | 25 - | croner | ~32k | 31.6x faster | 26 - | cron-parser | ~39k | 25.8x faster | 23 + | **cron-fast** | ~856k | baseline | 24 + | cron-schedule | ~332k | 2.6x faster | 25 + | croner | ~31k | 27.9x faster | 26 + | cron-parser | ~35k | 24.3x faster | 27 27 28 28 ### Validation 29 29 30 30 | Library | Avg ops/sec | vs cron-fast | 31 31 | ------------- | ----------- | ------------ | 32 - | **cron-fast** | ~1958k | baseline | 33 - | cron-validate | ~680k | 2.9x faster | 34 - | cron-schedule | ~462k | 4.2x faster | 35 - | cron-parser | ~97k | 20.2x faster | 36 - | croner | ~34k | 56.9x faster | 32 + | **cron-fast** | ~1785k | baseline | 33 + | cron-validate | ~654k | 2.7x faster | 34 + | cron-schedule | ~452k | 4.0x faster | 35 + | cron-parser | ~90k | 19.8x faster | 36 + | croner | ~33k | 54.6x faster | 37 37 38 38 ### Parsing 39 39 40 40 | Library | Avg ops/sec | vs cron-fast | 41 41 | ------------- | ----------- | ------------ | 42 - | **cron-fast** | ~1982k | baseline | 43 - | cron-validate | ~690k | 2.9x faster | 44 - | cron-schedule | ~469k | 4.2x faster | 45 - | cron-parser | ~98k | 20.2x faster | 46 - | croner | ~35k | 57.3x faster | 42 + | **cron-fast** | ~1765k | baseline | 43 + | cron-validate | ~637k | 2.8x faster | 44 + | cron-schedule | ~427k | 4.1x faster | 45 + | cron-parser | ~90k | 19.6x faster | 46 + | croner | ~32k | 55.2x faster | 47 47 48 48 Run benchmarks yourself: `pnpm benchmark` 49 49 ··· 53 53 54 54 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 55 55 | --------------------------- | --------: | ------------: | -----: | ----------: | 56 - | Every minute | ~1422k | ~180k ✓ | ~31k ✓ | ~32k ✓ | 57 - | Sparse: First of month | ~854k | ~509k ✓ | ~31k ✓ | ~17k ✓ | 58 - | Sparse: 31st (skips months) | ~806k | ~509k ✓ | ~28k ✓ | ~7k ✓ | 59 - | Step: Every 15 minutes | ~940k | ~258k ✓ | ~32k ✓ | ~54k ✓ | 60 - | Specific: 9 AM daily | ~1013k | ~356k ✓ | ~33k ✓ | ~43k ✓ | 61 - | OR-mode: 15th OR Monday | ~519k | ~549k | ~31k ✓ | ~38k ✓ | 62 - | Weekdays: Mon-Fri 9 AM | ~823k | ~104k ✓ | ~30k ✓ | ~44k ✓ | 56 + | Every minute | ~1171k | ~126k ✓ | ~19k ✓ | ~23k ✓ | 57 + | Sparse: First of month | ~589k | ~378k ✓ | ~26k ✓ | ~15k ✓ | 58 + | Sparse: 31st (skips months) | ~562k | ~374k ✓ | ~25k ✓ | ~6k ✓ | 59 + | Step: Every 15 minutes | ~697k | ~211k ✓ | ~27k ✓ | ~46k ✓ | 60 + | Specific: 9 AM daily | ~651k | ~252k ✓ | ~25k ✓ | ~32k ✓ | 61 + | OR-mode: 15th OR Monday | ~340k | ~396k ✗ | ~28k ✓ | ~32k ✓ | 62 + | Weekdays: Mon-Fri 9 AM | ~691k | ~269k ✓ | ~28k ✓ | ~40k ✓ | 63 63 64 64 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 65 65 ··· 67 67 68 68 | Test Case | cron-fast | cron-schedule | croner | cron-parser | 69 69 | --------------------------- | --------: | ------------: | -----: | ----------: | 70 - | Every minute | ~1324k | ~184k ✓ | ~31k ✓ | ~36k ✓ | 71 - | Sparse: First of month | ~1019k | ~545k ✓ | ~31k ✓ | ~9k ✓ | 72 - | Sparse: 31st (skips months) | ~786k | ~478k ✓ | ~31k ✓ | ~8k ✓ | 73 - | Step: Every 15 minutes | ~932k | ~264k ✓ | ~33k ✓ | ~56k ✓ | 74 - | Specific: 9 AM daily | ~1066k | ~365k ✓ | ~33k ✓ | ~49k ✓ | 75 - | OR-mode: 15th OR Monday | ~1027k | ~610k ✓ | ~32k ✓ | ~64k ✓ | 76 - | Weekdays: Mon-Fri 9 AM | ~864k | ~350k ✓ | ~31k ✓ | ~50k ✓ | 70 + | Every minute | ~1327k | ~164k ✓ | ~31k ✓ | ~32k ✓ | 71 + | Sparse: First of month | ~859k | ~446k ✓ | ~30k ✓ | ~8k ✓ | 72 + | Sparse: 31st (skips months) | ~620k | ~367k ✓ | ~29k ✓ | ~7k ✓ | 73 + | Step: Every 15 minutes | ~756k | ~236k ✓ | ~31k ✓ | ~51k ✓ | 74 + | Specific: 9 AM daily | ~892k | ~329k ✓ | ~32k ✓ | ~45k ✓ | 75 + | OR-mode: 15th OR Monday | ~785k | ~477k ✓ | ~31k ✓ | ~57k ✓ | 76 + | Weekdays: Mon-Fri 9 AM | ~750k | ~303k ✓ | ~30k ✓ | ~47k ✓ | 77 77 78 78 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 79 79 ··· 81 81 82 82 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 83 83 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 84 - | \* \* \* \* \* | ~2218k | ~195k ✓ | ~46k ✓ | ~35k ✓ | ~653k ✓ | 85 - | 0 0 1 \* \* | ~2141k | ~612k ✓ | ~124k ✓ | ~33k ✓ | ~673k ✓ | 86 - | 0 12 31 \* \* | ~1890k | ~628k ✓ | ~126k ✓ | ~35k ✓ | ~683k ✓ | 87 - | _/15 _ \* \* \* | ~1719k | ~280k ✓ | ~67k ✓ | ~34k ✓ | ~729k ✓ | 88 - | 0 9 \* \* \* | ~2325k | ~401k ✓ | ~89k ✓ | ~35k ✓ | ~670k ✓ | 89 - | 0 9 15 \* 1 | ~1936k | ~748k ✓ | ~138k ✓ | ~35k ✓ | ~708k ✓ | 90 - | 0 9 \* \* 1-5 | ~1481k | ~373k ✓ | ~89k ✓ | ~34k ✓ | ~647k ✓ | 84 + | \* \* \* \* \* | ~1985k | ~191k ✓ | ~44k ✓ | ~34k ✓ | ~622k ✓ | 85 + | 0 0 1 \* \* | ~2038k | ~610k ✓ | ~114k ✓ | ~33k ✓ | ~676k ✓ | 86 + | 0 12 31 \* \* | ~1772k | ~592k ✓ | ~114k ✓ | ~32k ✓ | ~653k ✓ | 87 + | _/15 _ \* \* \* | ~1506k | ~275k ✓ | ~63k ✓ | ~33k ✓ | ~687k ✓ | 88 + | 0 9 \* \* \* | ~2086k | ~387k ✓ | ~82k ✓ | ~32k ✓ | ~636k ✓ | 89 + | 0 9 15 \* 1 | ~1731k | ~724k ✓ | ~129k ✓ | ~33k ✓ | ~666k ✓ | 90 + | 0 9 \* \* 1-5 | ~1376k | ~382k ✓ | ~84k ✓ | ~32k ✓ | ~636k ✓ | 91 91 92 92 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower) 93 93 ··· 95 95 96 96 | Test Case | cron-fast | cron-schedule | cron-parser | croner | cron-validate | 97 97 | --------------- | --------: | ------------: | ----------: | -----: | ------------: | 98 - | \* \* \* \* \* | ~2235k | ~193k ✓ | ~47k ✓ | ~35k ✓ | ~668k ✓ | 99 - | 0 0 1 \* \* | ~2285k | ~628k ✓ | ~126k ✓ | ~34k ✓ | ~695k ✓ | 100 - | 0 12 31 \* \* | ~1895k | ~625k ✓ | ~125k ✓ | ~35k ✓ | ~697k ✓ | 101 - | _/15 _ \* \* \* | ~1722k | ~282k ✓ | ~68k ✓ | ~35k ✓ | ~731k ✓ | 102 - | 0 9 \* \* \* | ~2323k | ~400k ✓ | ~88k ✓ | ~35k ✓ | ~674k ✓ | 103 - | 0 9 15 \* 1 | ~1915k | ~759k ✓ | ~142k ✓ | ~34k ✓ | ~708k ✓ | 104 - | 0 9 \* \* 1-5 | ~1495k | ~397k ✓ | ~90k ✓ | ~34k ✓ | ~660k ✓ | 98 + | \* \* \* \* \* | ~2057k | ~179k ✓ | ~41k ✓ | ~32k ✓ | ~628k ✓ | 99 + | 0 0 1 \* \* | ~1987k | ~588k ✓ | ~115k ✓ | ~30k ✓ | ~671k ✓ | 100 + | 0 12 31 \* \* | ~1752k | ~592k ✓ | ~117k ✓ | ~33k ✓ | ~603k ✓ | 101 + | _/15 _ \* \* \* | ~1603k | ~279k ✓ | ~64k ✓ | ~34k ✓ | ~692k ✓ | 102 + | 0 9 \* \* \* | ~1919k | ~366k ✓ | ~81k ✓ | ~33k ✓ | ~646k ✓ | 103 + | 0 9 15 \* 1 | ~1741k | ~639k ✓ | ~132k ✓ | ~33k ✓ | ~594k ✓ | 104 + | 0 9 \* \* 1-5 | ~1293k | ~348k ✓ | ~80k ✓ | ~29k ✓ | ~627k ✓ | 105 105 106 106 ✓ = cron-fast is faster (≥10% faster) | ✗ = cron-fast is slower (≥10% slower)
+8 -1
jsr.json
··· 1 1 { 2 2 "name": "@kbilkis/cron-fast", 3 - "version": "3.0.0", 3 + "version": "3.1.0", 4 4 "description": "Fast and tiny JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.", 5 5 "keywords": [ 6 6 "javascript", 7 7 "typescript", 8 8 "cron", 9 + "crontab", 9 10 "parser", 11 + "cli", 12 + "command-line", 10 13 "scheduler", 11 14 "schedule", 15 + "human-readable", 16 + "validator", 17 + "recurring", 18 + "task", 12 19 "nodejs", 13 20 "node", 14 21 "deno",
+11 -1
package.json
··· 1 1 { 2 2 "name": "cron-fast", 3 - "version": "3.0.0", 3 + "version": "3.1.0", 4 4 "description": "Fast and tiny JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.", 5 5 "keywords": [ 6 6 "browser", 7 7 "bun", 8 + "cli", 8 9 "cloudflare-workers", 10 + "command-line", 9 11 "cron", 12 + "crontab", 10 13 "deno", 11 14 "esm", 12 15 "fast", 16 + "human-readable", 13 17 "javascript", 14 18 "lightweight", 15 19 "node", 16 20 "nodejs", 17 21 "parser", 18 22 "performance", 23 + "recurring", 19 24 "schedule", 20 25 "scheduler", 26 + "task", 21 27 "timezone", 22 28 "tree-shakeable", 23 29 "typescript", 30 + "validator", 24 31 "workerd", 25 32 "zero-dependencies" 26 33 ], ··· 34 41 "funding": { 35 42 "type": "github", 36 43 "url": "https://github.com/sponsors/kbilkis" 44 + }, 45 + "bin": { 46 + "cron-fast": "./dist/cli.cjs" 37 47 }, 38 48 "files": [ 39 49 "dist",
+284
src/cli.ts
··· 1 + #!/usr/bin/env node 2 + import { nextRun, previousRun, nextRuns, isValid, parse, describe, isMatch } from "./index.js"; 3 + 4 + interface CliOptions { 5 + expression: string; 6 + next?: number; 7 + prev?: number; 8 + timezone?: string; 9 + from?: Date; 10 + describe: boolean; 11 + validate: boolean; 12 + json: boolean; 13 + match?: Date; 14 + help: boolean; 15 + } 16 + 17 + function showHelp(): void { 18 + console.log(`Usage: cron-fast <expression> [options] 19 + 20 + A command-line tool for cron-fast — fast and tiny cron parser. 21 + 22 + Options: 23 + --next <n> Show next N execution times 24 + --prev <n> Show previous N execution times 25 + --tz <zone> IANA timezone (e.g., America/New_York) 26 + --from <date> Reference date in ISO 8601 format 27 + --describe Output human-readable description 28 + --validate Exit 0 if valid, 1 if invalid (no output) 29 + --match <date> Check if date matches the expression 30 + --json Output results as JSON 31 + --help Show this help message 32 + 33 + Examples: 34 + cron-fast "0 9 * * 1-5" --next 5 --tz America/New_York 35 + cron-fast "*/15 * * * *" --describe 36 + cron-fast "0 0 1 * *" --validate 37 + cron-fast "0 9 * * *" --match 2026-03-16T09:00:00Z --tz Europe/London 38 + `); 39 + } 40 + 41 + function parseArgs(argv: string[]): CliOptions { 42 + const args = argv.slice(2); 43 + if (args.length === 0 || args.includes("--help")) { 44 + return { expression: "", help: true, describe: false, validate: false, json: false }; 45 + } 46 + 47 + // First positional argument is the cron expression 48 + let expression = ""; 49 + const remainingArgs: string[] = []; 50 + 51 + for (const arg of args) { 52 + if (!arg.startsWith("-") && !expression) { 53 + expression = arg; 54 + } else { 55 + remainingArgs.push(arg); 56 + } 57 + } 58 + 59 + const options: CliOptions = { 60 + expression, 61 + describe: false, 62 + validate: false, 63 + json: false, 64 + help: false, 65 + }; 66 + 67 + for (let i = 0; i < remainingArgs.length; i++) { 68 + const arg = remainingArgs[i]; 69 + const next = remainingArgs[i + 1]; 70 + 71 + switch (arg) { 72 + case "--next": 73 + if (next !== undefined) { 74 + options.next = parseInt(next, 10); 75 + i++; 76 + } 77 + break; 78 + case "--prev": 79 + if (next !== undefined) { 80 + options.prev = parseInt(next, 10); 81 + i++; 82 + } 83 + break; 84 + case "--tz": 85 + if (next !== undefined) { 86 + options.timezone = next; 87 + i++; 88 + } 89 + break; 90 + case "--from": 91 + if (next !== undefined) { 92 + options.from = new Date(next); 93 + i++; 94 + } 95 + break; 96 + case "--match": 97 + if (next !== undefined) { 98 + options.match = new Date(next); 99 + i++; 100 + } 101 + break; 102 + case "--describe": 103 + options.describe = true; 104 + break; 105 + case "--validate": 106 + options.validate = true; 107 + break; 108 + case "--json": 109 + options.json = true; 110 + break; 111 + case "--help": 112 + options.help = true; 113 + break; 114 + } 115 + } 116 + 117 + return options; 118 + } 119 + 120 + function formatRun(date: Date, timezone?: string): string { 121 + try { 122 + const local = timezone 123 + ? date.toLocaleString("en-US", { 124 + timeZone: timezone, 125 + weekday: "short", 126 + year: "numeric", 127 + month: "short", 128 + day: "numeric", 129 + hour: "2-digit", 130 + minute: "2-digit", 131 + second: "2-digit", 132 + hour12: false, 133 + }) 134 + : date.toLocaleString("en-US", { 135 + weekday: "short", 136 + year: "numeric", 137 + month: "short", 138 + day: "numeric", 139 + hour: "2-digit", 140 + minute: "2-digit", 141 + second: "2-digit", 142 + hour12: false, 143 + timeZone: "UTC", 144 + }); 145 + 146 + const iso = date.toISOString(); 147 + return `${iso} (${local})`; 148 + } catch { 149 + return date.toISOString(); 150 + } 151 + } 152 + 153 + function runCli(): void { 154 + const options = parseArgs(process.argv); 155 + 156 + if (options.help) { 157 + showHelp(); 158 + process.exit(0); 159 + } 160 + 161 + if (!options.expression) { 162 + console.error("Error: Missing cron expression. Try: cron-fast --help"); 163 + process.exit(1); 164 + } 165 + 166 + // Validate mode 167 + if (options.validate) { 168 + const valid = isValid(options.expression); 169 + process.exit(valid ? 0 : 1); 170 + } 171 + 172 + // Check validity first (so we can give a nice error) 173 + try { 174 + parse(options.expression); 175 + } catch (e) { 176 + const message = e instanceof Error ? e.message : String(e); 177 + if (options.json) { 178 + console.log(JSON.stringify({ error: message, expression: options.expression }, null, 2)); 179 + } else { 180 + console.error(`Error: ${message}`); 181 + } 182 + process.exit(1); 183 + } 184 + 185 + const cronOptions = { 186 + timezone: options.timezone, 187 + from: options.from, 188 + }; 189 + 190 + // --match mode 191 + if (options.match !== undefined) { 192 + const result = isMatch( 193 + options.expression, 194 + options.match, 195 + options.timezone ? { timezone: options.timezone } : undefined, 196 + ); 197 + if (options.json) { 198 + console.log( 199 + JSON.stringify( 200 + { 201 + expression: options.expression, 202 + date: options.match.toISOString(), 203 + timezone: options.timezone, 204 + matches: result, 205 + }, 206 + null, 207 + 2, 208 + ), 209 + ); 210 + } else { 211 + console.log(result ? "true" : "false"); 212 + } 213 + process.exit(0); 214 + } 215 + 216 + // --describe mode 217 + if (options.describe) { 218 + const description = describe(options.expression); 219 + if (options.json) { 220 + console.log( 221 + JSON.stringify( 222 + { 223 + expression: options.expression, 224 + description, 225 + }, 226 + null, 227 + 2, 228 + ), 229 + ); 230 + } else { 231 + console.log(description); 232 + } 233 + process.exit(0); 234 + } 235 + 236 + // Default: show next runs 237 + const count = options.next ?? options.prev ?? 1; 238 + const direction = options.prev !== undefined ? "prev" : "next"; 239 + 240 + let runs: Date[]; 241 + if (direction === "prev") { 242 + runs = []; 243 + let current = options.from; 244 + for (let i = 0; i < count; i++) { 245 + const run = previousRun(options.expression, { ...cronOptions, from: current }); 246 + runs.push(run); 247 + current = new Date(run.getTime() - 60000); 248 + } 249 + } else { 250 + runs = nextRuns(options.expression, count, cronOptions); 251 + } 252 + 253 + if (options.json) { 254 + console.log( 255 + JSON.stringify( 256 + { 257 + expression: options.expression, 258 + description: describe(options.expression), 259 + timezone: options.timezone, 260 + from: cronOptions.from?.toISOString(), 261 + direction, 262 + runs: runs.map((r) => r.toISOString()), 263 + }, 264 + null, 265 + 2, 266 + ), 267 + ); 268 + } else { 269 + const description = describe(options.expression); 270 + console.log( 271 + `${direction === "next" ? "Next" : "Previous"} ${count} run${count === 1 ? "" : "s"} for: ${options.expression}`, 272 + ); 273 + console.log(`${description}`); 274 + if (options.timezone) { 275 + console.log(`${options.timezone} timezone`); 276 + } 277 + console.log(); 278 + runs.forEach((run, i) => { 279 + console.log(`${i + 1}. ${formatRun(run, options.timezone)}`); 280 + }); 281 + } 282 + } 283 + 284 + runCli();
+1 -1
tsdown.config.ts
··· 1 1 import { defineConfig } from "tsdown"; 2 2 3 3 export default defineConfig({ 4 - entry: ["src/index.ts"], 4 + entry: ["src/index.ts", "src/cli.ts"], 5 5 format: ["cjs", "esm"], 6 6 dts: true, 7 7 sourcemap: true,