cron-fast 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kasparas Bilkis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# cron-fast
|
|
2
|
+
|
|
3
|
+
**Fast JavaScript/TypeScript cron parser with timezone support.** Works everywhere: Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Universal** - Works in Node.js, Deno, Bun, Cloudflare Workers, and browsers
|
|
8
|
+
- **Lightweight** - Zero dependencies
|
|
9
|
+
- **Fast** - Optimal field increment algorithm
|
|
10
|
+
- **Tree-shakeable** - Import only what you need
|
|
11
|
+
- **Timezone support** - Built-in timezone handling using native `Intl`
|
|
12
|
+
- **Modern** - ESM + CJS, TypeScript-first
|
|
13
|
+
- **Fully tested** - Comprehensive test coverage across all runtimes
|
|
14
|
+
- **Simple API** - Clean, intuitive interface
|
|
15
|
+
- **ISO 8601 compatible** - Works with all standard date formats
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Node.js (npm)
|
|
21
|
+
npm install cron-fast
|
|
22
|
+
|
|
23
|
+
# Node.js (pnpm)
|
|
24
|
+
pnpm add cron-fast
|
|
25
|
+
|
|
26
|
+
# Node.js (yarn)
|
|
27
|
+
yarn add cron-fast
|
|
28
|
+
|
|
29
|
+
# Deno (JSR)
|
|
30
|
+
deno add jsr:@kbilkis/cron-fast
|
|
31
|
+
|
|
32
|
+
# Bun
|
|
33
|
+
bun add cron-fast
|
|
34
|
+
|
|
35
|
+
# Any runtime (JSR)
|
|
36
|
+
npx jsr add @kbilkis/cron-fast
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { nextRun, previousRun, isValid } from "cron-fast";
|
|
43
|
+
|
|
44
|
+
// Get next execution time (UTC)
|
|
45
|
+
const next = nextRun("0 9 * * *");
|
|
46
|
+
console.log(next); // Next 9:00 AM UTC
|
|
47
|
+
|
|
48
|
+
// With timezone
|
|
49
|
+
const nextNY = nextRun("0 9 * * *", { timezone: "America/New_York" });
|
|
50
|
+
console.log(nextNY); // Next 9:00 AM Eastern Time
|
|
51
|
+
|
|
52
|
+
// Get previous execution
|
|
53
|
+
const prev = previousRun("*/15 * * * *");
|
|
54
|
+
|
|
55
|
+
// Validate expression
|
|
56
|
+
if (isValid("0 9 * * *")) {
|
|
57
|
+
console.log("Valid cron expression!");
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### `nextRun(expression, options?)`
|
|
64
|
+
|
|
65
|
+
Get the next execution time for a cron expression.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
nextRun("0 9 * * *"); // Next 9:00 AM UTC
|
|
69
|
+
nextRun("0 9 * * *", { timezone: "Europe/London" }); // Next 9:00 AM London time
|
|
70
|
+
nextRun("0 9 * * *", { from: new Date("2026-03-15") }); // Next after Mar 15, 2026
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `previousRun(expression, options?)`
|
|
74
|
+
|
|
75
|
+
Get the previous execution time.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
previousRun("0 9 * * *"); // Last 9:00 AM UTC
|
|
79
|
+
previousRun("0 9 * * *", { timezone: "Asia/Tokyo" });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `nextRuns(expression, count, options?)`
|
|
83
|
+
|
|
84
|
+
Get next N execution times.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
nextRuns("0 9 * * *", 5); // Next 5 occurrences
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `isValid(expression)`
|
|
91
|
+
|
|
92
|
+
Validate a cron expression.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
isValid("0 9 * * *"); // true
|
|
96
|
+
isValid("invalid"); // false
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `isMatch(expression, date, options?)`
|
|
100
|
+
|
|
101
|
+
Check if a date matches the cron expression.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
isMatch("0 9 * * *", new Date("2026-03-15T09:00:00Z")); // true
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `parse(expression)`
|
|
108
|
+
|
|
109
|
+
Parse a cron expression into its components.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
parse("0 9 * * 1-5");
|
|
113
|
+
// Returns: { minute: [0], hour: [9], day: [1, 2, ..., 31], month: [0, 1, 2, ..., 11], weekday: [1,2,3,4,5] }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Cron Expression Format
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
* * * * *
|
|
120
|
+
│ │ │ │ │
|
|
121
|
+
│ │ │ │ └─ Day of Week (0-7, SUN-SAT)
|
|
122
|
+
│ │ │ └─── Month (1-12, JAN-DEC)
|
|
123
|
+
│ │ └───── Day of Month (1-31)
|
|
124
|
+
│ └─────── Hour (0-23)
|
|
125
|
+
└───────── Minute (0-59)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Supported Special Characters
|
|
129
|
+
|
|
130
|
+
- `*` - Any value
|
|
131
|
+
- `,` - Value list (e.g., `1,3,5`)
|
|
132
|
+
- `-` - Range (e.g., `1-5`)
|
|
133
|
+
- `/` - Step values (e.g., `*/5`)
|
|
134
|
+
|
|
135
|
+
## ISO 8601 Date Support
|
|
136
|
+
|
|
137
|
+
cron-fast fully supports ISO 8601 date formats for input:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// All these formats work:
|
|
141
|
+
nextRun("0 9 * * *", { from: new Date("2026-03-15T14:30:00Z") }); // UTC
|
|
142
|
+
nextRun("0 9 * * *", { from: new Date("2026-03-15T09:30:00-05:00") }); // With offset
|
|
143
|
+
nextRun("0 9 * * *", { from: new Date("2026-03-15T14:30:00.500Z") }); // With milliseconds
|
|
144
|
+
|
|
145
|
+
// Different representations of the same moment produce identical results
|
|
146
|
+
const utc = new Date("2026-03-15T14:30:00Z");
|
|
147
|
+
const est = new Date("2026-03-15T09:30:00-05:00"); // Same moment
|
|
148
|
+
nextRun("0 9 * * *", { from: utc }).getTime() === nextRun("0 9 * * *", { from: est }).getTime(); // true
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Note:** All returned Date objects are in UTC (ending with `Z` in `.toISOString()`). Use `.toLocaleString()` to display in any timezone.
|
|
152
|
+
|
|
153
|
+
## Tree-Shaking
|
|
154
|
+
|
|
155
|
+
cron-fast is fully tree-shakeable. Import only what you need:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Small bundle - only validation
|
|
159
|
+
import { isValid } from "cron-fast";
|
|
160
|
+
|
|
161
|
+
// Medium bundle - one function + dependencies
|
|
162
|
+
import { nextRun } from "cron-fast";
|
|
163
|
+
|
|
164
|
+
// Full bundle - everything
|
|
165
|
+
import * as cron from "cron-fast";
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Advanced Usage
|
|
169
|
+
|
|
170
|
+
### Working with Timezones
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Cron expression is interpreted in the specified timezone
|
|
174
|
+
const next = nextRun("0 9 * * *", { timezone: "America/New_York" });
|
|
175
|
+
|
|
176
|
+
// The returned Date is always UTC internally
|
|
177
|
+
console.log(next.toISOString()); // "2026-03-15T13:00:00.000Z" (9 AM EDT = 1 PM UTC)
|
|
178
|
+
|
|
179
|
+
// Display in any timezone
|
|
180
|
+
console.log(next.toLocaleString("en-US", { timeZone: "America/New_York" }));
|
|
181
|
+
// "3/15/2026, 9:00:00 AM"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Multiple Executions
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Get next 10 runs
|
|
188
|
+
const runs = nextRuns("0 */6 * * *", 10); // Every 6 hours
|
|
189
|
+
|
|
190
|
+
// With timezone
|
|
191
|
+
const runsNY = nextRuns("0 9 * * 1-5", 5, { timezone: "America/New_York" });
|
|
192
|
+
// Next 5 weekday mornings in New York
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Validation and Parsing
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Validate before using
|
|
199
|
+
if (!isValid(userInput)) {
|
|
200
|
+
throw new Error("Invalid cron expression");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Parse to see what it means
|
|
204
|
+
const parsed = parse("*/15 9-17 * * 1-5");
|
|
205
|
+
console.log(parsed);
|
|
206
|
+
// {
|
|
207
|
+
// minute: [0, 15, 30, 45],
|
|
208
|
+
// hour: [9, 10, 11, 12, 13, 14, 15, 16, 17],
|
|
209
|
+
// day: [1-31],
|
|
210
|
+
// month: [1-12],
|
|
211
|
+
// weekday: [1, 2, 3, 4, 5]
|
|
212
|
+
// }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Check if Date Matches
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const now = new Date();
|
|
219
|
+
|
|
220
|
+
if (isMatch("0 9 * * 1-5", now)) {
|
|
221
|
+
console.log("It's 9 AM on a weekday!");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// With timezone
|
|
225
|
+
if (isMatch("0 9 * * *", now, { timezone: "America/New_York" })) {
|
|
226
|
+
console.log("It's 9 AM in New York!");
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Tips & Gotchas
|
|
231
|
+
|
|
232
|
+
- **Timezone handling**: The cron expression is interpreted in the timezone you specify, but the returned Date is always in UTC
|
|
233
|
+
- **Daylight saving time**: Use IANA timezone names (like "America/New_York") instead of abbreviations (like "EST")
|
|
234
|
+
- **Validation**: Always check `isValid()` before parsing user input
|
|
235
|
+
- **Day 0 and 7**: Both represent Sunday in the day-of-week field
|
|
236
|
+
- **Ranges are inclusive**: `1-5` includes both 1 and 5
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT - see [LICENSE](LICENSE) for details.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e={jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12},t={sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6};function n(n){let a=n.trim();if(!a)throw Error(`Cron expression cannot be empty`);let o=a.split(/\s+/);if(o.length!==5)throw Error(`Invalid cron expression: expected 5 fields, got ${o.length}`);let[s,c,l,u,d]=o,f=i(d,0,7,t).map(e=>e===7?0:e),p={minute:i(s,0,59),hour:i(c,0,23),day:i(l,1,31),month:i(u,1,12,e).map(e=>e-1),weekday:Array.from(new Set(f)).sort((e,t)=>e-t)};return r(p),p}function r(e){let t=e.day.length===31,n=e.month.length===12;if(t||n)return;let r=[31,29,31,30,31,30,31,31,30,31,30,31],i=!1;for(let t of e.month){let n=r[t];for(let t of e.day)if(t<=n){i=!0;break}if(i)break}if(!i)throw Error(`Invalid cron expression: no valid day/month combination exists`)}function i(e,t,n,r){let i=new Set;if(e===`*`){for(let e=t;e<=n;e++)i.add(e);return Array.from(i).sort((e,t)=>e-t)}let o=e.split(`,`);for(let e of o)if(e.includes(`/`)){let[o,s]=e.split(`/`),c=parseInt(s,10);if(isNaN(c)||c<=0)throw Error(`Invalid step value: ${s}`);let l=t,u=n;if(o!==`*`)if(o.includes(`-`)){let[e,t]=o.split(`-`);l=a(e,r),u=a(t,r)}else l=a(o,r);for(let e=l;e<=u;e+=c)e>=t&&e<=n&&i.add(e)}else if(e.includes(`-`)){let[o,s]=e.split(`-`),c=a(o,r),l=a(s,r);if(c>l)throw Error(`Invalid range: ${e}`);for(let e=c;e<=l;e++)e>=t&&e<=n&&i.add(e)}else{let o=a(e,r);if(o>=t&&o<=n)i.add(o);else throw Error(`Value ${o} out of range [${t}-${n}]`)}if(i.size===0)throw Error(`No valid values in field: ${e}`);return Array.from(i).sort((e,t)=>e-t)}function a(e,t){let n=e.toLowerCase();if(t&&n in t)return t[n];let r=parseInt(e,10);if(isNaN(r))throw Error(`Invalid value: ${e}`);return r}function o(e){try{return n(e),!0}catch{return!1}}function s(e,t){let n=t.getUTCMinutes(),r=t.getUTCHours(),i=t.getUTCDate(),a=t.getUTCMonth(),o=t.getUTCDay();return e.minute.includes(n)&&e.hour.includes(r)&&e.month.includes(a)&&c(e,i,o)}function c(e,t,n){let r=e.day.includes(t),i=e.weekday.includes(n),a=e.day.length===31,o=e.weekday.length===7;return!a&&!o?r||i:a?o?!0:i:r}function l(e,t){for(let n of e)if(n>=t)return n;return null}function u(e,t){for(let n=e.length-1;n>=0;n--)if(e[n]<=t)return e[n];return null}function d(e,t){return new Date(e,t+1,0).getDate()}function f(e,t){let[n,r]=e.toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[i,a,o]=n.split(`/`).map(Number),[s,c,l]=r.split(`:`).map(Number);return s===24&&(s=0),new Date(Date.UTC(o,i-1,a,s,c,l))}function p(e,t){let n=e.getUTCFullYear(),r=e.getUTCMonth(),i=e.getUTCDate(),a=e.getUTCHours(),o=e.getUTCMinutes(),s=e.getUTCSeconds(),c=Date.UTC(n,r,i,a,o,s),l=c,u=l,d=1/0;for(let e=0;e<3;e++){let[e,n]=new Date(l).toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[r,i,a]=e.split(`/`).map(Number),[o,s,f]=n.split(`:`).map(Number);o===24&&(o=0);let p=Date.UTC(a,r-1,i,o,s,f),m=Math.abs(c-p);if((m<d||m===d&&l>u)&&(d=m,u=l),p===c)return new Date(l);let h=c-p;l+=h}let f=c+3600*1e3,p=f;for(let e=0;e<2;e++){let[e,n]=new Date(p).toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[r,i,a]=e.split(`/`).map(Number),[o,s,c]=n.split(`:`).map(Number);o===24&&(o=0);let l=Date.UTC(a,r-1,i,o,s,c);if(l===f)return new Date(p);let u=f-l;p+=u}return new Date(u)}const m={next:{find:l,minute:e=>e.minute[0],hour:e=>e.hour[0],offset:1},prev:{find:u,minute:e=>e.minute.at(-1),hour:e=>e.hour.at(-1),offset:-1}};function h(e,t){let r=n(e),i=t?.from||new Date,a=t?.timezone,o=a?f(i,a):new Date(i);o.setUTCSeconds(0,0),o.setUTCMinutes(o.getUTCMinutes()+1);let s=y(r,o,`next`,a);if(!s)throw Error(`No matching time found within reasonable search window`);return s}function g(e,t){let r=n(e),i=t?.from||new Date,a=t?.timezone,o=a?f(i,a):new Date(i);o.setUTCSeconds(0,0),o.setUTCMinutes(o.getUTCMinutes()-1);let s=y(r,o,`prev`,a);if(!s)throw Error(`No matching time found within reasonable search window`);return s}function _(e,t,n){if(t<=0)return[];let r=[],i=n?.from||new Date;for(let a=0;a<t;a++){let t=h(e,{...n,from:i});r.push(t),i=new Date(t.getTime()+6e4)}return r}function v(e,t,r){return s(n(e),r?.timezone?f(t,r.timezone):new Date(t))}function y(e,t,n,r){let i=new Date(t);for(let t=0;t<1e3;t++){if(s(e,i))return r?p(i,r):i;b(e,i,n)}return null}function b(e,t,n){let r=m[n],i=t.getUTCMinutes(),a=t.getUTCHours(),o=t.getUTCDate(),s=t.getUTCMonth(),c=t.getUTCFullYear(),l=d(c,s);if(!e.month.includes(s)){x(e,t,n,s,c);return}if(!e.day.includes(o)||o>l){S(e,t,n,o,s,c,l);return}if(!e.hour.includes(a)){let i=r.find(e.hour,a+r.offset);i===null?S(e,t,n,o,s,c,l):(t.setUTCHours(i),t.setUTCMinutes(r.minute(e)));return}if(!e.minute.includes(i)){let u=r.find(e.minute,i+r.offset);if(u!==null)t.setUTCMinutes(u);else{let i=r.find(e.hour,a+r.offset);i===null?S(e,t,n,o,s,c,l):(t.setUTCHours(i),t.setUTCMinutes(r.minute(e)))}return}S(e,t,n,o,s,c,l)}function x(e,t,n,r,i){let a=m[n],o=a.find(e.month,r+a.offset);if(o!==null)C(e,t,i,o,n);else{let r=n===`next`?e.month[0]:e.month.at(-1);C(e,t,i+a.offset,r,n)}}function S(e,t,n,r,i,a,o){let s=m[n],c=s.find(e.day,r+s.offset);(n===`next`?c!==null&&c<=o:c!==null)?(t.setUTCDate(c),t.setUTCHours(s.hour(e)),t.setUTCMinutes(s.minute(e))):x(e,t,n,i,a)}function C(e,t,n,r,i){let a=m[i];t.setUTCFullYear(n),t.setUTCDate(1),t.setUTCMonth(r);let o=d(n,r);if(i===`next`){let n=l(e.day,1);t.setUTCDate(n!==null&&n<=o?n:e.day[0])}else{let a=u(e.day,o);if(a!==null)t.setUTCDate(a);else{x(e,t,i,r,n);return}}t.setUTCHours(a.hour(e)),t.setUTCMinutes(a.minute(e))}exports.isMatch=v,exports.isValid=o,exports.nextRun=h,exports.nextRuns=_,exports.parse=n,exports.previousRun=g;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/parser.ts","../src/matcher.ts","../src/timezone.ts","../src/scheduler.ts"],"sourcesContent":["import type { ParsedCron } from \"./types.js\";\n\nconst MONTH_NAMES: Record<string, number> = {\n jan: 1,\n feb: 2,\n mar: 3,\n apr: 4,\n may: 5,\n jun: 6,\n jul: 7,\n aug: 8,\n sep: 9,\n oct: 10,\n nov: 11,\n dec: 12,\n};\n\nconst WEEKDAY_NAMES: Record<string, number> = {\n sun: 0,\n mon: 1,\n tue: 2,\n wed: 3,\n thu: 4,\n fri: 5,\n sat: 6,\n};\n\n/**\n * Parse a cron expression into structured format\n *\n * Cron format: minute hour day month weekday\n * - minute: 0-59\n * - hour: 0-23\n * - day: 1-31\n * - month: 1-12 (or JAN-DEC)\n * - weekday: 0-7 (or SUN-SAT, where 0 and 7 are Sunday)\n *\n * Note: Months are converted from cron's 1-indexed format (1-12) to\n * JavaScript's 0-indexed format (0-11) for internal consistency.\n */\nexport function parse(expression: string): ParsedCron {\n const trimmed = expression.trim();\n\n if (!trimmed) {\n throw new Error(\"Cron expression cannot be empty\");\n }\n\n const parts = trimmed.split(/\\s+/);\n\n if (parts.length !== 5) {\n throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);\n }\n\n const [minuteStr, hourStr, dayStr, monthStr, weekdayStr] = parts;\n\n const weekdays = parseField(weekdayStr, 0, 7, WEEKDAY_NAMES).map((d) => (d === 7 ? 0 : d));\n\n const parsed: ParsedCron = {\n minute: parseField(minuteStr, 0, 59),\n hour: parseField(hourStr, 0, 23),\n day: parseField(dayStr, 1, 31),\n month: parseField(monthStr, 1, 12, MONTH_NAMES).map((m) => m - 1), // Convert to 0-indexed (0 = Jan, 11 = Dec)\n weekday: Array.from(new Set(weekdays)).sort((a, b) => a - b), // Dedupe and sort\n };\n\n // Validate day/month combinations\n validateDayMonthCombinations(parsed);\n\n return parsed;\n}\n\n/**\n * Validate that day/month combinations are possible\n * Rejects expressions like \"0 0 31 2 *\" (Feb 31) or \"0 0 30 2 *\" (Feb 30)\n */\nfunction validateDayMonthCombinations(parsed: ParsedCron): void {\n // If day or month is wildcard, no validation needed\n const dayIsWildcard = parsed.day.length === 31;\n const monthIsWildcard = parsed.month.length === 12;\n\n if (dayIsWildcard || monthIsWildcard) {\n return;\n }\n\n // Days in each month (0-indexed: 0=Jan, 11=Dec)\n // February can have 29 days in leap years\n const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\n // Check if any specified month can accommodate any specified day\n let hasValidCombination = false;\n\n for (const month of parsed.month) {\n const maxDaysInMonth = daysInMonth[month];\n\n for (const day of parsed.day) {\n if (day <= maxDaysInMonth) {\n hasValidCombination = true;\n break;\n }\n }\n\n if (hasValidCombination) {\n break;\n }\n }\n\n if (!hasValidCombination) {\n throw new Error(`Invalid cron expression: no valid day/month combination exists`);\n }\n}\n\n/**\n * Parse a single cron field (e.g., star-slash-5, 1-10, 1,3,5)\n */\nfunction parseField(\n field: string,\n min: number,\n max: number,\n names?: Record<string, number>,\n): number[] {\n const values = new Set<number>();\n\n // Handle wildcard\n if (field === \"*\") {\n for (let i = min; i <= max; i++) {\n values.add(i);\n }\n return Array.from(values).sort((a, b) => a - b);\n }\n\n // Split by comma for multiple values\n const parts = field.split(\",\");\n\n for (const part of parts) {\n // Handle step values (e.g., star-slash-5 or 10-20/2)\n if (part.includes(\"/\")) {\n const [range, stepStr] = part.split(\"/\");\n const step = parseInt(stepStr, 10);\n\n if (isNaN(step) || step <= 0) {\n throw new Error(`Invalid step value: ${stepStr}`);\n }\n\n let start = min;\n let end = max;\n\n if (range !== \"*\") {\n if (range.includes(\"-\")) {\n const [startStr, endStr] = range.split(\"-\");\n start = parseValue(startStr, names);\n end = parseValue(endStr, names);\n } else {\n start = parseValue(range, names);\n }\n }\n\n for (let i = start; i <= end; i += step) {\n if (i >= min && i <= max) {\n values.add(i);\n }\n }\n }\n // Handle ranges (e.g., 1-5)\n else if (part.includes(\"-\")) {\n const [startStr, endStr] = part.split(\"-\");\n const start = parseValue(startStr, names);\n const end = parseValue(endStr, names);\n\n if (start > end) {\n throw new Error(`Invalid range: ${part}`);\n }\n\n for (let i = start; i <= end; i++) {\n if (i >= min && i <= max) {\n values.add(i);\n }\n }\n }\n // Handle single values\n else {\n const value = parseValue(part, names);\n if (value >= min && value <= max) {\n values.add(value);\n } else {\n throw new Error(`Value ${value} out of range [${min}-${max}]`);\n }\n }\n }\n\n if (values.size === 0) {\n throw new Error(`No valid values in field: ${field}`);\n }\n\n return Array.from(values).sort((a, b) => a - b);\n}\n\n/**\n * Parse a single value (number or name)\n */\nfunction parseValue(value: string, names?: Record<string, number>): number {\n const lower = value.toLowerCase();\n\n if (names && lower in names) {\n return names[lower];\n }\n\n const num = parseInt(value, 10);\n if (isNaN(num)) {\n throw new Error(`Invalid value: ${value}`);\n }\n\n return num;\n}\n\n/**\n * Validate a cron expression\n */\nexport function isValid(expression: string): boolean {\n try {\n parse(expression);\n return true;\n } catch {\n return false;\n }\n}\n","import type { ParsedCron } from \"./types.js\";\n\n/**\n * Check if a date matches the cron expression\n */\nexport function matches(parsed: ParsedCron, date: Date): boolean {\n const minute = date.getUTCMinutes();\n const hour = date.getUTCHours();\n const day = date.getUTCDate();\n const month = date.getUTCMonth(); // 0-indexed (0 = Jan, 11 = Dec)\n const weekday = date.getUTCDay();\n\n // Check if all fields match\n return (\n parsed.minute.includes(minute) &&\n parsed.hour.includes(hour) &&\n parsed.month.includes(month) &&\n matchesDayOrWeekday(parsed, day, weekday)\n );\n}\n\n/**\n * Day-of-month and day-of-week use OR logic by default\n * If both are restricted (not *), match either one\n */\nfunction matchesDayOrWeekday(parsed: ParsedCron, day: number, weekday: number): boolean {\n const dayMatches = parsed.day.includes(day);\n const weekdayMatches = parsed.weekday.includes(weekday);\n\n // If both are wildcards (all values), both match\n const dayIsWildcard = parsed.day.length === 31;\n const weekdayIsWildcard = parsed.weekday.length === 7;\n\n // If both are restricted, use OR logic (standard cron behavior)\n if (!dayIsWildcard && !weekdayIsWildcard) {\n return dayMatches || weekdayMatches;\n }\n\n // If only one is restricted, it must match\n if (!dayIsWildcard) {\n return dayMatches;\n }\n if (!weekdayIsWildcard) {\n return weekdayMatches;\n }\n\n // Both wildcards, always matches\n return true;\n}\n\n/**\n * Find the next value in a sorted array that is >= target\n * Returns null if no such value exists\n *\n * @param values - MUST be sorted in ascending order\n * @param target - The minimum value to find\n */\nexport function findNext(values: number[], target: number): number | null {\n for (const value of values) {\n if (value >= target) {\n return value;\n }\n }\n return null;\n}\n\n/**\n * Find the previous value in a sorted array that is <= target\n * Returns null if no such value exists\n *\n * @param values - MUST be sorted in ascending order\n * @param target - The maximum value to find\n */\nexport function findPrevious(values: number[], target: number): number | null {\n for (let i = values.length - 1; i >= 0; i--) {\n if (values[i] <= target) {\n return values[i];\n }\n }\n return null;\n}\n\n/**\n * Get the number of days in a month\n *\n * @param year - The year\n * @param month - The month (0-indexed: 0 = January, 11 = December)\n * @returns The number of days in the month\n */\nexport function getDaysInMonth(year: number, month: number): number {\n // Create date for first day of next month, then go back one day\n return new Date(year, month + 1, 0).getDate();\n}\n","/** Convert a UTC date to wall-clock time in the target timezone */\nexport function convertToTimezone(date: Date, timezone: string): Date {\n // Format the date in the target timezone\n const str = date.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n // Parse formatted string: \"MM/DD/YYYY, HH:mm:ss\"\n const [datePart, timePart] = str.split(\", \");\n const [month, day, year] = datePart.split(\"/\").map(Number);\n let [hour, minute, second] = timePart.split(\":\").map(Number);\n\n if (hour === 24) hour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n return new Date(Date.UTC(year, month - 1, day, hour, minute, second));\n}\n\n/**\n * Convert a timezone-local date back to UTC (inverse of convertToTimezone).\n *\n * Note: During DST fall-back, multiple UTC times map to the same wall-clock time.\n * The result is implementation-defined. Avoid scheduling during DST transition hours\n * for predictable behavior.\n */\nexport function convertFromTimezone(date: Date, timezone: string): Date {\n const targetYear = date.getUTCFullYear();\n const targetMonth = date.getUTCMonth();\n const targetDay = date.getUTCDate();\n const targetHour = date.getUTCHours();\n const targetMinute = date.getUTCMinutes();\n const targetSecond = date.getUTCSeconds();\n\n // Target time as a comparable number (for checking if we found it)\n const targetTime = Date.UTC(\n targetYear,\n targetMonth,\n targetDay,\n targetHour,\n targetMinute,\n targetSecond,\n );\n\n // Start with a guess: interpret the wall-clock time as UTC\n let guess = targetTime;\n let bestGuess = guess;\n let bestDiff = Infinity;\n\n // Iteratively refine the guess (usually converges in 1-2 iterations)\n for (let i = 0; i < 3; i++) {\n const testDate = new Date(guess);\n const testStr = testDate.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n // Parse what wall-clock time this guess produces\n const [testDatePart, testTimePart] = testStr.split(\", \");\n const [testMonth, testDay, testYear] = testDatePart.split(\"/\").map(Number);\n let [testHour, testMinute, testSecond] = testTimePart.split(\":\").map(Number);\n\n if (testHour === 24) testHour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n const gotTime = Date.UTC(testYear, testMonth - 1, testDay, testHour, testMinute, testSecond);\n\n // Track the best guess (closest to target, but prefer later times if equal distance)\n const diff = Math.abs(targetTime - gotTime);\n if (diff < bestDiff || (diff === bestDiff && guess > bestGuess)) {\n bestDiff = diff;\n bestGuess = guess;\n }\n\n // If we got what we wanted, we're done!\n // Note: During DST fall-back, two UTC times map to the same wall-clock time.\n // This returns whichever solution the iteration converges to first (implementation-defined).\n if (gotTime === targetTime) {\n return new Date(guess);\n }\n\n // Otherwise, adjust the guess by the difference\n const adjustment = targetTime - gotTime;\n guess += adjustment;\n }\n\n // If we didn't find an exact match after 3 iterations, we're likely in a DST gap\n // (e.g., 2:30 AM during spring forward doesn't exist)\n // Try one more time: check if adding 1 hour to the target gets us closer\n const oneHourLater = targetTime + 60 * 60 * 1000;\n let guessLater = oneHourLater;\n\n for (let i = 0; i < 2; i++) {\n const testDate = new Date(guessLater);\n const testStr = testDate.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n const [testDatePart, testTimePart] = testStr.split(\", \");\n const [testMonth, testDay, testYear] = testDatePart.split(\"/\").map(Number);\n let [testHour, testMinute, testSecond] = testTimePart.split(\":\").map(Number);\n\n if (testHour === 24) testHour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n const gotTime = Date.UTC(testYear, testMonth - 1, testDay, testHour, testMinute, testSecond);\n\n if (gotTime === oneHourLater) {\n // Target time was in a DST gap, return the time after the gap\n return new Date(guessLater);\n }\n\n const adjustment = oneHourLater - gotTime;\n guessLater += adjustment;\n }\n\n // Return the best guess we found\n return new Date(bestGuess);\n}\n","import type { ParsedCron, CronOptions } from \"./types.js\";\nimport { parse } from \"./parser.js\";\nimport { matches, findNext, findPrevious, getDaysInMonth } from \"./matcher.js\";\nimport { convertToTimezone, convertFromTimezone } from \"./timezone.js\";\n\nconst MAX_ITERATIONS = 1000;\nconst ONE_MINUTE_MS = 60_000;\n\ntype Direction = \"next\" | \"prev\";\n\n/** Direction-specific operations for unified forward/backward traversal */\nconst DIR = {\n next: {\n find: findNext,\n minute: (p: ParsedCron) => p.minute[0],\n hour: (p: ParsedCron) => p.hour[0],\n offset: 1,\n },\n prev: {\n find: findPrevious,\n minute: (p: ParsedCron) => p.minute.at(-1)!,\n hour: (p: ParsedCron) => p.hour.at(-1)!,\n offset: -1,\n },\n} as const;\n\n/** Get the next execution time for a cron expression */\nexport function nextRun(expression: string, options?: CronOptions): Date {\n const parsed = parse(expression);\n const from = options?.from || new Date();\n const tz = options?.timezone;\n\n const start = tz ? convertToTimezone(from, tz) : new Date(from);\n start.setUTCSeconds(0, 0);\n start.setUTCMinutes(start.getUTCMinutes() + 1);\n\n const result = findMatch(parsed, start, \"next\", tz);\n if (!result) throw new Error(\"No matching time found within reasonable search window\");\n return result;\n}\n\n/** Get the previous execution time for a cron expression */\nexport function previousRun(expression: string, options?: CronOptions): Date {\n const parsed = parse(expression);\n const from = options?.from || new Date();\n const tz = options?.timezone;\n\n const start = tz ? convertToTimezone(from, tz) : new Date(from);\n start.setUTCSeconds(0, 0);\n start.setUTCMinutes(start.getUTCMinutes() - 1);\n\n const result = findMatch(parsed, start, \"prev\", tz);\n if (!result) throw new Error(\"No matching time found within reasonable search window\");\n return result;\n}\n\n/** Get next N execution times */\nexport function nextRuns(expression: string, count: number, options?: CronOptions): Date[] {\n if (count <= 0) return [];\n\n const results: Date[] = [];\n let current = options?.from || new Date();\n\n for (let i = 0; i < count; i++) {\n const next = nextRun(expression, { ...options, from: current });\n results.push(next);\n current = new Date(next.getTime() + ONE_MINUTE_MS);\n }\n return results;\n}\n\n/** Check if a date matches the cron expression */\nexport function isMatch(\n expression: string,\n date: Date,\n options?: Pick<CronOptions, \"timezone\">,\n): boolean {\n const parsed = parse(expression);\n const checkDate = options?.timezone ? convertToTimezone(date, options.timezone) : new Date(date);\n return matches(parsed, checkDate);\n}\n\n/** Find matching time using smart field-increment algorithm */\nfunction findMatch(parsed: ParsedCron, start: Date, dir: Direction, tz?: string): Date | null {\n const current = new Date(start);\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n if (matches(parsed, current)) {\n return tz ? convertFromTimezone(current, tz) : current;\n }\n advanceDate(parsed, current, dir);\n }\n return null;\n}\n\n/**\n * Advance date to next/prev candidate time by mutating the date in place.\n *\n * Algorithm:\n * 1. Check fields from LARGEST (month) to SMALLEST (minute)\n * 2. When a field doesn't match, jump to the next valid value for that field\n * 3. Reset all smaller fields to their boundary (first value for 'next', last for 'prev')\n *\n * Example (direction='next', cron='0 9 * * *' meaning 9:00 AM daily):\n * Current: March 15, 10:30 AM\n * - Month (March)? ✓ matches\n * - Day (15)? ✓ matches\n * - Hour (10)? ✗ not in [9] → no next hour today → cascade to next day\n * - Result: March 16, 9:00 AM\n *\n * @param parsed - The parsed cron expression\n * @param date - The date to mutate (modified in place)\n * @param dir - Direction to advance ('next' or 'prev')\n */\nfunction advanceDate(parsed: ParsedCron, date: Date, dir: Direction): void {\n const d = DIR[dir];\n const minute = date.getUTCMinutes();\n const hour = date.getUTCHours();\n const day = date.getUTCDate();\n const month = date.getUTCMonth();\n const year = date.getUTCFullYear();\n const daysInMonth = getDaysInMonth(year, month);\n\n // Month mismatch\n if (!parsed.month.includes(month)) {\n moveToMonth(parsed, date, dir, month, year);\n return;\n }\n\n // Day mismatch\n if (!parsed.day.includes(day) || day > daysInMonth) {\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n return;\n }\n\n // Hour mismatch\n if (!parsed.hour.includes(hour)) {\n const targetHour = d.find(parsed.hour, hour + d.offset);\n if (targetHour !== null) {\n // Found valid hour in same day → reset minute to boundary\n date.setUTCHours(targetHour);\n date.setUTCMinutes(d.minute(parsed));\n } else {\n // No valid hour left today → move to next/prev day\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n }\n return;\n }\n\n // Minute mismatch\n if (!parsed.minute.includes(minute)) {\n const targetMinute = d.find(parsed.minute, minute + d.offset);\n if (targetMinute !== null) {\n // Found valid minute in same hour\n date.setUTCMinutes(targetMinute);\n } else {\n // No valid minute left → try next hour\n const targetHour = d.find(parsed.hour, hour + d.offset);\n if (targetHour !== null) {\n date.setUTCHours(targetHour);\n date.setUTCMinutes(d.minute(parsed));\n } else {\n // No valid hour left → move to next/prev day\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n }\n }\n return;\n }\n\n // Weekday mismatch: all fields match but wrong day-of-week.\n // Skip directly to next/prev day since no hour/minute on this day can match.\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n}\n\nfunction moveToMonth(\n parsed: ParsedCron,\n date: Date,\n dir: Direction,\n currentMonth: number,\n currentYear: number,\n): void {\n const d = DIR[dir];\n const targetMonth = d.find(parsed.month, currentMonth + d.offset);\n\n if (targetMonth !== null) {\n resetToMonthBoundary(parsed, date, currentYear, targetMonth, dir);\n } else {\n const boundaryMonth = dir === \"next\" ? parsed.month[0] : parsed.month.at(-1)!;\n resetToMonthBoundary(parsed, date, currentYear + d.offset, boundaryMonth, dir);\n }\n}\n\nfunction moveToDay(\n parsed: ParsedCron,\n date: Date,\n dir: Direction,\n currentDay: number,\n currentMonth: number,\n currentYear: number,\n daysInMonth: number,\n): void {\n const d = DIR[dir];\n const targetDay = d.find(parsed.day, currentDay + d.offset);\n const dayIsValid =\n dir === \"next\" ? targetDay !== null && targetDay <= daysInMonth : targetDay !== null;\n\n if (dayIsValid) {\n date.setUTCDate(targetDay!);\n date.setUTCHours(d.hour(parsed));\n date.setUTCMinutes(d.minute(parsed));\n } else {\n moveToMonth(parsed, date, dir, currentMonth, currentYear);\n }\n}\n\nfunction resetToMonthBoundary(\n parsed: ParsedCron,\n date: Date,\n year: number,\n month: number,\n dir: Direction,\n): void {\n const d = DIR[dir];\n date.setUTCFullYear(year);\n date.setUTCDate(1);\n date.setUTCMonth(month);\n\n const daysInMonth = getDaysInMonth(year, month);\n\n if (dir === \"next\") {\n const validDay = findNext(parsed.day, 1);\n date.setUTCDate(validDay !== null && validDay <= daysInMonth ? validDay : parsed.day[0]);\n } else {\n const prevDay = findPrevious(parsed.day, daysInMonth);\n if (prevDay !== null) {\n date.setUTCDate(prevDay);\n } else {\n // No valid day in this month, move to previous month\n moveToMonth(parsed, date, dir, month, year);\n return;\n }\n }\n\n date.setUTCHours(d.hour(parsed));\n date.setUTCMinutes(d.minute(parsed));\n}\n"],"mappings":"mEAEA,MAAM,EAAsC,CAC1C,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,GACL,IAAK,GACL,IAAK,GACN,CAEK,EAAwC,CAC5C,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACL,IAAK,EACN,CAeD,SAAgB,EAAM,EAAgC,CACpD,IAAM,EAAU,EAAW,MAAM,CAEjC,GAAI,CAAC,EACH,MAAU,MAAM,kCAAkC,CAGpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,CAElC,GAAI,EAAM,SAAW,EACnB,MAAU,MAAM,mDAAmD,EAAM,SAAS,CAGpF,GAAM,CAAC,EAAW,EAAS,EAAQ,EAAU,GAAc,EAErD,EAAW,EAAW,EAAY,EAAG,EAAG,EAAc,CAAC,IAAK,GAAO,IAAM,EAAI,EAAI,EAAG,CAEpF,EAAqB,CACzB,OAAQ,EAAW,EAAW,EAAG,GAAG,CACpC,KAAM,EAAW,EAAS,EAAG,GAAG,CAChC,IAAK,EAAW,EAAQ,EAAG,GAAG,CAC9B,MAAO,EAAW,EAAU,EAAG,GAAI,EAAY,CAAC,IAAK,GAAM,EAAI,EAAE,CACjE,QAAS,MAAM,KAAK,IAAI,IAAI,EAAS,CAAC,CAAC,MAAM,EAAG,IAAM,EAAI,EAAE,CAC7D,CAKD,OAFA,EAA6B,EAAO,CAE7B,EAOT,SAAS,EAA6B,EAA0B,CAE9D,IAAM,EAAgB,EAAO,IAAI,SAAW,GACtC,EAAkB,EAAO,MAAM,SAAW,GAEhD,GAAI,GAAiB,EACnB,OAKF,IAAM,EAAc,CAAC,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,GAAG,CAGhE,EAAsB,GAE1B,IAAK,IAAM,KAAS,EAAO,MAAO,CAChC,IAAM,EAAiB,EAAY,GAEnC,IAAK,IAAM,KAAO,EAAO,IACvB,GAAI,GAAO,EAAgB,CACzB,EAAsB,GACtB,MAIJ,GAAI,EACF,MAIJ,GAAI,CAAC,EACH,MAAU,MAAM,iEAAiE,CAOrF,SAAS,EACP,EACA,EACA,EACA,EACU,CACV,IAAM,EAAS,IAAI,IAGnB,GAAI,IAAU,IAAK,CACjB,IAAK,IAAI,EAAI,EAAK,GAAK,EAAK,IAC1B,EAAO,IAAI,EAAE,CAEf,OAAO,MAAM,KAAK,EAAO,CAAC,MAAM,EAAG,IAAM,EAAI,EAAE,CAIjD,IAAM,EAAQ,EAAM,MAAM,IAAI,CAE9B,IAAK,IAAM,KAAQ,EAEjB,GAAI,EAAK,SAAS,IAAI,CAAE,CACtB,GAAM,CAAC,EAAO,GAAW,EAAK,MAAM,IAAI,CAClC,EAAO,SAAS,EAAS,GAAG,CAElC,GAAI,MAAM,EAAK,EAAI,GAAQ,EACzB,MAAU,MAAM,uBAAuB,IAAU,CAGnD,IAAI,EAAQ,EACR,EAAM,EAEV,GAAI,IAAU,IACZ,GAAI,EAAM,SAAS,IAAI,CAAE,CACvB,GAAM,CAAC,EAAU,GAAU,EAAM,MAAM,IAAI,CAC3C,EAAQ,EAAW,EAAU,EAAM,CACnC,EAAM,EAAW,EAAQ,EAAM,MAE/B,EAAQ,EAAW,EAAO,EAAM,CAIpC,IAAK,IAAI,EAAI,EAAO,GAAK,EAAK,GAAK,EAC7B,GAAK,GAAO,GAAK,GACnB,EAAO,IAAI,EAAE,SAKV,EAAK,SAAS,IAAI,CAAE,CAC3B,GAAM,CAAC,EAAU,GAAU,EAAK,MAAM,IAAI,CACpC,EAAQ,EAAW,EAAU,EAAM,CACnC,EAAM,EAAW,EAAQ,EAAM,CAErC,GAAI,EAAQ,EACV,MAAU,MAAM,kBAAkB,IAAO,CAG3C,IAAK,IAAI,EAAI,EAAO,GAAK,EAAK,IACxB,GAAK,GAAO,GAAK,GACnB,EAAO,IAAI,EAAE,KAKd,CACH,IAAM,EAAQ,EAAW,EAAM,EAAM,CACrC,GAAI,GAAS,GAAO,GAAS,EAC3B,EAAO,IAAI,EAAM,MAEjB,MAAU,MAAM,SAAS,EAAM,iBAAiB,EAAI,GAAG,EAAI,GAAG,CAKpE,GAAI,EAAO,OAAS,EAClB,MAAU,MAAM,6BAA6B,IAAQ,CAGvD,OAAO,MAAM,KAAK,EAAO,CAAC,MAAM,EAAG,IAAM,EAAI,EAAE,CAMjD,SAAS,EAAW,EAAe,EAAwC,CACzE,IAAM,EAAQ,EAAM,aAAa,CAEjC,GAAI,GAAS,KAAS,EACpB,OAAO,EAAM,GAGf,IAAM,EAAM,SAAS,EAAO,GAAG,CAC/B,GAAI,MAAM,EAAI,CACZ,MAAU,MAAM,kBAAkB,IAAQ,CAG5C,OAAO,EAMT,SAAgB,EAAQ,EAA6B,CACnD,GAAI,CAEF,OADA,EAAM,EAAW,CACV,QACD,CACN,MAAO,ICzNX,SAAgB,EAAQ,EAAoB,EAAqB,CAC/D,IAAM,EAAS,EAAK,eAAe,CAC7B,EAAO,EAAK,aAAa,CACzB,EAAM,EAAK,YAAY,CACvB,EAAQ,EAAK,aAAa,CAC1B,EAAU,EAAK,WAAW,CAGhC,OACE,EAAO,OAAO,SAAS,EAAO,EAC9B,EAAO,KAAK,SAAS,EAAK,EAC1B,EAAO,MAAM,SAAS,EAAM,EAC5B,EAAoB,EAAQ,EAAK,EAAQ,CAQ7C,SAAS,EAAoB,EAAoB,EAAa,EAA0B,CACtF,IAAM,EAAa,EAAO,IAAI,SAAS,EAAI,CACrC,EAAiB,EAAO,QAAQ,SAAS,EAAQ,CAGjD,EAAgB,EAAO,IAAI,SAAW,GACtC,EAAoB,EAAO,QAAQ,SAAW,EAgBpD,MAbI,CAAC,GAAiB,CAAC,EACd,GAAc,EAIlB,EAGA,EAKE,GAJE,EAHA,EAiBX,SAAgB,EAAS,EAAkB,EAA+B,CACxE,IAAK,IAAM,KAAS,EAClB,GAAI,GAAS,EACX,OAAO,EAGX,OAAO,KAUT,SAAgB,EAAa,EAAkB,EAA+B,CAC5E,IAAK,IAAI,EAAI,EAAO,OAAS,EAAG,GAAK,EAAG,IACtC,GAAI,EAAO,IAAM,EACf,OAAO,EAAO,GAGlB,OAAO,KAUT,SAAgB,EAAe,EAAc,EAAuB,CAElE,OAAO,IAAI,KAAK,EAAM,EAAQ,EAAG,EAAE,CAAC,SAAS,CC1F/C,SAAgB,EAAkB,EAAY,EAAwB,CAcpE,GAAM,CAAC,EAAU,GAZL,EAAK,eAAe,QAAS,CACvC,SAAU,EACV,KAAM,UACN,MAAO,UACP,IAAK,UACL,KAAM,UACN,OAAQ,UACR,OAAQ,UACR,OAAQ,GACT,CAAC,CAG+B,MAAM,KAAK,CACtC,CAAC,EAAO,EAAK,GAAQ,EAAS,MAAM,IAAI,CAAC,IAAI,OAAO,CACtD,CAAC,EAAM,EAAQ,GAAU,EAAS,MAAM,IAAI,CAAC,IAAI,OAAO,CAI5D,OAFI,IAAS,KAAI,EAAO,GAEjB,IAAI,KAAK,KAAK,IAAI,EAAM,EAAQ,EAAG,EAAK,EAAM,EAAQ,EAAO,CAAC,CAUvE,SAAgB,EAAoB,EAAY,EAAwB,CACtE,IAAM,EAAa,EAAK,gBAAgB,CAClC,EAAc,EAAK,aAAa,CAChC,EAAY,EAAK,YAAY,CAC7B,EAAa,EAAK,aAAa,CAC/B,EAAe,EAAK,eAAe,CACnC,EAAe,EAAK,eAAe,CAGnC,EAAa,KAAK,IACtB,EACA,EACA,EACA,EACA,EACA,EACD,CAGG,EAAQ,EACR,EAAY,EACZ,EAAW,IAGf,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,IAAK,CAc1B,GAAM,CAAC,EAAc,GAbJ,IAAI,KAAK,EAAM,CACP,eAAe,QAAS,CAC/C,SAAU,EACV,KAAM,UACN,MAAO,UACP,IAAK,UACL,KAAM,UACN,OAAQ,UACR,OAAQ,UACR,OAAQ,GACT,CAAC,CAG2C,MAAM,KAAK,CAClD,CAAC,EAAW,EAAS,GAAY,EAAa,MAAM,IAAI,CAAC,IAAI,OAAO,CACtE,CAAC,EAAU,EAAY,GAAc,EAAa,MAAM,IAAI,CAAC,IAAI,OAAO,CAExE,IAAa,KAAI,EAAW,GAEhC,IAAM,EAAU,KAAK,IAAI,EAAU,EAAY,EAAG,EAAS,EAAU,EAAY,EAAW,CAGtF,EAAO,KAAK,IAAI,EAAa,EAAQ,CAS3C,IARI,EAAO,GAAa,IAAS,GAAY,EAAQ,KACnD,EAAW,EACX,EAAY,GAMV,IAAY,EACd,OAAO,IAAI,KAAK,EAAM,CAIxB,IAAM,EAAa,EAAa,EAChC,GAAS,EAMX,IAAM,EAAe,EAAa,KAAU,IACxC,EAAa,EAEjB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,IAAK,CAa1B,GAAM,CAAC,EAAc,GAZJ,IAAI,KAAK,EAAW,CACZ,eAAe,QAAS,CAC/C,SAAU,EACV,KAAM,UACN,MAAO,UACP,IAAK,UACL,KAAM,UACN,OAAQ,UACR,OAAQ,UACR,OAAQ,GACT,CAAC,CAE2C,MAAM,KAAK,CAClD,CAAC,EAAW,EAAS,GAAY,EAAa,MAAM,IAAI,CAAC,IAAI,OAAO,CACtE,CAAC,EAAU,EAAY,GAAc,EAAa,MAAM,IAAI,CAAC,IAAI,OAAO,CAExE,IAAa,KAAI,EAAW,GAEhC,IAAM,EAAU,KAAK,IAAI,EAAU,EAAY,EAAG,EAAS,EAAU,EAAY,EAAW,CAE5F,GAAI,IAAY,EAEd,OAAO,IAAI,KAAK,EAAW,CAG7B,IAAM,EAAa,EAAe,EAClC,GAAc,EAIhB,OAAO,IAAI,KAAK,EAAU,CChI5B,MAMM,EAAM,CACV,KAAM,CACJ,KAAM,EACN,OAAS,GAAkB,EAAE,OAAO,GACpC,KAAO,GAAkB,EAAE,KAAK,GAChC,OAAQ,EACT,CACD,KAAM,CACJ,KAAM,EACN,OAAS,GAAkB,EAAE,OAAO,GAAG,GAAG,CAC1C,KAAO,GAAkB,EAAE,KAAK,GAAG,GAAG,CACtC,OAAQ,GACT,CACF,CAGD,SAAgB,EAAQ,EAAoB,EAA6B,CACvE,IAAM,EAAS,EAAM,EAAW,CAC1B,EAAO,GAAS,MAAQ,IAAI,KAC5B,EAAK,GAAS,SAEd,EAAQ,EAAK,EAAkB,EAAM,EAAG,CAAG,IAAI,KAAK,EAAK,CAC/D,EAAM,cAAc,EAAG,EAAE,CACzB,EAAM,cAAc,EAAM,eAAe,CAAG,EAAE,CAE9C,IAAM,EAAS,EAAU,EAAQ,EAAO,OAAQ,EAAG,CACnD,GAAI,CAAC,EAAQ,MAAU,MAAM,yDAAyD,CACtF,OAAO,EAIT,SAAgB,EAAY,EAAoB,EAA6B,CAC3E,IAAM,EAAS,EAAM,EAAW,CAC1B,EAAO,GAAS,MAAQ,IAAI,KAC5B,EAAK,GAAS,SAEd,EAAQ,EAAK,EAAkB,EAAM,EAAG,CAAG,IAAI,KAAK,EAAK,CAC/D,EAAM,cAAc,EAAG,EAAE,CACzB,EAAM,cAAc,EAAM,eAAe,CAAG,EAAE,CAE9C,IAAM,EAAS,EAAU,EAAQ,EAAO,OAAQ,EAAG,CACnD,GAAI,CAAC,EAAQ,MAAU,MAAM,yDAAyD,CACtF,OAAO,EAIT,SAAgB,EAAS,EAAoB,EAAe,EAA+B,CACzF,GAAI,GAAS,EAAG,MAAO,EAAE,CAEzB,IAAM,EAAkB,EAAE,CACtB,EAAU,GAAS,MAAQ,IAAI,KAEnC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,IAAK,CAC9B,IAAM,EAAO,EAAQ,EAAY,CAAE,GAAG,EAAS,KAAM,EAAS,CAAC,CAC/D,EAAQ,KAAK,EAAK,CAClB,EAAU,IAAI,KAAK,EAAK,SAAS,CAAG,IAAc,CAEpD,OAAO,EAIT,SAAgB,EACd,EACA,EACA,EACS,CAGT,OAAO,EAFQ,EAAM,EAAW,CACd,GAAS,SAAW,EAAkB,EAAM,EAAQ,SAAS,CAAG,IAAI,KAAK,EAAK,CAC/D,CAInC,SAAS,EAAU,EAAoB,EAAa,EAAgB,EAA0B,CAC5F,IAAM,EAAU,IAAI,KAAK,EAAM,CAE/B,IAAK,IAAI,EAAI,EAAG,EAAI,IAAgB,IAAK,CACvC,GAAI,EAAQ,EAAQ,EAAQ,CAC1B,OAAO,EAAK,EAAoB,EAAS,EAAG,CAAG,EAEjD,EAAY,EAAQ,EAAS,EAAI,CAEnC,OAAO,KAsBT,SAAS,EAAY,EAAoB,EAAY,EAAsB,CACzE,IAAM,EAAI,EAAI,GACR,EAAS,EAAK,eAAe,CAC7B,EAAO,EAAK,aAAa,CACzB,EAAM,EAAK,YAAY,CACvB,EAAQ,EAAK,aAAa,CAC1B,EAAO,EAAK,gBAAgB,CAC5B,EAAc,EAAe,EAAM,EAAM,CAG/C,GAAI,CAAC,EAAO,MAAM,SAAS,EAAM,CAAE,CACjC,EAAY,EAAQ,EAAM,EAAK,EAAO,EAAK,CAC3C,OAIF,GAAI,CAAC,EAAO,IAAI,SAAS,EAAI,EAAI,EAAM,EAAa,CAClD,EAAU,EAAQ,EAAM,EAAK,EAAK,EAAO,EAAM,EAAY,CAC3D,OAIF,GAAI,CAAC,EAAO,KAAK,SAAS,EAAK,CAAE,CAC/B,IAAM,EAAa,EAAE,KAAK,EAAO,KAAM,EAAO,EAAE,OAAO,CACnD,IAAe,KAMjB,EAAU,EAAQ,EAAM,EAAK,EAAK,EAAO,EAAM,EAAY,EAJ3D,EAAK,YAAY,EAAW,CAC5B,EAAK,cAAc,EAAE,OAAO,EAAO,CAAC,EAKtC,OAIF,GAAI,CAAC,EAAO,OAAO,SAAS,EAAO,CAAE,CACnC,IAAM,EAAe,EAAE,KAAK,EAAO,OAAQ,EAAS,EAAE,OAAO,CAC7D,GAAI,IAAiB,KAEnB,EAAK,cAAc,EAAa,KAC3B,CAEL,IAAM,EAAa,EAAE,KAAK,EAAO,KAAM,EAAO,EAAE,OAAO,CACnD,IAAe,KAKjB,EAAU,EAAQ,EAAM,EAAK,EAAK,EAAO,EAAM,EAAY,EAJ3D,EAAK,YAAY,EAAW,CAC5B,EAAK,cAAc,EAAE,OAAO,EAAO,CAAC,EAMxC,OAKF,EAAU,EAAQ,EAAM,EAAK,EAAK,EAAO,EAAM,EAAY,CAG7D,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAI,EAAI,GACR,EAAc,EAAE,KAAK,EAAO,MAAO,EAAe,EAAE,OAAO,CAEjE,GAAI,IAAgB,KAClB,EAAqB,EAAQ,EAAM,EAAa,EAAa,EAAI,KAC5D,CACL,IAAM,EAAgB,IAAQ,OAAS,EAAO,MAAM,GAAK,EAAO,MAAM,GAAG,GAAG,CAC5E,EAAqB,EAAQ,EAAM,EAAc,EAAE,OAAQ,EAAe,EAAI,EAIlF,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAI,EAAI,GACR,EAAY,EAAE,KAAK,EAAO,IAAK,EAAa,EAAE,OAAO,EAEzD,IAAQ,OAAS,IAAc,MAAQ,GAAa,EAAc,IAAc,OAGhF,EAAK,WAAW,EAAW,CAC3B,EAAK,YAAY,EAAE,KAAK,EAAO,CAAC,CAChC,EAAK,cAAc,EAAE,OAAO,EAAO,CAAC,EAEpC,EAAY,EAAQ,EAAM,EAAK,EAAc,EAAY,CAI7D,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAI,EAAI,GACd,EAAK,eAAe,EAAK,CACzB,EAAK,WAAW,EAAE,CAClB,EAAK,YAAY,EAAM,CAEvB,IAAM,EAAc,EAAe,EAAM,EAAM,CAE/C,GAAI,IAAQ,OAAQ,CAClB,IAAM,EAAW,EAAS,EAAO,IAAK,EAAE,CACxC,EAAK,WAAW,IAAa,MAAQ,GAAY,EAAc,EAAW,EAAO,IAAI,GAAG,KACnF,CACL,IAAM,EAAU,EAAa,EAAO,IAAK,EAAY,CACrD,GAAI,IAAY,KACd,EAAK,WAAW,EAAQ,KACnB,CAEL,EAAY,EAAQ,EAAM,EAAK,EAAO,EAAK,CAC3C,QAIJ,EAAK,YAAY,EAAE,KAAK,EAAO,CAAC,CAChC,EAAK,cAAc,EAAE,OAAO,EAAO,CAAC"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Options for cron execution time calculations
|
|
4
|
+
*/
|
|
5
|
+
interface CronOptions {
|
|
6
|
+
/** IANA timezone string (e.g., 'America/New_York', 'Europe/London') */
|
|
7
|
+
timezone?: string;
|
|
8
|
+
/** Reference date to calculate from (defaults to now) */
|
|
9
|
+
from?: Date;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parsed cron expression with valid values for each field
|
|
13
|
+
*
|
|
14
|
+
* Note: Internally, months are stored as 0-indexed (0 = January, 11 = December)
|
|
15
|
+
* to match JavaScript's Date object convention. The parser automatically converts
|
|
16
|
+
* from cron's 1-indexed format (1-12) to 0-indexed (0-11).
|
|
17
|
+
*/
|
|
18
|
+
interface ParsedCron {
|
|
19
|
+
minute: number[];
|
|
20
|
+
hour: number[];
|
|
21
|
+
day: number[];
|
|
22
|
+
month: number[];
|
|
23
|
+
weekday: number[];
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/scheduler.d.ts
|
|
27
|
+
/** Get the next execution time for a cron expression */
|
|
28
|
+
declare function nextRun(expression: string, options?: CronOptions): Date;
|
|
29
|
+
/** Get the previous execution time for a cron expression */
|
|
30
|
+
declare function previousRun(expression: string, options?: CronOptions): Date;
|
|
31
|
+
/** Get next N execution times */
|
|
32
|
+
declare function nextRuns(expression: string, count: number, options?: CronOptions): Date[];
|
|
33
|
+
/** Check if a date matches the cron expression */
|
|
34
|
+
declare function isMatch(expression: string, date: Date, options?: Pick<CronOptions, "timezone">): boolean;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/parser.d.ts
|
|
37
|
+
/**
|
|
38
|
+
* Parse a cron expression into structured format
|
|
39
|
+
*
|
|
40
|
+
* Cron format: minute hour day month weekday
|
|
41
|
+
* - minute: 0-59
|
|
42
|
+
* - hour: 0-23
|
|
43
|
+
* - day: 1-31
|
|
44
|
+
* - month: 1-12 (or JAN-DEC)
|
|
45
|
+
* - weekday: 0-7 (or SUN-SAT, where 0 and 7 are Sunday)
|
|
46
|
+
*
|
|
47
|
+
* Note: Months are converted from cron's 1-indexed format (1-12) to
|
|
48
|
+
* JavaScript's 0-indexed format (0-11) for internal consistency.
|
|
49
|
+
*/
|
|
50
|
+
declare function parse(expression: string): ParsedCron;
|
|
51
|
+
/**
|
|
52
|
+
* Validate a cron expression
|
|
53
|
+
*/
|
|
54
|
+
declare function isValid(expression: string): boolean;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type CronOptions, type ParsedCron, isMatch, isValid, nextRun, nextRuns, parse, previousRun };
|
|
57
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/scheduler.ts","../src/parser.ts"],"mappings":";;AAGA;;UAAiB,WAAA;EAIJ;EAFX,QAAA;EAEA;EAAA,IAAA,GAAO,IAAA;AAAA;;AAUT;;;;;;UAAiB,UAAA;EACf,MAAA;EACA,IAAA;EACA,GAAA;EACA,KAAA;EACA,OAAA;AAAA;;;AAnBF;AAAA,iBCwBgB,OAAA,CAAQ,UAAA,UAAoB,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAepD,WAAA,CAAY,UAAA,UAAoB,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAexD,QAAA,CAAS,UAAA,UAAoB,KAAA,UAAe,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAepE,OAAA,CACd,UAAA,UACA,IAAA,EAAM,IAAA,EACN,OAAA,GAAU,IAAA,CAAK,WAAA;;;ADxEjB;;;;;;;;;AAcA;;;;AAdA,iBEqCgB,KAAA,CAAM,UAAA,WAAqB,UAAA;;;;iBAiL3B,OAAA,CAAQ,UAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Options for cron execution time calculations
|
|
4
|
+
*/
|
|
5
|
+
interface CronOptions {
|
|
6
|
+
/** IANA timezone string (e.g., 'America/New_York', 'Europe/London') */
|
|
7
|
+
timezone?: string;
|
|
8
|
+
/** Reference date to calculate from (defaults to now) */
|
|
9
|
+
from?: Date;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parsed cron expression with valid values for each field
|
|
13
|
+
*
|
|
14
|
+
* Note: Internally, months are stored as 0-indexed (0 = January, 11 = December)
|
|
15
|
+
* to match JavaScript's Date object convention. The parser automatically converts
|
|
16
|
+
* from cron's 1-indexed format (1-12) to 0-indexed (0-11).
|
|
17
|
+
*/
|
|
18
|
+
interface ParsedCron {
|
|
19
|
+
minute: number[];
|
|
20
|
+
hour: number[];
|
|
21
|
+
day: number[];
|
|
22
|
+
month: number[];
|
|
23
|
+
weekday: number[];
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/scheduler.d.ts
|
|
27
|
+
/** Get the next execution time for a cron expression */
|
|
28
|
+
declare function nextRun(expression: string, options?: CronOptions): Date;
|
|
29
|
+
/** Get the previous execution time for a cron expression */
|
|
30
|
+
declare function previousRun(expression: string, options?: CronOptions): Date;
|
|
31
|
+
/** Get next N execution times */
|
|
32
|
+
declare function nextRuns(expression: string, count: number, options?: CronOptions): Date[];
|
|
33
|
+
/** Check if a date matches the cron expression */
|
|
34
|
+
declare function isMatch(expression: string, date: Date, options?: Pick<CronOptions, "timezone">): boolean;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/parser.d.ts
|
|
37
|
+
/**
|
|
38
|
+
* Parse a cron expression into structured format
|
|
39
|
+
*
|
|
40
|
+
* Cron format: minute hour day month weekday
|
|
41
|
+
* - minute: 0-59
|
|
42
|
+
* - hour: 0-23
|
|
43
|
+
* - day: 1-31
|
|
44
|
+
* - month: 1-12 (or JAN-DEC)
|
|
45
|
+
* - weekday: 0-7 (or SUN-SAT, where 0 and 7 are Sunday)
|
|
46
|
+
*
|
|
47
|
+
* Note: Months are converted from cron's 1-indexed format (1-12) to
|
|
48
|
+
* JavaScript's 0-indexed format (0-11) for internal consistency.
|
|
49
|
+
*/
|
|
50
|
+
declare function parse(expression: string): ParsedCron;
|
|
51
|
+
/**
|
|
52
|
+
* Validate a cron expression
|
|
53
|
+
*/
|
|
54
|
+
declare function isValid(expression: string): boolean;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type CronOptions, type ParsedCron, isMatch, isValid, nextRun, nextRuns, parse, previousRun };
|
|
57
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/scheduler.ts","../src/parser.ts"],"mappings":";;AAGA;;UAAiB,WAAA;EAIJ;EAFX,QAAA;EAEA;EAAA,IAAA,GAAO,IAAA;AAAA;;AAUT;;;;;;UAAiB,UAAA;EACf,MAAA;EACA,IAAA;EACA,GAAA;EACA,KAAA;EACA,OAAA;AAAA;;;AAnBF;AAAA,iBCwBgB,OAAA,CAAQ,UAAA,UAAoB,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAepD,WAAA,CAAY,UAAA,UAAoB,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAexD,QAAA,CAAS,UAAA,UAAoB,KAAA,UAAe,OAAA,GAAU,WAAA,GAAc,IAAA;;iBAepE,OAAA,CACd,UAAA,UACA,IAAA,EAAM,IAAA,EACN,OAAA,GAAU,IAAA,CAAK,WAAA;;;ADxEjB;;;;;;;;;AAcA;;;;AAdA,iBEqCgB,KAAA,CAAM,UAAA,WAAqB,UAAA;;;;iBAiL3B,OAAA,CAAQ,UAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e={jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12},t={sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6};function n(n){let a=n.trim();if(!a)throw Error(`Cron expression cannot be empty`);let o=a.split(/\s+/);if(o.length!==5)throw Error(`Invalid cron expression: expected 5 fields, got ${o.length}`);let[s,c,l,u,d]=o,f=i(d,0,7,t).map(e=>e===7?0:e),p={minute:i(s,0,59),hour:i(c,0,23),day:i(l,1,31),month:i(u,1,12,e).map(e=>e-1),weekday:Array.from(new Set(f)).sort((e,t)=>e-t)};return r(p),p}function r(e){let t=e.day.length===31,n=e.month.length===12;if(t||n)return;let r=[31,29,31,30,31,30,31,31,30,31,30,31],i=!1;for(let t of e.month){let n=r[t];for(let t of e.day)if(t<=n){i=!0;break}if(i)break}if(!i)throw Error(`Invalid cron expression: no valid day/month combination exists`)}function i(e,t,n,r){let i=new Set;if(e===`*`){for(let e=t;e<=n;e++)i.add(e);return Array.from(i).sort((e,t)=>e-t)}let o=e.split(`,`);for(let e of o)if(e.includes(`/`)){let[o,s]=e.split(`/`),c=parseInt(s,10);if(isNaN(c)||c<=0)throw Error(`Invalid step value: ${s}`);let l=t,u=n;if(o!==`*`)if(o.includes(`-`)){let[e,t]=o.split(`-`);l=a(e,r),u=a(t,r)}else l=a(o,r);for(let e=l;e<=u;e+=c)e>=t&&e<=n&&i.add(e)}else if(e.includes(`-`)){let[o,s]=e.split(`-`),c=a(o,r),l=a(s,r);if(c>l)throw Error(`Invalid range: ${e}`);for(let e=c;e<=l;e++)e>=t&&e<=n&&i.add(e)}else{let o=a(e,r);if(o>=t&&o<=n)i.add(o);else throw Error(`Value ${o} out of range [${t}-${n}]`)}if(i.size===0)throw Error(`No valid values in field: ${e}`);return Array.from(i).sort((e,t)=>e-t)}function a(e,t){let n=e.toLowerCase();if(t&&n in t)return t[n];let r=parseInt(e,10);if(isNaN(r))throw Error(`Invalid value: ${e}`);return r}function o(e){try{return n(e),!0}catch{return!1}}function s(e,t){let n=t.getUTCMinutes(),r=t.getUTCHours(),i=t.getUTCDate(),a=t.getUTCMonth(),o=t.getUTCDay();return e.minute.includes(n)&&e.hour.includes(r)&&e.month.includes(a)&&c(e,i,o)}function c(e,t,n){let r=e.day.includes(t),i=e.weekday.includes(n),a=e.day.length===31,o=e.weekday.length===7;return!a&&!o?r||i:a?o?!0:i:r}function l(e,t){for(let n of e)if(n>=t)return n;return null}function u(e,t){for(let n=e.length-1;n>=0;n--)if(e[n]<=t)return e[n];return null}function d(e,t){return new Date(e,t+1,0).getDate()}function f(e,t){let[n,r]=e.toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[i,a,o]=n.split(`/`).map(Number),[s,c,l]=r.split(`:`).map(Number);return s===24&&(s=0),new Date(Date.UTC(o,i-1,a,s,c,l))}function p(e,t){let n=e.getUTCFullYear(),r=e.getUTCMonth(),i=e.getUTCDate(),a=e.getUTCHours(),o=e.getUTCMinutes(),s=e.getUTCSeconds(),c=Date.UTC(n,r,i,a,o,s),l=c,u=l,d=1/0;for(let e=0;e<3;e++){let[e,n]=new Date(l).toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[r,i,a]=e.split(`/`).map(Number),[o,s,f]=n.split(`:`).map(Number);o===24&&(o=0);let p=Date.UTC(a,r-1,i,o,s,f),m=Math.abs(c-p);if((m<d||m===d&&l>u)&&(d=m,u=l),p===c)return new Date(l);let h=c-p;l+=h}let f=c+3600*1e3,p=f;for(let e=0;e<2;e++){let[e,n]=new Date(p).toLocaleString(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).split(`, `),[r,i,a]=e.split(`/`).map(Number),[o,s,c]=n.split(`:`).map(Number);o===24&&(o=0);let l=Date.UTC(a,r-1,i,o,s,c);if(l===f)return new Date(p);let u=f-l;p+=u}return new Date(u)}const m={next:{find:l,minute:e=>e.minute[0],hour:e=>e.hour[0],offset:1},prev:{find:u,minute:e=>e.minute.at(-1),hour:e=>e.hour.at(-1),offset:-1}};function h(e,t){let r=n(e),i=t?.from||new Date,a=t?.timezone,o=a?f(i,a):new Date(i);o.setUTCSeconds(0,0),o.setUTCMinutes(o.getUTCMinutes()+1);let s=y(r,o,`next`,a);if(!s)throw Error(`No matching time found within reasonable search window`);return s}function g(e,t){let r=n(e),i=t?.from||new Date,a=t?.timezone,o=a?f(i,a):new Date(i);o.setUTCSeconds(0,0),o.setUTCMinutes(o.getUTCMinutes()-1);let s=y(r,o,`prev`,a);if(!s)throw Error(`No matching time found within reasonable search window`);return s}function _(e,t,n){if(t<=0)return[];let r=[],i=n?.from||new Date;for(let a=0;a<t;a++){let t=h(e,{...n,from:i});r.push(t),i=new Date(t.getTime()+6e4)}return r}function v(e,t,r){return s(n(e),r?.timezone?f(t,r.timezone):new Date(t))}function y(e,t,n,r){let i=new Date(t);for(let t=0;t<1e3;t++){if(s(e,i))return r?p(i,r):i;b(e,i,n)}return null}function b(e,t,n){let r=m[n],i=t.getUTCMinutes(),a=t.getUTCHours(),o=t.getUTCDate(),s=t.getUTCMonth(),c=t.getUTCFullYear(),l=d(c,s);if(!e.month.includes(s)){x(e,t,n,s,c);return}if(!e.day.includes(o)||o>l){S(e,t,n,o,s,c,l);return}if(!e.hour.includes(a)){let i=r.find(e.hour,a+r.offset);i===null?S(e,t,n,o,s,c,l):(t.setUTCHours(i),t.setUTCMinutes(r.minute(e)));return}if(!e.minute.includes(i)){let u=r.find(e.minute,i+r.offset);if(u!==null)t.setUTCMinutes(u);else{let i=r.find(e.hour,a+r.offset);i===null?S(e,t,n,o,s,c,l):(t.setUTCHours(i),t.setUTCMinutes(r.minute(e)))}return}S(e,t,n,o,s,c,l)}function x(e,t,n,r,i){let a=m[n],o=a.find(e.month,r+a.offset);if(o!==null)C(e,t,i,o,n);else{let r=n===`next`?e.month[0]:e.month.at(-1);C(e,t,i+a.offset,r,n)}}function S(e,t,n,r,i,a,o){let s=m[n],c=s.find(e.day,r+s.offset);(n===`next`?c!==null&&c<=o:c!==null)?(t.setUTCDate(c),t.setUTCHours(s.hour(e)),t.setUTCMinutes(s.minute(e))):x(e,t,n,i,a)}function C(e,t,n,r,i){let a=m[i];t.setUTCFullYear(n),t.setUTCDate(1),t.setUTCMonth(r);let o=d(n,r);if(i===`next`){let n=l(e.day,1);t.setUTCDate(n!==null&&n<=o?n:e.day[0])}else{let a=u(e.day,o);if(a!==null)t.setUTCDate(a);else{x(e,t,i,r,n);return}}t.setUTCHours(a.hour(e)),t.setUTCMinutes(a.minute(e))}export{v as isMatch,o as isValid,h as nextRun,_ as nextRuns,n as parse,g as previousRun};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/parser.ts","../src/matcher.ts","../src/timezone.ts","../src/scheduler.ts"],"sourcesContent":["import type { ParsedCron } from \"./types.js\";\n\nconst MONTH_NAMES: Record<string, number> = {\n jan: 1,\n feb: 2,\n mar: 3,\n apr: 4,\n may: 5,\n jun: 6,\n jul: 7,\n aug: 8,\n sep: 9,\n oct: 10,\n nov: 11,\n dec: 12,\n};\n\nconst WEEKDAY_NAMES: Record<string, number> = {\n sun: 0,\n mon: 1,\n tue: 2,\n wed: 3,\n thu: 4,\n fri: 5,\n sat: 6,\n};\n\n/**\n * Parse a cron expression into structured format\n *\n * Cron format: minute hour day month weekday\n * - minute: 0-59\n * - hour: 0-23\n * - day: 1-31\n * - month: 1-12 (or JAN-DEC)\n * - weekday: 0-7 (or SUN-SAT, where 0 and 7 are Sunday)\n *\n * Note: Months are converted from cron's 1-indexed format (1-12) to\n * JavaScript's 0-indexed format (0-11) for internal consistency.\n */\nexport function parse(expression: string): ParsedCron {\n const trimmed = expression.trim();\n\n if (!trimmed) {\n throw new Error(\"Cron expression cannot be empty\");\n }\n\n const parts = trimmed.split(/\\s+/);\n\n if (parts.length !== 5) {\n throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);\n }\n\n const [minuteStr, hourStr, dayStr, monthStr, weekdayStr] = parts;\n\n const weekdays = parseField(weekdayStr, 0, 7, WEEKDAY_NAMES).map((d) => (d === 7 ? 0 : d));\n\n const parsed: ParsedCron = {\n minute: parseField(minuteStr, 0, 59),\n hour: parseField(hourStr, 0, 23),\n day: parseField(dayStr, 1, 31),\n month: parseField(monthStr, 1, 12, MONTH_NAMES).map((m) => m - 1), // Convert to 0-indexed (0 = Jan, 11 = Dec)\n weekday: Array.from(new Set(weekdays)).sort((a, b) => a - b), // Dedupe and sort\n };\n\n // Validate day/month combinations\n validateDayMonthCombinations(parsed);\n\n return parsed;\n}\n\n/**\n * Validate that day/month combinations are possible\n * Rejects expressions like \"0 0 31 2 *\" (Feb 31) or \"0 0 30 2 *\" (Feb 30)\n */\nfunction validateDayMonthCombinations(parsed: ParsedCron): void {\n // If day or month is wildcard, no validation needed\n const dayIsWildcard = parsed.day.length === 31;\n const monthIsWildcard = parsed.month.length === 12;\n\n if (dayIsWildcard || monthIsWildcard) {\n return;\n }\n\n // Days in each month (0-indexed: 0=Jan, 11=Dec)\n // February can have 29 days in leap years\n const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\n // Check if any specified month can accommodate any specified day\n let hasValidCombination = false;\n\n for (const month of parsed.month) {\n const maxDaysInMonth = daysInMonth[month];\n\n for (const day of parsed.day) {\n if (day <= maxDaysInMonth) {\n hasValidCombination = true;\n break;\n }\n }\n\n if (hasValidCombination) {\n break;\n }\n }\n\n if (!hasValidCombination) {\n throw new Error(`Invalid cron expression: no valid day/month combination exists`);\n }\n}\n\n/**\n * Parse a single cron field (e.g., star-slash-5, 1-10, 1,3,5)\n */\nfunction parseField(\n field: string,\n min: number,\n max: number,\n names?: Record<string, number>,\n): number[] {\n const values = new Set<number>();\n\n // Handle wildcard\n if (field === \"*\") {\n for (let i = min; i <= max; i++) {\n values.add(i);\n }\n return Array.from(values).sort((a, b) => a - b);\n }\n\n // Split by comma for multiple values\n const parts = field.split(\",\");\n\n for (const part of parts) {\n // Handle step values (e.g., star-slash-5 or 10-20/2)\n if (part.includes(\"/\")) {\n const [range, stepStr] = part.split(\"/\");\n const step = parseInt(stepStr, 10);\n\n if (isNaN(step) || step <= 0) {\n throw new Error(`Invalid step value: ${stepStr}`);\n }\n\n let start = min;\n let end = max;\n\n if (range !== \"*\") {\n if (range.includes(\"-\")) {\n const [startStr, endStr] = range.split(\"-\");\n start = parseValue(startStr, names);\n end = parseValue(endStr, names);\n } else {\n start = parseValue(range, names);\n }\n }\n\n for (let i = start; i <= end; i += step) {\n if (i >= min && i <= max) {\n values.add(i);\n }\n }\n }\n // Handle ranges (e.g., 1-5)\n else if (part.includes(\"-\")) {\n const [startStr, endStr] = part.split(\"-\");\n const start = parseValue(startStr, names);\n const end = parseValue(endStr, names);\n\n if (start > end) {\n throw new Error(`Invalid range: ${part}`);\n }\n\n for (let i = start; i <= end; i++) {\n if (i >= min && i <= max) {\n values.add(i);\n }\n }\n }\n // Handle single values\n else {\n const value = parseValue(part, names);\n if (value >= min && value <= max) {\n values.add(value);\n } else {\n throw new Error(`Value ${value} out of range [${min}-${max}]`);\n }\n }\n }\n\n if (values.size === 0) {\n throw new Error(`No valid values in field: ${field}`);\n }\n\n return Array.from(values).sort((a, b) => a - b);\n}\n\n/**\n * Parse a single value (number or name)\n */\nfunction parseValue(value: string, names?: Record<string, number>): number {\n const lower = value.toLowerCase();\n\n if (names && lower in names) {\n return names[lower];\n }\n\n const num = parseInt(value, 10);\n if (isNaN(num)) {\n throw new Error(`Invalid value: ${value}`);\n }\n\n return num;\n}\n\n/**\n * Validate a cron expression\n */\nexport function isValid(expression: string): boolean {\n try {\n parse(expression);\n return true;\n } catch {\n return false;\n }\n}\n","import type { ParsedCron } from \"./types.js\";\n\n/**\n * Check if a date matches the cron expression\n */\nexport function matches(parsed: ParsedCron, date: Date): boolean {\n const minute = date.getUTCMinutes();\n const hour = date.getUTCHours();\n const day = date.getUTCDate();\n const month = date.getUTCMonth(); // 0-indexed (0 = Jan, 11 = Dec)\n const weekday = date.getUTCDay();\n\n // Check if all fields match\n return (\n parsed.minute.includes(minute) &&\n parsed.hour.includes(hour) &&\n parsed.month.includes(month) &&\n matchesDayOrWeekday(parsed, day, weekday)\n );\n}\n\n/**\n * Day-of-month and day-of-week use OR logic by default\n * If both are restricted (not *), match either one\n */\nfunction matchesDayOrWeekday(parsed: ParsedCron, day: number, weekday: number): boolean {\n const dayMatches = parsed.day.includes(day);\n const weekdayMatches = parsed.weekday.includes(weekday);\n\n // If both are wildcards (all values), both match\n const dayIsWildcard = parsed.day.length === 31;\n const weekdayIsWildcard = parsed.weekday.length === 7;\n\n // If both are restricted, use OR logic (standard cron behavior)\n if (!dayIsWildcard && !weekdayIsWildcard) {\n return dayMatches || weekdayMatches;\n }\n\n // If only one is restricted, it must match\n if (!dayIsWildcard) {\n return dayMatches;\n }\n if (!weekdayIsWildcard) {\n return weekdayMatches;\n }\n\n // Both wildcards, always matches\n return true;\n}\n\n/**\n * Find the next value in a sorted array that is >= target\n * Returns null if no such value exists\n *\n * @param values - MUST be sorted in ascending order\n * @param target - The minimum value to find\n */\nexport function findNext(values: number[], target: number): number | null {\n for (const value of values) {\n if (value >= target) {\n return value;\n }\n }\n return null;\n}\n\n/**\n * Find the previous value in a sorted array that is <= target\n * Returns null if no such value exists\n *\n * @param values - MUST be sorted in ascending order\n * @param target - The maximum value to find\n */\nexport function findPrevious(values: number[], target: number): number | null {\n for (let i = values.length - 1; i >= 0; i--) {\n if (values[i] <= target) {\n return values[i];\n }\n }\n return null;\n}\n\n/**\n * Get the number of days in a month\n *\n * @param year - The year\n * @param month - The month (0-indexed: 0 = January, 11 = December)\n * @returns The number of days in the month\n */\nexport function getDaysInMonth(year: number, month: number): number {\n // Create date for first day of next month, then go back one day\n return new Date(year, month + 1, 0).getDate();\n}\n","/** Convert a UTC date to wall-clock time in the target timezone */\nexport function convertToTimezone(date: Date, timezone: string): Date {\n // Format the date in the target timezone\n const str = date.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n // Parse formatted string: \"MM/DD/YYYY, HH:mm:ss\"\n const [datePart, timePart] = str.split(\", \");\n const [month, day, year] = datePart.split(\"/\").map(Number);\n let [hour, minute, second] = timePart.split(\":\").map(Number);\n\n if (hour === 24) hour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n return new Date(Date.UTC(year, month - 1, day, hour, minute, second));\n}\n\n/**\n * Convert a timezone-local date back to UTC (inverse of convertToTimezone).\n *\n * Note: During DST fall-back, multiple UTC times map to the same wall-clock time.\n * The result is implementation-defined. Avoid scheduling during DST transition hours\n * for predictable behavior.\n */\nexport function convertFromTimezone(date: Date, timezone: string): Date {\n const targetYear = date.getUTCFullYear();\n const targetMonth = date.getUTCMonth();\n const targetDay = date.getUTCDate();\n const targetHour = date.getUTCHours();\n const targetMinute = date.getUTCMinutes();\n const targetSecond = date.getUTCSeconds();\n\n // Target time as a comparable number (for checking if we found it)\n const targetTime = Date.UTC(\n targetYear,\n targetMonth,\n targetDay,\n targetHour,\n targetMinute,\n targetSecond,\n );\n\n // Start with a guess: interpret the wall-clock time as UTC\n let guess = targetTime;\n let bestGuess = guess;\n let bestDiff = Infinity;\n\n // Iteratively refine the guess (usually converges in 1-2 iterations)\n for (let i = 0; i < 3; i++) {\n const testDate = new Date(guess);\n const testStr = testDate.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n // Parse what wall-clock time this guess produces\n const [testDatePart, testTimePart] = testStr.split(\", \");\n const [testMonth, testDay, testYear] = testDatePart.split(\"/\").map(Number);\n let [testHour, testMinute, testSecond] = testTimePart.split(\":\").map(Number);\n\n if (testHour === 24) testHour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n const gotTime = Date.UTC(testYear, testMonth - 1, testDay, testHour, testMinute, testSecond);\n\n // Track the best guess (closest to target, but prefer later times if equal distance)\n const diff = Math.abs(targetTime - gotTime);\n if (diff < bestDiff || (diff === bestDiff && guess > bestGuess)) {\n bestDiff = diff;\n bestGuess = guess;\n }\n\n // If we got what we wanted, we're done!\n // Note: During DST fall-back, two UTC times map to the same wall-clock time.\n // This returns whichever solution the iteration converges to first (implementation-defined).\n if (gotTime === targetTime) {\n return new Date(guess);\n }\n\n // Otherwise, adjust the guess by the difference\n const adjustment = targetTime - gotTime;\n guess += adjustment;\n }\n\n // If we didn't find an exact match after 3 iterations, we're likely in a DST gap\n // (e.g., 2:30 AM during spring forward doesn't exist)\n // Try one more time: check if adding 1 hour to the target gets us closer\n const oneHourLater = targetTime + 60 * 60 * 1000;\n let guessLater = oneHourLater;\n\n for (let i = 0; i < 2; i++) {\n const testDate = new Date(guessLater);\n const testStr = testDate.toLocaleString(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n\n const [testDatePart, testTimePart] = testStr.split(\", \");\n const [testMonth, testDay, testYear] = testDatePart.split(\"/\").map(Number);\n let [testHour, testMinute, testSecond] = testTimePart.split(\":\").map(Number);\n\n if (testHour === 24) testHour = 0; // Normalize \"24:00:00\" to \"00:00:00\"\n\n const gotTime = Date.UTC(testYear, testMonth - 1, testDay, testHour, testMinute, testSecond);\n\n if (gotTime === oneHourLater) {\n // Target time was in a DST gap, return the time after the gap\n return new Date(guessLater);\n }\n\n const adjustment = oneHourLater - gotTime;\n guessLater += adjustment;\n }\n\n // Return the best guess we found\n return new Date(bestGuess);\n}\n","import type { ParsedCron, CronOptions } from \"./types.js\";\nimport { parse } from \"./parser.js\";\nimport { matches, findNext, findPrevious, getDaysInMonth } from \"./matcher.js\";\nimport { convertToTimezone, convertFromTimezone } from \"./timezone.js\";\n\nconst MAX_ITERATIONS = 1000;\nconst ONE_MINUTE_MS = 60_000;\n\ntype Direction = \"next\" | \"prev\";\n\n/** Direction-specific operations for unified forward/backward traversal */\nconst DIR = {\n next: {\n find: findNext,\n minute: (p: ParsedCron) => p.minute[0],\n hour: (p: ParsedCron) => p.hour[0],\n offset: 1,\n },\n prev: {\n find: findPrevious,\n minute: (p: ParsedCron) => p.minute.at(-1)!,\n hour: (p: ParsedCron) => p.hour.at(-1)!,\n offset: -1,\n },\n} as const;\n\n/** Get the next execution time for a cron expression */\nexport function nextRun(expression: string, options?: CronOptions): Date {\n const parsed = parse(expression);\n const from = options?.from || new Date();\n const tz = options?.timezone;\n\n const start = tz ? convertToTimezone(from, tz) : new Date(from);\n start.setUTCSeconds(0, 0);\n start.setUTCMinutes(start.getUTCMinutes() + 1);\n\n const result = findMatch(parsed, start, \"next\", tz);\n if (!result) throw new Error(\"No matching time found within reasonable search window\");\n return result;\n}\n\n/** Get the previous execution time for a cron expression */\nexport function previousRun(expression: string, options?: CronOptions): Date {\n const parsed = parse(expression);\n const from = options?.from || new Date();\n const tz = options?.timezone;\n\n const start = tz ? convertToTimezone(from, tz) : new Date(from);\n start.setUTCSeconds(0, 0);\n start.setUTCMinutes(start.getUTCMinutes() - 1);\n\n const result = findMatch(parsed, start, \"prev\", tz);\n if (!result) throw new Error(\"No matching time found within reasonable search window\");\n return result;\n}\n\n/** Get next N execution times */\nexport function nextRuns(expression: string, count: number, options?: CronOptions): Date[] {\n if (count <= 0) return [];\n\n const results: Date[] = [];\n let current = options?.from || new Date();\n\n for (let i = 0; i < count; i++) {\n const next = nextRun(expression, { ...options, from: current });\n results.push(next);\n current = new Date(next.getTime() + ONE_MINUTE_MS);\n }\n return results;\n}\n\n/** Check if a date matches the cron expression */\nexport function isMatch(\n expression: string,\n date: Date,\n options?: Pick<CronOptions, \"timezone\">,\n): boolean {\n const parsed = parse(expression);\n const checkDate = options?.timezone ? convertToTimezone(date, options.timezone) : new Date(date);\n return matches(parsed, checkDate);\n}\n\n/** Find matching time using smart field-increment algorithm */\nfunction findMatch(parsed: ParsedCron, start: Date, dir: Direction, tz?: string): Date | null {\n const current = new Date(start);\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n if (matches(parsed, current)) {\n return tz ? convertFromTimezone(current, tz) : current;\n }\n advanceDate(parsed, current, dir);\n }\n return null;\n}\n\n/**\n * Advance date to next/prev candidate time by mutating the date in place.\n *\n * Algorithm:\n * 1. Check fields from LARGEST (month) to SMALLEST (minute)\n * 2. When a field doesn't match, jump to the next valid value for that field\n * 3. Reset all smaller fields to their boundary (first value for 'next', last for 'prev')\n *\n * Example (direction='next', cron='0 9 * * *' meaning 9:00 AM daily):\n * Current: March 15, 10:30 AM\n * - Month (March)? ✓ matches\n * - Day (15)? ✓ matches\n * - Hour (10)? ✗ not in [9] → no next hour today → cascade to next day\n * - Result: March 16, 9:00 AM\n *\n * @param parsed - The parsed cron expression\n * @param date - The date to mutate (modified in place)\n * @param dir - Direction to advance ('next' or 'prev')\n */\nfunction advanceDate(parsed: ParsedCron, date: Date, dir: Direction): void {\n const d = DIR[dir];\n const minute = date.getUTCMinutes();\n const hour = date.getUTCHours();\n const day = date.getUTCDate();\n const month = date.getUTCMonth();\n const year = date.getUTCFullYear();\n const daysInMonth = getDaysInMonth(year, month);\n\n // Month mismatch\n if (!parsed.month.includes(month)) {\n moveToMonth(parsed, date, dir, month, year);\n return;\n }\n\n // Day mismatch\n if (!parsed.day.includes(day) || day > daysInMonth) {\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n return;\n }\n\n // Hour mismatch\n if (!parsed.hour.includes(hour)) {\n const targetHour = d.find(parsed.hour, hour + d.offset);\n if (targetHour !== null) {\n // Found valid hour in same day → reset minute to boundary\n date.setUTCHours(targetHour);\n date.setUTCMinutes(d.minute(parsed));\n } else {\n // No valid hour left today → move to next/prev day\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n }\n return;\n }\n\n // Minute mismatch\n if (!parsed.minute.includes(minute)) {\n const targetMinute = d.find(parsed.minute, minute + d.offset);\n if (targetMinute !== null) {\n // Found valid minute in same hour\n date.setUTCMinutes(targetMinute);\n } else {\n // No valid minute left → try next hour\n const targetHour = d.find(parsed.hour, hour + d.offset);\n if (targetHour !== null) {\n date.setUTCHours(targetHour);\n date.setUTCMinutes(d.minute(parsed));\n } else {\n // No valid hour left → move to next/prev day\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n }\n }\n return;\n }\n\n // Weekday mismatch: all fields match but wrong day-of-week.\n // Skip directly to next/prev day since no hour/minute on this day can match.\n moveToDay(parsed, date, dir, day, month, year, daysInMonth);\n}\n\nfunction moveToMonth(\n parsed: ParsedCron,\n date: Date,\n dir: Direction,\n currentMonth: number,\n currentYear: number,\n): void {\n const d = DIR[dir];\n const targetMonth = d.find(parsed.month, currentMonth + d.offset);\n\n if (targetMonth !== null) {\n resetToMonthBoundary(parsed, date, currentYear, targetMonth, dir);\n } else {\n const boundaryMonth = dir === \"next\" ? parsed.month[0] : parsed.month.at(-1)!;\n resetToMonthBoundary(parsed, date, currentYear + d.offset, boundaryMonth, dir);\n }\n}\n\nfunction moveToDay(\n parsed: ParsedCron,\n date: Date,\n dir: Direction,\n currentDay: number,\n currentMonth: number,\n currentYear: number,\n daysInMonth: number,\n): void {\n const d = DIR[dir];\n const targetDay = d.find(parsed.day, currentDay + d.offset);\n const dayIsValid =\n dir === \"next\" ? targetDay !== null && targetDay <= daysInMonth : targetDay !== null;\n\n if (dayIsValid) {\n date.setUTCDate(targetDay!);\n date.setUTCHours(d.hour(parsed));\n date.setUTCMinutes(d.minute(parsed));\n } else {\n moveToMonth(parsed, date, dir, currentMonth, currentYear);\n }\n}\n\nfunction resetToMonthBoundary(\n parsed: ParsedCron,\n date: Date,\n year: number,\n month: number,\n dir: Direction,\n): void {\n const d = DIR[dir];\n date.setUTCFullYear(year);\n date.setUTCDate(1);\n date.setUTCMonth(month);\n\n const daysInMonth = getDaysInMonth(year, month);\n\n if (dir === \"next\") {\n const validDay = findNext(parsed.day, 1);\n date.setUTCDate(validDay !== null && validDay <= daysInMonth ? validDay : parsed.day[0]);\n } else {\n const prevDay = findPrevious(parsed.day, daysInMonth);\n if (prevDay !== null) {\n date.setUTCDate(prevDay);\n } else {\n // No valid day in this month, move to previous month\n moveToMonth(parsed, date, dir, month, year);\n return;\n }\n }\n\n date.setUTCHours(d.hour(parsed));\n date.setUTCMinutes(d.minute(parsed));\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cron-fast",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Fast JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"browser",
|
|
7
|
+
"bun",
|
|
8
|
+
"cloudflare-workers",
|
|
9
|
+
"cron",
|
|
10
|
+
"deno",
|
|
11
|
+
"esm",
|
|
12
|
+
"fast",
|
|
13
|
+
"javascript",
|
|
14
|
+
"lightweight",
|
|
15
|
+
"node",
|
|
16
|
+
"nodejs",
|
|
17
|
+
"parser",
|
|
18
|
+
"performance",
|
|
19
|
+
"schedule",
|
|
20
|
+
"scheduler",
|
|
21
|
+
"timezone",
|
|
22
|
+
"tree-shakeable",
|
|
23
|
+
"typescript",
|
|
24
|
+
"workerd",
|
|
25
|
+
"zero-dependencies"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://github.com/kbilkis/cron-fast#readme",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"author": "Kasparas Bilkis kasparas@bilkis.lt",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/kbilkis/cron-fast.git"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"main": "./dist/index.cjs",
|
|
42
|
+
"module": "./dist/index.mjs",
|
|
43
|
+
"types": "./dist/index.d.mts",
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./dist/index.d.mts",
|
|
48
|
+
"default": "./dist/index.mjs"
|
|
49
|
+
},
|
|
50
|
+
"require": {
|
|
51
|
+
"types": "./dist/index.d.cts",
|
|
52
|
+
"default": "./dist/index.cjs"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@cloudflare/vitest-pool-workers": "^0.12.10",
|
|
58
|
+
"@types/node": "^25.2.2",
|
|
59
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
60
|
+
"@vitest/ui": "^3.2.4",
|
|
61
|
+
"oxfmt": "^0.28.0",
|
|
62
|
+
"oxlint": "^1.43.0",
|
|
63
|
+
"tsdown": "^0.20.3",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"vite": "^7.3.1",
|
|
66
|
+
"vitest": "3.2.4"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsdown",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest",
|
|
72
|
+
"test:ui": "vitest --ui",
|
|
73
|
+
"test:coverage": "vitest run --coverage",
|
|
74
|
+
"test:workerd": "vitest run --config vitest.workerd.config.ts",
|
|
75
|
+
"lint": "oxlint",
|
|
76
|
+
"lint:fix": "oxlint --fix",
|
|
77
|
+
"fmt": "oxfmt",
|
|
78
|
+
"fmt:check": "oxfmt --check",
|
|
79
|
+
"typecheck": "tsc --noEmit"
|
|
80
|
+
}
|
|
81
|
+
}
|