@stacksjs/cron 0.70.55 → 0.70.57
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/package.json +10 -7
- package/src/bun-cron.d.ts +0 -41
- package/src/index.ts +0 -64
- package/src/parser.ts +0 -296
- package/src/types.ts +0 -6
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/parser.d.ts → parser.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/cron",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.57",
|
|
5
5
|
"description": "The Stacks cron.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -28,20 +28,23 @@
|
|
|
28
28
|
"exports": {
|
|
29
29
|
".": {
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
31
|
+
"development": "./src/index.ts",
|
|
32
|
+
"bun": "./dist/index.js",
|
|
33
|
+
"import": "./dist/index.js",
|
|
34
|
+
"default": "./dist/index.js"
|
|
33
35
|
},
|
|
34
36
|
"./*": {
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
+
"development": "./src/*",
|
|
38
|
+
"bun": "./dist/*",
|
|
39
|
+
"import": "./dist/*",
|
|
40
|
+
"default": "./dist/*"
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
43
|
"module": "dist/index.js",
|
|
40
44
|
"types": "dist/index.d.ts",
|
|
41
45
|
"files": [
|
|
42
46
|
"README.md",
|
|
43
|
-
"dist"
|
|
44
|
-
"src"
|
|
47
|
+
"dist"
|
|
45
48
|
],
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "bun build.ts",
|
package/src/bun-cron.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
declare module 'bun' {
|
|
2
|
-
type CronExpression =
|
|
3
|
-
| '* * * * *'
|
|
4
|
-
| '@yearly'
|
|
5
|
-
| '@annually'
|
|
6
|
-
| '@monthly'
|
|
7
|
-
| '@weekly'
|
|
8
|
-
| '@daily'
|
|
9
|
-
| '@midnight'
|
|
10
|
-
| '@hourly'
|
|
11
|
-
| (string & {})
|
|
12
|
-
|
|
13
|
-
interface BunCron {
|
|
14
|
-
/**
|
|
15
|
-
* Register an OS-level cron job that runs a JavaScript/TypeScript module on a schedule.
|
|
16
|
-
*
|
|
17
|
-
* @param path - Path to the script to run (resolved relative to caller)
|
|
18
|
-
* @param schedule - Cron expression (5-field) or predefined nickname (@daily, @hourly, etc.)
|
|
19
|
-
* @param title - Unique job identifier (alphanumeric, hyphens, underscores only)
|
|
20
|
-
*/
|
|
21
|
-
(path: string, schedule: CronExpression, title: string): Promise<void>
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Parse a cron expression and return the next matching UTC Date.
|
|
25
|
-
*
|
|
26
|
-
* @param expression - A 5-field cron expression or nickname
|
|
27
|
-
* @param relativeDate - Starting point for the search (defaults to Date.now())
|
|
28
|
-
* @returns The next Date matching the expression, or null if no match within ~4 years
|
|
29
|
-
*/
|
|
30
|
-
parse(expression: CronExpression, relativeDate?: Date | number): Date | null
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Remove a previously registered OS-level cron job by its title.
|
|
34
|
-
*
|
|
35
|
-
* @param title - The title of the cron job to remove
|
|
36
|
-
*/
|
|
37
|
-
remove(title: string): Promise<void>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
var cron: BunCron
|
|
41
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/// <reference path="./bun-cron.d.ts" />
|
|
2
|
-
|
|
3
|
-
export * from './types'
|
|
4
|
-
export { parseCron } from './parser'
|
|
5
|
-
|
|
6
|
-
import { parseCron } from './parser'
|
|
7
|
-
|
|
8
|
-
// Parse a cron expression and return the next matching Date.
|
|
9
|
-
// Uses Bun's native cron parser when available, otherwise falls back
|
|
10
|
-
// to our own 5-field parser with identical behavior.
|
|
11
|
-
//
|
|
12
|
-
// Examples:
|
|
13
|
-
// parse('0 0 * * *') → next midnight UTC
|
|
14
|
-
// parse('@hourly') → next hour
|
|
15
|
-
// parse('0,15,30,45 * * * *') → next 15-min mark
|
|
16
|
-
export function parse(expression: string, relativeDate?: Date | number): Date | null {
|
|
17
|
-
// Use native Bun.cron.parse when available (ships with Bun.cron PR)
|
|
18
|
-
if (typeof Bun !== 'undefined' && Bun.cron?.parse) {
|
|
19
|
-
return Bun.cron.parse(expression, relativeDate)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return parseCron(expression, relativeDate)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register an OS-level cron job that persists across process restarts.
|
|
27
|
-
*
|
|
28
|
-
* Requires Bun's native cron support (crontab on Linux, launchd on macOS,
|
|
29
|
-
* schtasks on Windows).
|
|
30
|
-
*
|
|
31
|
-
* The target script must export a `scheduled(controller)` handler:
|
|
32
|
-
* ```ts
|
|
33
|
-
* export default {
|
|
34
|
-
* async scheduled(controller) {
|
|
35
|
-
* // controller.cron, controller.scheduledTime
|
|
36
|
-
* await doWork()
|
|
37
|
-
* }
|
|
38
|
-
* }
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* @param path - Path to the script to run
|
|
42
|
-
* @param schedule - Cron expression or nickname (@daily, @hourly, etc.)
|
|
43
|
-
* @param title - Unique job identifier (alphanumeric, hyphens, underscores)
|
|
44
|
-
*/
|
|
45
|
-
export async function register(path: string, schedule: string, title: string): Promise<void> {
|
|
46
|
-
if (typeof Bun === 'undefined' || !Bun.cron) {
|
|
47
|
-
throw new Error('Bun.cron is not available. OS-level cron requires a Bun version with native cron support.')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
await Bun.cron(path, schedule, title)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Remove a previously registered OS-level cron job.
|
|
55
|
-
*
|
|
56
|
-
* @param title - The title of the cron job to remove
|
|
57
|
-
*/
|
|
58
|
-
export async function remove(title: string): Promise<void> {
|
|
59
|
-
if (typeof Bun === 'undefined' || !Bun.cron?.remove) {
|
|
60
|
-
throw new Error('Bun.cron is not available. OS-level cron requires a Bun version with native cron support.')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
await Bun.cron.remove(title)
|
|
64
|
-
}
|
package/src/parser.ts
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native 5-field cron expression parser.
|
|
3
|
-
*
|
|
4
|
-
* Implements the same API as Bun.cron.parse() — when Bun ships native
|
|
5
|
-
* cron support, this can be swapped out with a one-line change.
|
|
6
|
-
*
|
|
7
|
-
* Supports:
|
|
8
|
-
* - Standard 5-field format: minute hour dayOfMonth month dayOfWeek
|
|
9
|
-
* - Nicknames: @yearly, @annually, @monthly, @weekly, @daily, @midnight, @hourly
|
|
10
|
-
* - Operators: * (all), , (list), - (range), / (step)
|
|
11
|
-
* - Named values: JAN-DEC, SUN-SAT (case-insensitive)
|
|
12
|
-
* - POSIX OR logic: when both dayOfMonth and dayOfWeek are specified (neither *),
|
|
13
|
-
* the expression matches when either condition is true
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const NICKNAMES: Record<string, string> = {
|
|
17
|
-
'@yearly': '0 0 1 1 *',
|
|
18
|
-
'@annually': '0 0 1 1 *',
|
|
19
|
-
'@monthly': '0 0 1 * *',
|
|
20
|
-
'@weekly': '0 0 * * 0',
|
|
21
|
-
'@daily': '0 0 * * *',
|
|
22
|
-
'@midnight': '0 0 * * *',
|
|
23
|
-
'@hourly': '0 * * * *',
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const MONTH_NAMES: Record<string, number> = {
|
|
27
|
-
jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
|
|
28
|
-
jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12,
|
|
29
|
-
january: 1, february: 2, march: 3, april: 4, june: 6,
|
|
30
|
-
july: 7, august: 8, september: 9, october: 10, november: 11, december: 12,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const DAY_NAMES: Record<string, number> = {
|
|
34
|
-
sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6,
|
|
35
|
-
sunday: 0, monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Track which 6-field expressions we've already warned about so a
|
|
40
|
-
* scheduler tick doesn't spam the log every iteration
|
|
41
|
-
* (stacksjs/stacks#1877 Cr-6, mirroring #1872 Q-12).
|
|
42
|
-
*/
|
|
43
|
-
const sixFieldWarnedFor = new Set<string>()
|
|
44
|
-
|
|
45
|
-
function warnSecondsIgnored(expression: string, secondsField: string): void {
|
|
46
|
-
if (sixFieldWarnedFor.has(expression)) return
|
|
47
|
-
sixFieldWarnedFor.add(expression)
|
|
48
|
-
// eslint-disable-next-line no-console
|
|
49
|
-
console.warn(
|
|
50
|
-
`[cron] 6-field cron expression '${expression}' has seconds='${secondsField}' — the parser is 5-field only. `
|
|
51
|
-
+ `Seconds field IGNORED. For second-precision use the scheduler's '.everySecond()' instead.`,
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function resolveNames(value: string, names: Record<string, number>): string {
|
|
56
|
-
return value.replace(/[a-z]+/gi, match => {
|
|
57
|
-
const num = names[match.toLowerCase()]
|
|
58
|
-
if (num === undefined) throw new Error(`Invalid cron value: ${match}`)
|
|
59
|
-
return String(num)
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseField(field: string, min: number, max: number, names?: Record<string, number>): Set<number> {
|
|
64
|
-
const resolved = names ? resolveNames(field, names) : field
|
|
65
|
-
const values = new Set<number>()
|
|
66
|
-
|
|
67
|
-
for (const part of resolved.split(',')) {
|
|
68
|
-
const stepMatch = part.match(/^(.+)\/(\d+)$/)
|
|
69
|
-
let range: string
|
|
70
|
-
let step = 1
|
|
71
|
-
|
|
72
|
-
if (stepMatch) {
|
|
73
|
-
range = stepMatch[1] ?? ''
|
|
74
|
-
step = Number.parseInt(stepMatch[2] ?? '', 10)
|
|
75
|
-
if (step <= 0) throw new Error(`Invalid step: ${step}`)
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
range = part
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (range === '*') {
|
|
82
|
-
for (let i = min; i <= max; i += step) values.add(i)
|
|
83
|
-
}
|
|
84
|
-
else if (range.includes('-')) {
|
|
85
|
-
const [startStr, endStr] = range.split('-')
|
|
86
|
-
const start = Number.parseInt(startStr ?? '', 10)
|
|
87
|
-
const end = Number.parseInt(endStr ?? '', 10)
|
|
88
|
-
if (Number.isNaN(start) || Number.isNaN(end)) throw new Error(`Invalid range: ${range}`)
|
|
89
|
-
if (start < min || end > max) throw new Error(`Range out of bounds: ${range} (${min}-${max})`)
|
|
90
|
-
for (let i = start; i <= end; i += step) values.add(i)
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
const num = Number.parseInt(range, 10)
|
|
94
|
-
if (Number.isNaN(num)) throw new Error(`Invalid cron value: ${range}`)
|
|
95
|
-
if (num < min || num > max) throw new Error(`Value out of range: ${num} (${min}-${max})`)
|
|
96
|
-
values.add(num)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return values
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Options for `parseCron` (stacksjs/stacks#1877 Cr-5).
|
|
105
|
-
*/
|
|
106
|
-
export interface ParseCronOptions {
|
|
107
|
-
/**
|
|
108
|
-
* IANA timezone (e.g. `'America/Los_Angeles'`) the cron expression
|
|
109
|
-
* should be interpreted against. When set, field comparisons run
|
|
110
|
-
* in local time within that zone — `'0 14 * * *'` fires at 14:00
|
|
111
|
-
* local instead of 14:00 UTC. Defaults to UTC for backwards-compat.
|
|
112
|
-
*
|
|
113
|
-
* **DST caveat (Cr-1, deferred):** spring-forward (e.g. 02:00→03:00
|
|
114
|
-
* in US Pacific) and fall-back (02:00 happens twice) can cause
|
|
115
|
-
* fires to be skipped or duplicated for expressions whose hour
|
|
116
|
-
* lands in the transition window. Most scheduled tasks don't care
|
|
117
|
-
* because they fire on the 0th minute of every hour. Tasks that
|
|
118
|
-
* MUST fire exactly once during the transition need a TZ-aware
|
|
119
|
-
* scheduler layer above this parser.
|
|
120
|
-
*/
|
|
121
|
-
tz?: string
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Parse a cron expression and return the next matching Date.
|
|
126
|
-
*
|
|
127
|
-
* @param expression - A 5-field cron expression or nickname (@daily, etc.)
|
|
128
|
-
* @param relativeDate - Starting point for the search (defaults to Date.now())
|
|
129
|
-
* @param options - Optional `{ tz }` for timezone-local interpretation
|
|
130
|
-
* @returns The next Date matching the expression, or null if no match within ~4 years
|
|
131
|
-
*/
|
|
132
|
-
export function parseCron(expression: string, relativeDate?: Date | number, options: ParseCronOptions = {}): Date | null {
|
|
133
|
-
const trimmed = expression.trim()
|
|
134
|
-
const normalized = NICKNAMES[trimmed.toLowerCase()] ?? trimmed
|
|
135
|
-
|
|
136
|
-
let fields = normalized.split(/\s+/).filter(Boolean)
|
|
137
|
-
|
|
138
|
-
// 6-field cron (with leading seconds) is widely used by Quartz /
|
|
139
|
-
// Spring schedulers. This parser is 5-field, but rather than
|
|
140
|
-
// throw and break the user's app at boot, drop the seconds field
|
|
141
|
-
// with a once-per-expression warn (stacksjs/stacks#1877 Cr-6).
|
|
142
|
-
// If the seconds field is non-zero/non-wildcard, the user is
|
|
143
|
-
// expressing intent that's silently lost — the warn surfaces that.
|
|
144
|
-
if (fields.length === 6) {
|
|
145
|
-
const secondsField = fields[0]!
|
|
146
|
-
if (secondsField !== '0' && secondsField !== '*')
|
|
147
|
-
warnSecondsIgnored(expression, secondsField)
|
|
148
|
-
fields = fields.slice(1)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (fields.length !== 5) {
|
|
152
|
-
throw new Error(`Invalid cron expression: expected 5 fields (or 6 with leading seconds), got ${fields.length}`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const [minuteField, hourField, domField, monthField, dowField] = fields as [string, string, string, string, string]
|
|
156
|
-
const minutes = parseField(minuteField, 0, 59)
|
|
157
|
-
const hours = parseField(hourField, 0, 23)
|
|
158
|
-
const daysOfMonth = parseField(domField, 1, 31)
|
|
159
|
-
const months = parseField(monthField, 1, 12, MONTH_NAMES)
|
|
160
|
-
const daysOfWeek = parseField(dowField, 0, 7, DAY_NAMES)
|
|
161
|
-
|
|
162
|
-
// Normalize Sunday: 7 → 0
|
|
163
|
-
if (daysOfWeek.has(7)) {
|
|
164
|
-
daysOfWeek.add(0)
|
|
165
|
-
daysOfWeek.delete(7)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// POSIX: if both dayOfMonth and dayOfWeek are specified (neither is *), use OR logic
|
|
169
|
-
const domWild = domField === '*'
|
|
170
|
-
const dowWild = dowField === '*'
|
|
171
|
-
|
|
172
|
-
// Start point
|
|
173
|
-
const base = relativeDate instanceof Date
|
|
174
|
-
? relativeDate.getTime()
|
|
175
|
-
: typeof relativeDate === 'number'
|
|
176
|
-
? relativeDate
|
|
177
|
-
: Date.now()
|
|
178
|
-
|
|
179
|
-
if (Number.isNaN(base) || !Number.isFinite(base)) {
|
|
180
|
-
throw new Error('Invalid date')
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// When a timezone is configured, build a part-extractor that reads
|
|
184
|
-
// local time in that zone instead of UTC (stacksjs/stacks#1877 Cr-5).
|
|
185
|
-
// The search loop still advances in UTC milliseconds, but field
|
|
186
|
-
// comparisons happen against the TZ-local view. The extra cost is
|
|
187
|
-
// an Intl.DateTimeFormat per iteration — measurable on a fast loop
|
|
188
|
-
// but negligible for the once-per-tick cron path.
|
|
189
|
-
const tz = options.tz
|
|
190
|
-
const getParts = tz
|
|
191
|
-
? makeTzPartsExtractor(tz)
|
|
192
|
-
: (date: Date) => ({
|
|
193
|
-
year: date.getUTCFullYear(),
|
|
194
|
-
month: date.getUTCMonth() + 1,
|
|
195
|
-
day: date.getUTCDate(),
|
|
196
|
-
hour: date.getUTCHours(),
|
|
197
|
-
minute: date.getUTCMinutes(),
|
|
198
|
-
dow: date.getUTCDay(),
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
// Begin search from the next minute
|
|
202
|
-
const d = new Date(base)
|
|
203
|
-
d.setUTCSeconds(0, 0)
|
|
204
|
-
d.setUTCMinutes(d.getUTCMinutes() + 1)
|
|
205
|
-
|
|
206
|
-
// Search up to ~4 years
|
|
207
|
-
const maxTime = d.getTime() + 4 * 365.25 * 24 * 60 * 60 * 1000
|
|
208
|
-
|
|
209
|
-
while (d.getTime() < maxTime) {
|
|
210
|
-
const parts = getParts(d)
|
|
211
|
-
|
|
212
|
-
// Month check
|
|
213
|
-
if (!months.has(parts.month)) {
|
|
214
|
-
d.setUTCMonth(d.getUTCMonth() + 1, 1)
|
|
215
|
-
d.setUTCHours(0, 0, 0, 0)
|
|
216
|
-
continue
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Day check (POSIX OR logic)
|
|
220
|
-
const domMatch = daysOfMonth.has(parts.day)
|
|
221
|
-
const dowMatch = daysOfWeek.has(parts.dow)
|
|
222
|
-
|
|
223
|
-
let dayMatch: boolean
|
|
224
|
-
if (domWild && dowWild)
|
|
225
|
-
dayMatch = true
|
|
226
|
-
else if (domWild)
|
|
227
|
-
dayMatch = dowMatch
|
|
228
|
-
else if (dowWild)
|
|
229
|
-
dayMatch = domMatch
|
|
230
|
-
else
|
|
231
|
-
dayMatch = domMatch || dowMatch // POSIX OR
|
|
232
|
-
|
|
233
|
-
if (!dayMatch) {
|
|
234
|
-
d.setUTCDate(d.getUTCDate() + 1)
|
|
235
|
-
d.setUTCHours(0, 0, 0, 0)
|
|
236
|
-
continue
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Hour check
|
|
240
|
-
if (!hours.has(parts.hour)) {
|
|
241
|
-
d.setUTCHours(d.getUTCHours() + 1, 0, 0, 0)
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Minute check
|
|
246
|
-
if (!minutes.has(parts.minute)) {
|
|
247
|
-
d.setUTCMinutes(d.getUTCMinutes() + 1, 0, 0)
|
|
248
|
-
continue
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return new Date(d.getTime())
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return null // No match within ~4 years (e.g. Feb 30)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Build a part-extractor for the given IANA timezone. Uses
|
|
259
|
-
* `Intl.DateTimeFormat.formatToParts` which is fast in Bun and
|
|
260
|
-
* handles DST automatically (the same instant in UTC may produce
|
|
261
|
-
* different local parts on either side of a transition).
|
|
262
|
-
*
|
|
263
|
-
* The day-of-week is computed via the `weekday: 'short'` field
|
|
264
|
-
* mapped to 0-6 (Sunday = 0) to match the cron convention.
|
|
265
|
-
*/
|
|
266
|
-
function makeTzPartsExtractor(tz: string): (d: Date) => { year: number, month: number, day: number, hour: number, minute: number, dow: number } {
|
|
267
|
-
const fmt = new Intl.DateTimeFormat('en-US', {
|
|
268
|
-
timeZone: tz,
|
|
269
|
-
hourCycle: 'h23',
|
|
270
|
-
year: 'numeric',
|
|
271
|
-
month: 'numeric',
|
|
272
|
-
day: 'numeric',
|
|
273
|
-
hour: 'numeric',
|
|
274
|
-
minute: 'numeric',
|
|
275
|
-
weekday: 'short',
|
|
276
|
-
})
|
|
277
|
-
const DOW_MAP: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }
|
|
278
|
-
return (d: Date) => {
|
|
279
|
-
const parts = fmt.formatToParts(d)
|
|
280
|
-
let year = 0
|
|
281
|
-
let month = 0
|
|
282
|
-
let day = 0
|
|
283
|
-
let hour = 0
|
|
284
|
-
let minute = 0
|
|
285
|
-
let dow = 0
|
|
286
|
-
for (const p of parts) {
|
|
287
|
-
if (p.type === 'year') year = Number.parseInt(p.value, 10)
|
|
288
|
-
else if (p.type === 'month') month = Number.parseInt(p.value, 10)
|
|
289
|
-
else if (p.type === 'day') day = Number.parseInt(p.value, 10)
|
|
290
|
-
else if (p.type === 'hour') hour = Number.parseInt(p.value, 10) % 24 // 24:00 → 00 normalization
|
|
291
|
-
else if (p.type === 'minute') minute = Number.parseInt(p.value, 10)
|
|
292
|
-
else if (p.type === 'weekday') dow = DOW_MAP[p.value] ?? 0
|
|
293
|
-
}
|
|
294
|
-
return { year, month, day, hour, minute, dow }
|
|
295
|
-
}
|
|
296
|
-
}
|
package/src/types.ts
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|