cronli5 0.0.0-beta.6 → 0.1.0
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/CHANGELOG.md +276 -0
- package/LICENSE.md +2 -2
- package/README.md +328 -91
- package/cli.js +73 -10
- package/cronli5.min.js +3 -0
- package/dist/cronli5.cjs +1556 -0
- package/dist/cronli5.js +1532 -0
- package/dist/lang/de.cjs +546 -0
- package/dist/lang/de.js +522 -0
- package/dist/lang/en.cjs +830 -0
- package/dist/lang/en.js +806 -0
- package/dist/lang/es.cjs +1024 -0
- package/dist/lang/es.js +1000 -0
- package/dist/lang/fi.cjs +942 -0
- package/dist/lang/fi.js +918 -0
- package/dist/lang/zh.cjs +612 -0
- package/dist/lang/zh.js +588 -0
- package/package.json +92 -13
- package/src/browser.ts +9 -0
- package/src/core/analyze.ts +549 -0
- package/src/core/format.ts +51 -0
- package/src/core/index.ts +28 -0
- package/src/core/ir.ts +167 -0
- package/src/core/normalize.ts +143 -0
- package/src/core/parse.ts +132 -0
- package/src/core/shapes.ts +41 -0
- package/src/core/specs.ts +93 -0
- package/src/core/util.ts +28 -0
- package/src/core/validate.ts +170 -0
- package/src/cronli5.ts +95 -0
- package/src/lang/de/dialects.ts +52 -0
- package/src/lang/de/index.ts +820 -0
- package/src/lang/de/notes.md +79 -0
- package/src/lang/de/status.json +18 -0
- package/src/lang/en/dialects.ts +70 -0
- package/src/lang/en/index.ts +1290 -0
- package/src/lang/en/notes.md +44 -0
- package/src/lang/en/status.json +20 -0
- package/src/lang/es/dialects.ts +55 -0
- package/src/lang/es/index.ts +1737 -0
- package/src/lang/es/notes.md +63 -0
- package/src/lang/es/status.json +19 -0
- package/src/lang/fi/dialects.ts +31 -0
- package/src/lang/fi/index.ts +1499 -0
- package/src/lang/fi/notes.md +163 -0
- package/src/lang/fi/status.json +7 -0
- package/src/lang/zh/dialects.ts +27 -0
- package/src/lang/zh/index.ts +863 -0
- package/src/lang/zh/notes.md +118 -0
- package/src/lang/zh/status.json +7 -0
- package/src/types.ts +143 -0
- package/types/browser.d.ts +2 -0
- package/types/core/analyze.d.ts +13 -0
- package/types/core/format.d.ts +16 -0
- package/types/core/index.d.ts +8 -0
- package/types/core/ir.d.ts +185 -0
- package/types/core/normalize.d.ts +5 -0
- package/types/core/parse.d.ts +5 -0
- package/types/core/shapes.d.ts +6 -0
- package/types/core/specs.d.ts +27 -0
- package/types/core/util.d.ts +7 -0
- package/types/core/validate.d.ts +5 -0
- package/types/cronli5.d.ts +7 -0
- package/types/lang/de/dialects.d.ts +7 -0
- package/types/lang/de/index.d.ts +4 -0
- package/types/lang/en/dialects.d.ts +4 -0
- package/types/lang/en/index.d.ts +3 -0
- package/types/lang/es/dialects.d.ts +13 -0
- package/types/lang/es/index.d.ts +4 -0
- package/types/lang/fi/dialects.d.ts +4 -0
- package/types/lang/fi/index.d.ts +3 -0
- package/types/lang/zh/dialects.d.ts +6 -0
- package/types/lang/zh/index.d.ts +4 -0
- package/types/types.d.ts +113 -0
- package/.eslintrc.json +0 -217
- package/.npmignore +0 -2
- package/conli5.min.js +0 -4
- package/cronli5.js +0 -559
- package/test/.eslintrc.json +0 -10
- package/test/bad_input/arrays.js +0 -34
- package/test/bad_input/bad-types.js +0 -33
- package/test/bad_input/error-types.js +0 -7
- package/test/bad_input/objects.js +0 -47
- package/test/bad_input/strings.js +0 -10
- package/test/baseline/baseline.js +0 -14
- package/test/basic/arrays.js +0 -76
- package/test/basic/objects.js +0 -70
- package/test/basic/strings.js +0 -76
- package/test/complex/steps/strings.js +0 -42
- package/test/mocha.opts +0 -5
- package/test/options/ampm.js +0 -17
- package/test/options/seconds.js +0 -0
- package/test/options/short.js +0 -27
- package/test/options/years.js +0 -0
- package/test/runner.js +0 -52
- package/test/simple/arrays.js +0 -33
- package/test/simple/objects.js +0 -23
- package/test/simple/strings.js +0 -33
package/dist/cronli5.js
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
// src/core/specs.ts
|
|
2
|
+
var weekdayNumbers = {
|
|
3
|
+
SUN: 0,
|
|
4
|
+
MON: 1,
|
|
5
|
+
TUE: 2,
|
|
6
|
+
WED: 3,
|
|
7
|
+
THU: 4,
|
|
8
|
+
FRI: 5,
|
|
9
|
+
SAT: 6
|
|
10
|
+
};
|
|
11
|
+
var monthNumbers = {
|
|
12
|
+
JAN: 1,
|
|
13
|
+
FEB: 2,
|
|
14
|
+
MAR: 3,
|
|
15
|
+
APR: 4,
|
|
16
|
+
MAY: 5,
|
|
17
|
+
JUN: 6,
|
|
18
|
+
JUL: 7,
|
|
19
|
+
AUG: 8,
|
|
20
|
+
SEP: 9,
|
|
21
|
+
OCT: 10,
|
|
22
|
+
NOV: 11,
|
|
23
|
+
DEC: 12
|
|
24
|
+
};
|
|
25
|
+
var fieldSpecs = {
|
|
26
|
+
second: { cyclic: true, max: 59, min: 0, top: 59 },
|
|
27
|
+
minute: { cyclic: true, max: 59, min: 0, top: 59 },
|
|
28
|
+
hour: { cyclic: true, max: 23, min: 0, top: 23 },
|
|
29
|
+
date: { aliases: { "?": "*" }, cyclic: true, max: 31, min: 1, top: 31 },
|
|
30
|
+
month: { cyclic: true, max: 12, min: 1, numbers: monthNumbers, top: 12 },
|
|
31
|
+
weekday: {
|
|
32
|
+
aliases: { "?": "*", L: "6" },
|
|
33
|
+
cyclic: true,
|
|
34
|
+
max: 7,
|
|
35
|
+
min: 0,
|
|
36
|
+
numbers: weekdayNumbers,
|
|
37
|
+
top: 6
|
|
38
|
+
},
|
|
39
|
+
year: { max: 9999, min: 1970 }
|
|
40
|
+
};
|
|
41
|
+
var fieldOrder = [
|
|
42
|
+
"second",
|
|
43
|
+
"minute",
|
|
44
|
+
"hour",
|
|
45
|
+
"date",
|
|
46
|
+
"month",
|
|
47
|
+
"weekday",
|
|
48
|
+
"year"
|
|
49
|
+
];
|
|
50
|
+
var macros = {
|
|
51
|
+
"@annually": "0 0 1 1 *",
|
|
52
|
+
"@yearly": "0 0 1 1 *",
|
|
53
|
+
"@monthly": "0 0 1 * *",
|
|
54
|
+
"@weekly": "0 0 * * 0",
|
|
55
|
+
"@daily": "0 0 * * *",
|
|
56
|
+
"@midnight": "0 0 * * *",
|
|
57
|
+
"@hourly": "0 * * * *"
|
|
58
|
+
};
|
|
59
|
+
var maxClockTimes = 6;
|
|
60
|
+
|
|
61
|
+
// src/core/util.ts
|
|
62
|
+
function includes(str, sub) {
|
|
63
|
+
return ("" + str).indexOf(sub) !== -1;
|
|
64
|
+
}
|
|
65
|
+
function unique(items) {
|
|
66
|
+
return Array.from(new Set(items));
|
|
67
|
+
}
|
|
68
|
+
function isNonNegativeInteger(value) {
|
|
69
|
+
const digits = /^\d+$/;
|
|
70
|
+
return digits.test(value);
|
|
71
|
+
}
|
|
72
|
+
function toFieldNumber(token, numberMap) {
|
|
73
|
+
return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/core/validate.ts
|
|
77
|
+
function validateCronPattern(cronPattern) {
|
|
78
|
+
fieldOrder.forEach(function validate(field) {
|
|
79
|
+
validateField(cronPattern[field], fieldSpecs[field], field);
|
|
80
|
+
});
|
|
81
|
+
return cronPattern;
|
|
82
|
+
}
|
|
83
|
+
function validateField(value, spec, field) {
|
|
84
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
85
|
+
throwInvalidField(value, field);
|
|
86
|
+
}
|
|
87
|
+
const stringValue = "" + value;
|
|
88
|
+
if (stringValue === "*") {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (field === "date" && isQuartzDate(stringValue) || field === "weekday" && isQuartzWeekday(stringValue, spec)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
stringValue.split(",").forEach(function check(segment) {
|
|
95
|
+
if (!isValidSegment(segment, spec)) {
|
|
96
|
+
throwInvalidField(segment, field);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function isQuartzDate(value) {
|
|
101
|
+
if (value === "L" || value === "LW" || value === "WL") {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
const offset = /^L-(\d{1,2})$/.exec(value);
|
|
105
|
+
if (offset) {
|
|
106
|
+
return +offset[1] >= 1 && +offset[1] <= 30;
|
|
107
|
+
}
|
|
108
|
+
const nearest = /^(\d{1,2})W$|^W(\d{1,2})$/.exec(value);
|
|
109
|
+
if (nearest) {
|
|
110
|
+
const day = +(nearest[1] || nearest[2]);
|
|
111
|
+
return day >= 1 && day <= 31;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function isQuartzWeekday(value, spec) {
|
|
116
|
+
if (/L$/.test(value)) {
|
|
117
|
+
return isValidSingle(value.slice(0, -1), spec);
|
|
118
|
+
}
|
|
119
|
+
const parts = value.split("#");
|
|
120
|
+
if (parts.length === 2) {
|
|
121
|
+
return isValidSingle(parts[0], spec) && /^[1-5]$/.test(parts[1]);
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function isValidSegment(segment, spec) {
|
|
126
|
+
if (includes(segment, "/")) {
|
|
127
|
+
return isValidStep(segment, spec);
|
|
128
|
+
}
|
|
129
|
+
if (includes(segment, "-")) {
|
|
130
|
+
return isValidRange(segment, spec);
|
|
131
|
+
}
|
|
132
|
+
return isValidSingle(segment, spec);
|
|
133
|
+
}
|
|
134
|
+
function isValidStep(segment, spec) {
|
|
135
|
+
const parts = segment.split("/");
|
|
136
|
+
if (parts.length !== 2 || !isNonNegativeInteger(parts[1]) || +parts[1] < 1) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return parts[0] === "*" || isValidSingle(parts[0], spec) || isValidRange(parts[0], spec, true);
|
|
140
|
+
}
|
|
141
|
+
function isValidRange(segment, spec, requireOrdered) {
|
|
142
|
+
const parts = segment.split("-");
|
|
143
|
+
if (parts.length !== 2) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (!isValidSingle(parts[0], spec) || !isValidSingle(parts[1], spec)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (spec.cyclic && !requireOrdered) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return toFieldNumber(parts[0], spec.numbers) <= toFieldNumber(parts[1], spec.numbers);
|
|
153
|
+
}
|
|
154
|
+
function isValidSingle(value, spec) {
|
|
155
|
+
if (value === "*") {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
if (isNonNegativeInteger(value)) {
|
|
159
|
+
return +value >= spec.min && +value <= spec.max;
|
|
160
|
+
}
|
|
161
|
+
if (spec.numbers) {
|
|
162
|
+
return value.toUpperCase() in spec.numbers;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
function throwInvalidField(value, field) {
|
|
167
|
+
throw new Error('`cronli5` was passed an invalid field value "' + value + '" for the ' + field + " field.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/normalize.ts
|
|
171
|
+
function applyQuartzAliases(cronPattern) {
|
|
172
|
+
fieldOrder.forEach(function apply(field) {
|
|
173
|
+
const aliases = fieldSpecs[field].aliases;
|
|
174
|
+
const alias = aliases && aliases["" + cronPattern[field]];
|
|
175
|
+
if (alias) {
|
|
176
|
+
cronPattern[field] = alias;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function normalizeCronPattern(cronPattern) {
|
|
181
|
+
fieldOrder.forEach(function normalize(field) {
|
|
182
|
+
const value = "" + cronPattern[field];
|
|
183
|
+
if (field === "date" && isQuartzDate(value) || field === "weekday" && isQuartzWeekday(value, fieldSpecs[field])) {
|
|
184
|
+
cronPattern[field] = value;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
cronPattern[field] = normalizeField(value, fieldSpecs[field]);
|
|
188
|
+
});
|
|
189
|
+
return cronPattern;
|
|
190
|
+
}
|
|
191
|
+
function normalizeField(value, spec) {
|
|
192
|
+
const stringValue = "" + value;
|
|
193
|
+
if (stringValue === "*") {
|
|
194
|
+
return stringValue;
|
|
195
|
+
}
|
|
196
|
+
const segments = stringValue.split(",").map(function canonical(segment) {
|
|
197
|
+
return collapseDegenerateRange(
|
|
198
|
+
collapseOnceStep(collapseUnitStep(segment, spec), spec),
|
|
199
|
+
spec
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
if (segments.indexOf("*") !== -1) {
|
|
203
|
+
return "*";
|
|
204
|
+
}
|
|
205
|
+
return unique(segments).sort(function ascending(a, b) {
|
|
206
|
+
return firstFire(a, spec) - firstFire(b, spec);
|
|
207
|
+
}).join(",");
|
|
208
|
+
}
|
|
209
|
+
function collapseUnitStep(segment, spec) {
|
|
210
|
+
const parts = segment.split("/");
|
|
211
|
+
if (!spec.cyclic || parts.length !== 2 || +parts[1] !== 1) {
|
|
212
|
+
return segment;
|
|
213
|
+
}
|
|
214
|
+
const start = parts[0];
|
|
215
|
+
if (includes(start, "-")) {
|
|
216
|
+
return start;
|
|
217
|
+
}
|
|
218
|
+
if (start === "*" || toFieldNumber(start, spec.numbers) === spec.min) {
|
|
219
|
+
return "*";
|
|
220
|
+
}
|
|
221
|
+
return start + "-" + spec.top;
|
|
222
|
+
}
|
|
223
|
+
function collapseOnceStep(segment, spec) {
|
|
224
|
+
const parts = segment.split("/");
|
|
225
|
+
if (!spec.cyclic || typeof spec.top !== "number" || parts.length !== 2 || includes(parts[0], "-")) {
|
|
226
|
+
return segment;
|
|
227
|
+
}
|
|
228
|
+
const start = parts[0];
|
|
229
|
+
const first = start === "*" ? spec.min : toFieldNumber(start, spec.numbers);
|
|
230
|
+
if (first + +parts[1] <= spec.top) {
|
|
231
|
+
return segment;
|
|
232
|
+
}
|
|
233
|
+
return start === "*" ? "" + spec.min : start;
|
|
234
|
+
}
|
|
235
|
+
function collapseDegenerateRange(segment, spec) {
|
|
236
|
+
const start = segment.split("/")[0];
|
|
237
|
+
if (!includes(start, "-")) {
|
|
238
|
+
return segment;
|
|
239
|
+
}
|
|
240
|
+
const bounds = start.split("-");
|
|
241
|
+
if (toFieldNumber(bounds[0], spec.numbers) !== toFieldNumber(bounds[1], spec.numbers)) {
|
|
242
|
+
return segment;
|
|
243
|
+
}
|
|
244
|
+
return bounds[0];
|
|
245
|
+
}
|
|
246
|
+
function firstFire(segment, spec) {
|
|
247
|
+
const start = segment.split("/")[0].split("-")[0];
|
|
248
|
+
return start === "*" ? spec.min : toFieldNumber(start, spec.numbers);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/core/parse.ts
|
|
252
|
+
function parseCronPattern(cronPattern, opts) {
|
|
253
|
+
const isArray = cronPattern instanceof Array;
|
|
254
|
+
const isEmpty = cronPattern === null || typeof cronPattern === "undefined" || cronPattern === "" || isArray && cronPattern.length === 0;
|
|
255
|
+
if (isEmpty) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"`cronli5` expects a non-empty cron pattern as the first argument."
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (isArray) {
|
|
261
|
+
return cronifyArray(cronPattern, opts);
|
|
262
|
+
}
|
|
263
|
+
if (typeof cronPattern === "object") {
|
|
264
|
+
return cronifyObject(cronPattern);
|
|
265
|
+
}
|
|
266
|
+
if (typeof cronPattern === "string") {
|
|
267
|
+
return cronifyString(cronPattern, opts);
|
|
268
|
+
}
|
|
269
|
+
throw new Error("`cronli5` was passed an unexpected type.");
|
|
270
|
+
}
|
|
271
|
+
function cronifyArray(cronlikeArray, opts) {
|
|
272
|
+
if (cronlikeArray.length > 7) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"`cronli5` was passed a cron pattern with more than seven fields."
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (!opts.seconds && cronlikeArray.length < (opts.years ? 7 : 6)) {
|
|
278
|
+
cronlikeArray.unshift("0");
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
second: cronlikeArray[0] || "0",
|
|
282
|
+
minute: cronlikeArray[1] || "*",
|
|
283
|
+
hour: cronlikeArray[2] || "*",
|
|
284
|
+
date: cronlikeArray[3] || "*",
|
|
285
|
+
month: cronlikeArray[4] || "*",
|
|
286
|
+
weekday: cronlikeArray[5] || "*",
|
|
287
|
+
year: cronlikeArray[6] || "*"
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function cronifyObject(cronable) {
|
|
291
|
+
if (!cronable.second && !cronable.minute && !cronable.hour) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"`cronli5` expects that any object being interpreted as a cron pattern have at least one of the following properties: `second`, `minute`, or `hour`"
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const hasSecond = typeof cronable.second !== "undefined";
|
|
297
|
+
const hasMinute = typeof cronable.minute !== "undefined";
|
|
298
|
+
const defaultMinute = hasSecond ? "*" : "0";
|
|
299
|
+
const defaultHour = hasSecond || hasMinute ? "*" : "0";
|
|
300
|
+
return {
|
|
301
|
+
second: pick(cronable.second, "0"),
|
|
302
|
+
minute: pick(cronable.minute, defaultMinute),
|
|
303
|
+
hour: pick(cronable.hour, defaultHour),
|
|
304
|
+
date: pick(cronable.date, "*"),
|
|
305
|
+
month: pick(cronable.month, "*"),
|
|
306
|
+
weekday: pick(cronable.weekday, "*"),
|
|
307
|
+
year: pick(cronable.year, "*")
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function pick(value, fallback) {
|
|
311
|
+
return typeof value === "undefined" ? fallback : value;
|
|
312
|
+
}
|
|
313
|
+
function cronifyString(cronString, opts) {
|
|
314
|
+
const cronlikeArray = expandMacro(cronString).split(/\s+/);
|
|
315
|
+
return cronifyArray(cronlikeArray, opts);
|
|
316
|
+
}
|
|
317
|
+
function expandMacro(cronString) {
|
|
318
|
+
const trimmed = cronString.trim();
|
|
319
|
+
if (trimmed.charAt(0) !== "@") {
|
|
320
|
+
return cronString;
|
|
321
|
+
}
|
|
322
|
+
const macro = trimmed.toLowerCase();
|
|
323
|
+
if (Object.hasOwn(macros, macro)) {
|
|
324
|
+
return macros[macro];
|
|
325
|
+
}
|
|
326
|
+
throw new Error("`cronli5` does not recognize the macro `" + trimmed + "`.");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/core/shapes.ts
|
|
330
|
+
function isSingleValue(field) {
|
|
331
|
+
return field !== "*" && !includes(field, ",") && !includes(field, "-") && !includes(field, "/");
|
|
332
|
+
}
|
|
333
|
+
function isPlainRange(field) {
|
|
334
|
+
return includes(field, "-") && !includes(field, ",") && !includes(field, "/");
|
|
335
|
+
}
|
|
336
|
+
function isPlainStep(field) {
|
|
337
|
+
return includes(field, "/") && !includes(field, ",");
|
|
338
|
+
}
|
|
339
|
+
function isDiscreteList(field) {
|
|
340
|
+
return field !== "*" && !includes(field, "-") && !includes(field, "/");
|
|
341
|
+
}
|
|
342
|
+
function isDiscreteHours(hourField) {
|
|
343
|
+
return hourField !== "*" && !isPlainRange(hourField) && !isPlainStep(hourField);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/core/analyze.ts
|
|
347
|
+
function getOccurrences(start, interval, max) {
|
|
348
|
+
const occurrences = [];
|
|
349
|
+
let value = start;
|
|
350
|
+
while (value <= max) {
|
|
351
|
+
occurrences.push(value);
|
|
352
|
+
value += interval;
|
|
353
|
+
}
|
|
354
|
+
return occurrences;
|
|
355
|
+
}
|
|
356
|
+
function enumerateStep(field, min, max, numberMap) {
|
|
357
|
+
const parts = field.split("/");
|
|
358
|
+
const interval = +parts[1];
|
|
359
|
+
if (includes(parts[0], "-")) {
|
|
360
|
+
const bounds = parts[0].split("-");
|
|
361
|
+
return getOccurrences(
|
|
362
|
+
toFieldNumber(bounds[0], numberMap),
|
|
363
|
+
interval,
|
|
364
|
+
toFieldNumber(bounds[1], numberMap)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const start = parts[0] === "*" ? min : toFieldNumber(parts[0], numberMap);
|
|
368
|
+
return getOccurrences(start, interval, max);
|
|
369
|
+
}
|
|
370
|
+
function enumerateFires(field, min, max) {
|
|
371
|
+
const fires = [];
|
|
372
|
+
field.split(",").forEach(function expand(segment) {
|
|
373
|
+
if (includes(segment, "/")) {
|
|
374
|
+
fires.push(...enumerateStep(segment, min, max));
|
|
375
|
+
} else if (includes(segment, "-")) {
|
|
376
|
+
const bounds = segment.split("-");
|
|
377
|
+
if (+bounds[0] <= +bounds[1]) {
|
|
378
|
+
fires.push(...getOccurrences(+bounds[0], 1, +bounds[1]));
|
|
379
|
+
} else {
|
|
380
|
+
fires.push(...getOccurrences(+bounds[0], 1, max));
|
|
381
|
+
fires.push(...getOccurrences(min, 1, +bounds[1]));
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
fires.push(+segment);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return unique(fires);
|
|
388
|
+
}
|
|
389
|
+
function enumerateValues(field) {
|
|
390
|
+
if (!isDiscreteList(field)) {
|
|
391
|
+
return [0];
|
|
392
|
+
}
|
|
393
|
+
return field.split(",").map(Number);
|
|
394
|
+
}
|
|
395
|
+
function minuteSpan(minuteField) {
|
|
396
|
+
if (minuteField === "*") {
|
|
397
|
+
return [0, 59];
|
|
398
|
+
}
|
|
399
|
+
if (isPlainRange(minuteField)) {
|
|
400
|
+
const bounds = minuteField.split("-");
|
|
401
|
+
if (+bounds[0] <= +bounds[1]) {
|
|
402
|
+
return [+bounds[0], +bounds[1]];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
function lastMinuteFire(minuteField) {
|
|
408
|
+
if (minuteField === "*") {
|
|
409
|
+
return 59;
|
|
410
|
+
}
|
|
411
|
+
return Math.max(...enumerateFires(minuteField, 0, 59));
|
|
412
|
+
}
|
|
413
|
+
function clockSecond(secondField) {
|
|
414
|
+
if (isSingleValue(secondField) && secondField !== "0") {
|
|
415
|
+
return +secondField;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function fieldShape(value, field) {
|
|
419
|
+
if (value === "*") {
|
|
420
|
+
return "wildcard";
|
|
421
|
+
}
|
|
422
|
+
if (field === "date" && isQuartzDate(value) || field === "weekday" && isQuartzWeekday(value, fieldSpecs.weekday)) {
|
|
423
|
+
return "quartz";
|
|
424
|
+
}
|
|
425
|
+
if (includes(value, ",")) {
|
|
426
|
+
return "list";
|
|
427
|
+
}
|
|
428
|
+
if (includes(value, "/")) {
|
|
429
|
+
return "step";
|
|
430
|
+
}
|
|
431
|
+
if (includes(value, "-")) {
|
|
432
|
+
return "range";
|
|
433
|
+
}
|
|
434
|
+
return "single";
|
|
435
|
+
}
|
|
436
|
+
function fieldSegments(value, shape, spec) {
|
|
437
|
+
if (shape === "wildcard" || shape === "quartz") {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return value.split(",").map(function classify(segment) {
|
|
441
|
+
if (includes(segment, "/")) {
|
|
442
|
+
const parts = segment.split("/");
|
|
443
|
+
return {
|
|
444
|
+
// Only the named/cyclic fields are segmented this way; `top` is
|
|
445
|
+
// always present for them (year has no segmentable step form).
|
|
446
|
+
fires: enumerateStep(
|
|
447
|
+
segment,
|
|
448
|
+
spec.min,
|
|
449
|
+
spec.top,
|
|
450
|
+
spec.numbers
|
|
451
|
+
),
|
|
452
|
+
interval: +parts[1],
|
|
453
|
+
kind: "step",
|
|
454
|
+
startToken: parts[0]
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (includes(segment, "-")) {
|
|
458
|
+
return { bounds: segment.split("-"), kind: "range" };
|
|
459
|
+
}
|
|
460
|
+
return { kind: "single", value: segment };
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function analyze(pattern) {
|
|
464
|
+
const shapes = {};
|
|
465
|
+
const segments = {};
|
|
466
|
+
fieldOrder.forEach(function classify(field) {
|
|
467
|
+
shapes[field] = fieldShape(pattern[field], field);
|
|
468
|
+
segments[field] = fieldSegments(
|
|
469
|
+
pattern[field],
|
|
470
|
+
shapes[field],
|
|
471
|
+
fieldSpecs[field]
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
const analyses = {
|
|
475
|
+
clockSecond: clockSecond(pattern.second),
|
|
476
|
+
lastMinuteFire: lastMinuteFire(pattern.minute),
|
|
477
|
+
minuteSpan: minuteSpan(pattern.minute),
|
|
478
|
+
segments
|
|
479
|
+
};
|
|
480
|
+
const content = { analyses, pattern, shapes };
|
|
481
|
+
return { ...content, plan: selectStrategy(content) };
|
|
482
|
+
}
|
|
483
|
+
function selectStrategy(content) {
|
|
484
|
+
const { analyses, pattern, shapes } = content;
|
|
485
|
+
if (pattern.second !== "0") {
|
|
486
|
+
const seconds = planSeconds(pattern, shapes, analyses);
|
|
487
|
+
if (seconds) {
|
|
488
|
+
return seconds;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return planMinutes(pattern, shapes, analyses) || planHours(pattern, shapes, analyses);
|
|
492
|
+
}
|
|
493
|
+
function planSeconds(pattern, shapes, analyses) {
|
|
494
|
+
const standalone = planStandaloneSeconds(pattern, shapes);
|
|
495
|
+
if (standalone) {
|
|
496
|
+
return standalone;
|
|
497
|
+
}
|
|
498
|
+
if (pattern.hour === "*" && shapes.minute === "single" && pattern.second !== "*") {
|
|
499
|
+
return {
|
|
500
|
+
kind: "secondsWithinMinute",
|
|
501
|
+
singleSecond: shapes.second === "single"
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (shapes.second === "single" && isDiscreteList(pattern.minute) && isDiscreteHours(pattern.hour)) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
kind: "composeSeconds",
|
|
509
|
+
rest: planMinutes(pattern, shapes, analyses) || planHours(pattern, shapes, analyses)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function planStandaloneSeconds(pattern, shapes) {
|
|
513
|
+
if (pattern.minute !== "*" || pattern.hour !== "*") {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
if (pattern.second === "*") {
|
|
517
|
+
return { kind: "everySecond" };
|
|
518
|
+
}
|
|
519
|
+
if (shapes.second === "single") {
|
|
520
|
+
return { kind: "secondPastMinute" };
|
|
521
|
+
}
|
|
522
|
+
return { kind: "standaloneSeconds" };
|
|
523
|
+
}
|
|
524
|
+
function planMinutes(pattern, shapes, analyses) {
|
|
525
|
+
if (shapes.minute === "step") {
|
|
526
|
+
return {
|
|
527
|
+
hours: planFrequencyHours(pattern, shapes, analyses),
|
|
528
|
+
kind: "minuteFrequency"
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (shapes.hour === "single" && analyses.minuteSpan) {
|
|
532
|
+
return {
|
|
533
|
+
hour: +pattern.hour,
|
|
534
|
+
kind: "minuteSpanInHour",
|
|
535
|
+
span: analyses.minuteSpan
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const acrossHours = planMinutesAcrossHours(pattern, shapes);
|
|
539
|
+
if (acrossHours) {
|
|
540
|
+
return acrossHours;
|
|
541
|
+
}
|
|
542
|
+
const underStep = planMinuteUnderHourStep(pattern, shapes);
|
|
543
|
+
if (underStep) {
|
|
544
|
+
return underStep;
|
|
545
|
+
}
|
|
546
|
+
if (pattern.hour === "*") {
|
|
547
|
+
return planMinutesUnderOpenHour(pattern, shapes);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function cleanHourStride(hourField) {
|
|
551
|
+
const [start, step] = hourField.split("/");
|
|
552
|
+
const startHour = start === "*" ? 0 : +start;
|
|
553
|
+
return start.indexOf("-") === -1 && 24 % +step === 0 && startHour < +step;
|
|
554
|
+
}
|
|
555
|
+
function planMinuteUnderHourStep(pattern, shapes) {
|
|
556
|
+
if (shapes.hour !== "step") {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
if (pattern.minute === "*") {
|
|
560
|
+
return cleanHourStride(pattern.hour) ? { form: "wildcard", kind: "minuteSpanAcrossHourStep" } : {
|
|
561
|
+
form: "wildcard",
|
|
562
|
+
kind: "minutesAcrossHours",
|
|
563
|
+
times: hourTimesPlan(pattern.hour)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (shapes.minute === "range") {
|
|
567
|
+
return { form: "range", kind: "minuteSpanAcrossHourStep" };
|
|
568
|
+
}
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
function planFrequencyHours(pattern, shapes, analyses) {
|
|
572
|
+
if (shapes.hour === "list") {
|
|
573
|
+
return { kind: "during", times: hourTimesPlan(pattern.hour) };
|
|
574
|
+
}
|
|
575
|
+
if (shapes.hour === "range") {
|
|
576
|
+
const bounds = pattern.hour.split("-");
|
|
577
|
+
return {
|
|
578
|
+
from: +bounds[0],
|
|
579
|
+
kind: "window",
|
|
580
|
+
last: analyses.lastMinuteFire,
|
|
581
|
+
to: +bounds[1]
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
if (shapes.hour === "single") {
|
|
585
|
+
return {
|
|
586
|
+
from: +pattern.hour,
|
|
587
|
+
kind: "window",
|
|
588
|
+
last: analyses.lastMinuteFire,
|
|
589
|
+
to: +pattern.hour
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (shapes.hour === "step") {
|
|
593
|
+
return cleanHourStride(pattern.hour) ? { kind: "step" } : { kind: "during", times: hourTimesPlan(pattern.hour) };
|
|
594
|
+
}
|
|
595
|
+
return { kind: "none" };
|
|
596
|
+
}
|
|
597
|
+
function planMinutesAcrossHours(pattern, shapes) {
|
|
598
|
+
if (!isDiscreteHours(pattern.hour)) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
if (pattern.minute === "*") {
|
|
602
|
+
return {
|
|
603
|
+
form: "wildcard",
|
|
604
|
+
kind: "minutesAcrossHours",
|
|
605
|
+
times: hourTimesPlan(pattern.hour)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (shapes.minute === "range" || shapes.minute === "list" && includes(pattern.minute, "-") && !includes(pattern.minute, "/")) {
|
|
609
|
+
return {
|
|
610
|
+
form: shapes.minute === "range" ? "range" : "list",
|
|
611
|
+
kind: "minutesAcrossHours",
|
|
612
|
+
times: hourTimesPlan(pattern.hour)
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
function planMinutesUnderOpenHour(pattern, shapes) {
|
|
618
|
+
if (shapes.minute === "range") {
|
|
619
|
+
return { kind: "rangeOfMinutes" };
|
|
620
|
+
}
|
|
621
|
+
if (shapes.minute === "list") {
|
|
622
|
+
return { kind: "multipleMinutes" };
|
|
623
|
+
}
|
|
624
|
+
if (pattern.minute === "*") {
|
|
625
|
+
return { kind: "everyMinute" };
|
|
626
|
+
}
|
|
627
|
+
if (pattern.minute !== "0") {
|
|
628
|
+
return { kind: "singleMinute" };
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function planHours(pattern, shapes, analyses) {
|
|
632
|
+
if (shapes.hour === "range") {
|
|
633
|
+
const bounds = pattern.hour.split("-");
|
|
634
|
+
let minuteForm = "lead";
|
|
635
|
+
if (pattern.minute === "*") {
|
|
636
|
+
minuteForm = "wildcard";
|
|
637
|
+
} else if (shapes.minute === "range") {
|
|
638
|
+
minuteForm = "range";
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
from: +bounds[0],
|
|
642
|
+
kind: "hourRange",
|
|
643
|
+
last: analyses.lastMinuteFire,
|
|
644
|
+
minuteForm,
|
|
645
|
+
to: +bounds[1]
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (shapes.hour === "step" && pattern.minute === "0") {
|
|
649
|
+
return { kind: "hourStep" };
|
|
650
|
+
}
|
|
651
|
+
if (pattern.hour === "*") {
|
|
652
|
+
return { kind: "everyHour" };
|
|
653
|
+
}
|
|
654
|
+
return planClockTimes(pattern, analyses);
|
|
655
|
+
}
|
|
656
|
+
function planClockTimes(pattern, analyses) {
|
|
657
|
+
const hours = enumerateFires(pattern.hour, 0, 23);
|
|
658
|
+
const minutes = enumerateValues(pattern.minute);
|
|
659
|
+
if (hours.length * minutes.length > maxClockTimes) {
|
|
660
|
+
return {
|
|
661
|
+
fold: minutes.length === 1,
|
|
662
|
+
kind: "compactClockTimes",
|
|
663
|
+
minute: minutes[0]
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const times = [];
|
|
667
|
+
hours.forEach(function eachHour(hour) {
|
|
668
|
+
minutes.forEach(function eachMinute(minute) {
|
|
669
|
+
times.push({ hour, minute, second: analyses.clockSecond });
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
return { kind: "clockTimes", times };
|
|
673
|
+
}
|
|
674
|
+
function hourTimesPlan(hourField) {
|
|
675
|
+
const fires = enumerateFires(hourField, 0, 23);
|
|
676
|
+
if (fires.length <= maxClockTimes) {
|
|
677
|
+
return { fires, kind: "fires" };
|
|
678
|
+
}
|
|
679
|
+
return { kind: "segments" };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/core/index.ts
|
|
683
|
+
function prepare(cronPattern, opts) {
|
|
684
|
+
const pattern = parseCronPattern(cronPattern, opts);
|
|
685
|
+
applyQuartzAliases(pattern);
|
|
686
|
+
validateCronPattern(pattern);
|
|
687
|
+
return normalizeCronPattern(pattern);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/core/format.ts
|
|
691
|
+
function pad(n) {
|
|
692
|
+
n = "" + n;
|
|
693
|
+
return n.length < 2 ? "0" + n : n;
|
|
694
|
+
}
|
|
695
|
+
function numeral(n, words, opts) {
|
|
696
|
+
return opts.short ? n : words[n] || n;
|
|
697
|
+
}
|
|
698
|
+
function clockDigits(time, { sep, pad: padHour, lean }) {
|
|
699
|
+
const head = padHour ? pad(time.hour) : "" + time.hour;
|
|
700
|
+
if (lean && !time.minute && !time.second) {
|
|
701
|
+
return head;
|
|
702
|
+
}
|
|
703
|
+
return head + sep + pad(time.minute) + (time.second ? sep + pad(time.second) : "");
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/lang/en/dialects.ts
|
|
707
|
+
var dialects = {
|
|
708
|
+
gb: {
|
|
709
|
+
am: "am",
|
|
710
|
+
closeUp: true,
|
|
711
|
+
dayFirst: true,
|
|
712
|
+
midday: "midday",
|
|
713
|
+
midnight: "midnight",
|
|
714
|
+
ordinals: false,
|
|
715
|
+
pm: "pm",
|
|
716
|
+
sep: ".",
|
|
717
|
+
serialComma: false,
|
|
718
|
+
through: " to "
|
|
719
|
+
},
|
|
720
|
+
us: {
|
|
721
|
+
am: "a.m.",
|
|
722
|
+
closeUp: false,
|
|
723
|
+
dayFirst: false,
|
|
724
|
+
midday: "noon",
|
|
725
|
+
midnight: "midnight",
|
|
726
|
+
ordinals: false,
|
|
727
|
+
pm: "p.m.",
|
|
728
|
+
sep: ":",
|
|
729
|
+
serialComma: true,
|
|
730
|
+
through: " through "
|
|
731
|
+
},
|
|
732
|
+
house: {
|
|
733
|
+
am: "AM",
|
|
734
|
+
closeUp: false,
|
|
735
|
+
dayFirst: false,
|
|
736
|
+
midday: "noon",
|
|
737
|
+
midnight: "midnight",
|
|
738
|
+
ordinals: true,
|
|
739
|
+
pm: "PM",
|
|
740
|
+
sep: ":",
|
|
741
|
+
serialComma: true,
|
|
742
|
+
through: " - "
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
function resolveDialect(dialect) {
|
|
746
|
+
if (typeof dialect === "object" && dialect !== null) {
|
|
747
|
+
return { ...dialects.us, ...dialect };
|
|
748
|
+
}
|
|
749
|
+
const name = dialect === "uk" ? "gb" : dialect;
|
|
750
|
+
return dialects[name] || dialects.us;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/lang/en/index.ts
|
|
754
|
+
var numbers = [
|
|
755
|
+
"zero",
|
|
756
|
+
"one",
|
|
757
|
+
"two",
|
|
758
|
+
"three",
|
|
759
|
+
"four",
|
|
760
|
+
"five",
|
|
761
|
+
"six",
|
|
762
|
+
"seven",
|
|
763
|
+
"eight",
|
|
764
|
+
"nine",
|
|
765
|
+
"ten"
|
|
766
|
+
];
|
|
767
|
+
var suffixes = [
|
|
768
|
+
"th",
|
|
769
|
+
"st",
|
|
770
|
+
"nd",
|
|
771
|
+
"rd"
|
|
772
|
+
];
|
|
773
|
+
var monthNames = [
|
|
774
|
+
null,
|
|
775
|
+
["January", "Jan"],
|
|
776
|
+
["February", "Feb"],
|
|
777
|
+
["March", "Mar"],
|
|
778
|
+
["April", "Apr"],
|
|
779
|
+
["May", "May"],
|
|
780
|
+
["June", "Jun"],
|
|
781
|
+
["July", "Jul"],
|
|
782
|
+
["August", "Aug"],
|
|
783
|
+
["September", "Sep"],
|
|
784
|
+
["October", "Oct"],
|
|
785
|
+
["November", "Nov"],
|
|
786
|
+
["December", "Dec"]
|
|
787
|
+
];
|
|
788
|
+
var weekdayNames = [
|
|
789
|
+
["Sunday", "Sun"],
|
|
790
|
+
["Monday", "Mon"],
|
|
791
|
+
["Tuesday", "Tue"],
|
|
792
|
+
["Wednesday", "Wed"],
|
|
793
|
+
["Thursday", "Thu"],
|
|
794
|
+
["Friday", "Fri"],
|
|
795
|
+
["Saturday", "Sat"]
|
|
796
|
+
];
|
|
797
|
+
var monthAbbreviations = {
|
|
798
|
+
JAN: monthNames[1],
|
|
799
|
+
FEB: monthNames[2],
|
|
800
|
+
MAR: monthNames[3],
|
|
801
|
+
APR: monthNames[4],
|
|
802
|
+
MAY: monthNames[5],
|
|
803
|
+
JUN: monthNames[6],
|
|
804
|
+
JUL: monthNames[7],
|
|
805
|
+
AUG: monthNames[8],
|
|
806
|
+
SEP: monthNames[9],
|
|
807
|
+
OCT: monthNames[10],
|
|
808
|
+
NOV: monthNames[11],
|
|
809
|
+
DEC: monthNames[12]
|
|
810
|
+
};
|
|
811
|
+
var weekdayAbbreviations = {
|
|
812
|
+
SUN: weekdayNames[0],
|
|
813
|
+
MON: weekdayNames[1],
|
|
814
|
+
TUE: weekdayNames[2],
|
|
815
|
+
WED: weekdayNames[3],
|
|
816
|
+
THU: weekdayNames[4],
|
|
817
|
+
FRI: weekdayNames[5],
|
|
818
|
+
SAT: weekdayNames[6]
|
|
819
|
+
};
|
|
820
|
+
var nthWeekdayNames = [null, "first", "second", "third", "fourth", "fifth"];
|
|
821
|
+
function normalizeOptions(options) {
|
|
822
|
+
options = options || {};
|
|
823
|
+
return {
|
|
824
|
+
ampm: typeof options.ampm === "boolean" ? options.ampm : true,
|
|
825
|
+
lenient: !!options.lenient,
|
|
826
|
+
seconds: !!options.seconds,
|
|
827
|
+
short: !!options.short,
|
|
828
|
+
style: resolveDialect(options.dialect),
|
|
829
|
+
years: !!options.years
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function describe(ir, opts) {
|
|
833
|
+
return applyYear(render(ir, ir.plan, opts), ir, opts);
|
|
834
|
+
}
|
|
835
|
+
function render(ir, plan, opts) {
|
|
836
|
+
const renderer = renderers[plan.kind];
|
|
837
|
+
return renderer(ir, plan, opts);
|
|
838
|
+
}
|
|
839
|
+
function renderEverySecond(ir, plan, opts) {
|
|
840
|
+
return "every second" + trailingQualifier(ir, opts);
|
|
841
|
+
}
|
|
842
|
+
function renderStandaloneSeconds(ir, plan, opts) {
|
|
843
|
+
return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
|
|
844
|
+
}
|
|
845
|
+
function renderSecondPastMinute(ir, plan, opts) {
|
|
846
|
+
const secondField = ir.pattern.second;
|
|
847
|
+
return getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute, every minute" + trailingQualifier(ir, opts);
|
|
848
|
+
}
|
|
849
|
+
function renderSecondsWithinMinute(ir, plan, opts) {
|
|
850
|
+
const minuteField = ir.pattern.minute;
|
|
851
|
+
const minuteWord = getNumber(minuteField, opts);
|
|
852
|
+
const minuteUnit = pluralize(minuteField, "minute");
|
|
853
|
+
if (plan.singleSecond) {
|
|
854
|
+
const secondField = ir.pattern.second;
|
|
855
|
+
return minuteWord + " " + minuteUnit + " and " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the hour, every hour" + trailingQualifier(ir, opts);
|
|
856
|
+
}
|
|
857
|
+
return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
|
|
858
|
+
}
|
|
859
|
+
function renderComposeSeconds(ir, plan, opts) {
|
|
860
|
+
return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
|
|
861
|
+
}
|
|
862
|
+
function secondsLeadClause(ir, opts) {
|
|
863
|
+
const secondField = ir.pattern.second;
|
|
864
|
+
const shape = ir.shapes.second;
|
|
865
|
+
if (secondField === "*") {
|
|
866
|
+
return "every second";
|
|
867
|
+
}
|
|
868
|
+
if (shape === "step") {
|
|
869
|
+
return stepCycle60(
|
|
870
|
+
ir.analyses.segments.second[0],
|
|
871
|
+
"second",
|
|
872
|
+
"minute",
|
|
873
|
+
opts
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
if (shape === "range") {
|
|
877
|
+
const bounds = secondField.split("-");
|
|
878
|
+
const num = seriesNumber(bounds, opts);
|
|
879
|
+
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
|
|
880
|
+
}
|
|
881
|
+
if (shape === "single") {
|
|
882
|
+
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
|
|
883
|
+
}
|
|
884
|
+
return listPastThe(
|
|
885
|
+
segmentWords(ir.analyses.segments.second, opts),
|
|
886
|
+
"second",
|
|
887
|
+
"minute",
|
|
888
|
+
opts
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
function renderEveryMinute(ir, plan, opts) {
|
|
892
|
+
return "every minute" + trailingQualifier(ir, opts);
|
|
893
|
+
}
|
|
894
|
+
function renderSingleMinute(ir, plan, opts) {
|
|
895
|
+
const minuteField = ir.pattern.minute;
|
|
896
|
+
return getNumber(minuteField, opts) + " " + pluralize(minuteField, "minute") + " past the hour, every hour" + trailingQualifier(ir, opts);
|
|
897
|
+
}
|
|
898
|
+
function renderRangeOfMinutes(ir, plan, opts) {
|
|
899
|
+
return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
|
|
900
|
+
}
|
|
901
|
+
function renderMultipleMinutes(ir, plan, opts) {
|
|
902
|
+
return listPastThe(
|
|
903
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
904
|
+
"minute",
|
|
905
|
+
"hour",
|
|
906
|
+
opts
|
|
907
|
+
) + trailingQualifier(ir, opts);
|
|
908
|
+
}
|
|
909
|
+
function renderMinuteFrequency(ir, plan, opts) {
|
|
910
|
+
let phrase = stepCycle60(
|
|
911
|
+
ir.analyses.segments.minute[0],
|
|
912
|
+
"minute",
|
|
913
|
+
"hour",
|
|
914
|
+
opts
|
|
915
|
+
);
|
|
916
|
+
if (plan.hours.kind === "during") {
|
|
917
|
+
phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
|
|
918
|
+
} else if (plan.hours.kind === "window") {
|
|
919
|
+
phrase += " " + hourWindow(plan.hours, opts);
|
|
920
|
+
} else if (plan.hours.kind === "step") {
|
|
921
|
+
phrase += " " + everyNthHour(ir.analyses.segments.hour[0], opts);
|
|
922
|
+
}
|
|
923
|
+
return phrase + trailingQualifier(ir, opts);
|
|
924
|
+
}
|
|
925
|
+
function renderMinuteSpanInHour(ir, plan, opts) {
|
|
926
|
+
return "every minute from " + getTime({ hour: plan.hour, minute: plan.span[0] }, opts) + through(opts) + getTime({ hour: plan.hour, minute: plan.span[1] }, opts) + trailingQualifier(ir, opts);
|
|
927
|
+
}
|
|
928
|
+
function renderMinutesAcrossHours(ir, plan, opts) {
|
|
929
|
+
if (plan.form === "wildcard") {
|
|
930
|
+
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
931
|
+
}
|
|
932
|
+
const times = hourTimesFromPlan(ir, plan.times, true, opts);
|
|
933
|
+
const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
|
|
934
|
+
// The 'list' form is a minute list, which has segments.
|
|
935
|
+
listPastThe(
|
|
936
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
937
|
+
"minute",
|
|
938
|
+
"hour",
|
|
939
|
+
opts
|
|
940
|
+
)
|
|
941
|
+
);
|
|
942
|
+
return lead + ", at " + times + trailingQualifier(ir, opts);
|
|
943
|
+
}
|
|
944
|
+
var stepOrdinals = {
|
|
945
|
+
2: "other",
|
|
946
|
+
3: "third",
|
|
947
|
+
4: "fourth",
|
|
948
|
+
6: "sixth",
|
|
949
|
+
8: "eighth",
|
|
950
|
+
12: "twelfth"
|
|
951
|
+
};
|
|
952
|
+
function everyNthHour(segment, opts) {
|
|
953
|
+
const base = "during every " + stepOrdinals[segment.interval] + " hour";
|
|
954
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
955
|
+
return start === 0 ? base : base + " starting at " + getTime({ hour: start, minute: 0 }, opts);
|
|
956
|
+
}
|
|
957
|
+
function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
958
|
+
const segment = ir.analyses.segments.hour[0];
|
|
959
|
+
if (plan.form === "wildcard") {
|
|
960
|
+
return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
|
|
961
|
+
}
|
|
962
|
+
return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
|
|
963
|
+
}
|
|
964
|
+
function minuteRangeLead(minuteField, opts) {
|
|
965
|
+
const bounds = minuteField.split("-");
|
|
966
|
+
const num = seriesNumber(bounds, opts);
|
|
967
|
+
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
968
|
+
}
|
|
969
|
+
function renderEveryHour(ir, plan, opts) {
|
|
970
|
+
return "every hour" + trailingQualifier(ir, opts);
|
|
971
|
+
}
|
|
972
|
+
function renderHourRange(ir, plan, opts) {
|
|
973
|
+
const window = hourWindow(plan, opts);
|
|
974
|
+
if (plan.minuteForm === "wildcard") {
|
|
975
|
+
return "every minute " + window + trailingQualifier(ir, opts);
|
|
976
|
+
}
|
|
977
|
+
if (plan.minuteForm === "range") {
|
|
978
|
+
return minuteRangeLead(ir.pattern.minute, opts) + ", " + window + trailingQualifier(ir, opts);
|
|
979
|
+
}
|
|
980
|
+
return rangeMinuteLead(ir, opts) + " " + window + trailingQualifier(ir, opts);
|
|
981
|
+
}
|
|
982
|
+
function rangeMinuteLead(ir, opts) {
|
|
983
|
+
if (ir.pattern.minute === "0") {
|
|
984
|
+
return "every hour";
|
|
985
|
+
}
|
|
986
|
+
return listPastThe(
|
|
987
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
988
|
+
"minute",
|
|
989
|
+
"hour",
|
|
990
|
+
opts
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
function renderHourStep(ir, plan, opts) {
|
|
994
|
+
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
995
|
+
}
|
|
996
|
+
function hourWindow(window, opts) {
|
|
997
|
+
return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
|
|
998
|
+
}
|
|
999
|
+
function renderClockTimes(ir, plan, opts) {
|
|
1000
|
+
const plain = mixedTwelve(plan.times);
|
|
1001
|
+
const times = plan.times.map(function clock(time) {
|
|
1002
|
+
return getTime({
|
|
1003
|
+
hour: time.hour,
|
|
1004
|
+
minute: time.minute,
|
|
1005
|
+
second: time.second,
|
|
1006
|
+
plain
|
|
1007
|
+
}, opts);
|
|
1008
|
+
});
|
|
1009
|
+
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts);
|
|
1010
|
+
}
|
|
1011
|
+
function renderCompactClockTimes(ir, plan, opts) {
|
|
1012
|
+
if (plan.fold) {
|
|
1013
|
+
const hasRange = ir.analyses.segments.hour.some(function range(segment) {
|
|
1014
|
+
return segment.kind === "range";
|
|
1015
|
+
});
|
|
1016
|
+
if (hasRange && !ir.analyses.clockSecond) {
|
|
1017
|
+
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
1018
|
+
}
|
|
1019
|
+
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1020
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1021
|
+
}
|
|
1022
|
+
const phrase = (
|
|
1023
|
+
// The non-fold branch is a minute list, which has segments.
|
|
1024
|
+
listPastThe(
|
|
1025
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
1026
|
+
"minute",
|
|
1027
|
+
"hour",
|
|
1028
|
+
opts
|
|
1029
|
+
) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
|
|
1030
|
+
);
|
|
1031
|
+
return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
|
|
1032
|
+
}
|
|
1033
|
+
function foldedHourWindows(ir, plan, opts) {
|
|
1034
|
+
const minute = plan.minute;
|
|
1035
|
+
const windows = [];
|
|
1036
|
+
const singles = [];
|
|
1037
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1038
|
+
if (segment.kind === "range") {
|
|
1039
|
+
windows.push("from " + getTime(
|
|
1040
|
+
{ hour: segment.bounds[0], minute: 0 },
|
|
1041
|
+
opts
|
|
1042
|
+
) + through(opts) + getTime({ hour: segment.bounds[1], minute }, opts));
|
|
1043
|
+
} else if (segment.kind === "step") {
|
|
1044
|
+
singles.push(...segment.fires);
|
|
1045
|
+
} else {
|
|
1046
|
+
singles.push(+segment.value);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
let phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
|
|
1050
|
+
if (singles.length) {
|
|
1051
|
+
phrase += " and at " + joinList(singles.map(function time(hour) {
|
|
1052
|
+
return getTime({ hour, minute }, opts);
|
|
1053
|
+
}), opts);
|
|
1054
|
+
}
|
|
1055
|
+
return phrase;
|
|
1056
|
+
}
|
|
1057
|
+
var renderers = {
|
|
1058
|
+
clockTimes: renderClockTimes,
|
|
1059
|
+
compactClockTimes: renderCompactClockTimes,
|
|
1060
|
+
composeSeconds: renderComposeSeconds,
|
|
1061
|
+
everyHour: renderEveryHour,
|
|
1062
|
+
everyMinute: renderEveryMinute,
|
|
1063
|
+
everySecond: renderEverySecond,
|
|
1064
|
+
hourRange: renderHourRange,
|
|
1065
|
+
hourStep: renderHourStep,
|
|
1066
|
+
minuteFrequency: renderMinuteFrequency,
|
|
1067
|
+
minuteSpanAcrossHourStep: renderMinuteSpanAcrossHourStep,
|
|
1068
|
+
minuteSpanInHour: renderMinuteSpanInHour,
|
|
1069
|
+
minutesAcrossHours: renderMinutesAcrossHours,
|
|
1070
|
+
multipleMinutes: renderMultipleMinutes,
|
|
1071
|
+
rangeOfMinutes: renderRangeOfMinutes,
|
|
1072
|
+
secondPastMinute: renderSecondPastMinute,
|
|
1073
|
+
secondsWithinMinute: renderSecondsWithinMinute,
|
|
1074
|
+
singleMinute: renderSingleMinute,
|
|
1075
|
+
standaloneSeconds: renderStandaloneSeconds
|
|
1076
|
+
};
|
|
1077
|
+
function stepCycle60(segment, unit, anchor, opts) {
|
|
1078
|
+
if (segment.startToken.indexOf("-") !== -1) {
|
|
1079
|
+
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1080
|
+
}
|
|
1081
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
1082
|
+
const interval = segment.interval;
|
|
1083
|
+
if (start !== 0) {
|
|
1084
|
+
if (segment.fires.length <= 3) {
|
|
1085
|
+
return listPastThe(
|
|
1086
|
+
numberWords(segment.fires, opts),
|
|
1087
|
+
unit,
|
|
1088
|
+
anchor,
|
|
1089
|
+
opts
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1093
|
+
}
|
|
1094
|
+
if (60 % interval === 0) {
|
|
1095
|
+
return "every " + getNumber(interval, opts) + " " + unit + "s";
|
|
1096
|
+
}
|
|
1097
|
+
if (segment.fires.length <= 2) {
|
|
1098
|
+
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1099
|
+
}
|
|
1100
|
+
return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
|
|
1101
|
+
}
|
|
1102
|
+
function stepHours(segment, opts) {
|
|
1103
|
+
if (segment.startToken.indexOf("-") !== -1) {
|
|
1104
|
+
return "at " + hourTimes(segment.fires, opts);
|
|
1105
|
+
}
|
|
1106
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
1107
|
+
const interval = segment.interval;
|
|
1108
|
+
if (start === 0 && 24 % interval === 0) {
|
|
1109
|
+
return "every " + getNumber(interval, opts) + " hours";
|
|
1110
|
+
}
|
|
1111
|
+
if (segment.fires.length <= 3) {
|
|
1112
|
+
return "at " + hourTimes(segment.fires, opts);
|
|
1113
|
+
}
|
|
1114
|
+
if (start === 0) {
|
|
1115
|
+
return "every " + getNumber(interval, opts) + " hours from midnight";
|
|
1116
|
+
}
|
|
1117
|
+
return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
|
|
1118
|
+
}
|
|
1119
|
+
function seriesNumber(values, opts) {
|
|
1120
|
+
const anyBig = values.some(function big(v) {
|
|
1121
|
+
return +v > 10;
|
|
1122
|
+
});
|
|
1123
|
+
return function format(n) {
|
|
1124
|
+
return anyBig ? "" + n : getNumber(n, opts);
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function numberWords(fires, opts) {
|
|
1128
|
+
return fires.map(seriesNumber(fires, opts));
|
|
1129
|
+
}
|
|
1130
|
+
function segmentWords(segments, opts) {
|
|
1131
|
+
const values = segments.flatMap(function collect(segment) {
|
|
1132
|
+
if (segment.kind === "range") {
|
|
1133
|
+
return segment.bounds;
|
|
1134
|
+
}
|
|
1135
|
+
return segment.kind === "step" ? segment.fires : [segment.value];
|
|
1136
|
+
});
|
|
1137
|
+
const num = seriesNumber(values, opts);
|
|
1138
|
+
return segments.flatMap(function word(segment) {
|
|
1139
|
+
if (segment.kind === "range") {
|
|
1140
|
+
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
1141
|
+
}
|
|
1142
|
+
if (segment.kind === "step") {
|
|
1143
|
+
return segment.fires.map(num);
|
|
1144
|
+
}
|
|
1145
|
+
return [num(segment.value)];
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
function listPastThe(words, unit, anchor, opts) {
|
|
1149
|
+
return "at " + joinList(words, opts) + " " + unit + "s past the " + anchor;
|
|
1150
|
+
}
|
|
1151
|
+
function wordTime(hour, minute, second) {
|
|
1152
|
+
return (+hour === 0 || +hour === 12) && +minute === 0 && !(typeof second === "number" && second > 0);
|
|
1153
|
+
}
|
|
1154
|
+
function mixedTwelve(entries) {
|
|
1155
|
+
const words = entries.filter(function word(e) {
|
|
1156
|
+
return wordTime(e.hour, e.minute, e.second);
|
|
1157
|
+
});
|
|
1158
|
+
return words.length > 0 && words.length < entries.length;
|
|
1159
|
+
}
|
|
1160
|
+
function hourTimes(hours, opts) {
|
|
1161
|
+
const plain = mixedTwelve(hours.map(function entry(hour) {
|
|
1162
|
+
return { hour, minute: 0 };
|
|
1163
|
+
}));
|
|
1164
|
+
const times = hours.map(function clock(hour) {
|
|
1165
|
+
return getTime({ hour, minute: 0, plain }, opts);
|
|
1166
|
+
});
|
|
1167
|
+
return joinList(times, opts);
|
|
1168
|
+
}
|
|
1169
|
+
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
1170
|
+
if (times.kind === "fires") {
|
|
1171
|
+
return hourTimes(times.fires, opts);
|
|
1172
|
+
}
|
|
1173
|
+
return hourSegmentTimes(ir, { minute: 0, second: null }, atContext, opts);
|
|
1174
|
+
}
|
|
1175
|
+
function segmentHours(segment) {
|
|
1176
|
+
if (segment.kind === "range") {
|
|
1177
|
+
return segment.bounds;
|
|
1178
|
+
}
|
|
1179
|
+
return segment.kind === "step" ? segment.fires : [segment.value];
|
|
1180
|
+
}
|
|
1181
|
+
function hourSegmentTimes(ir, fold, atContext, opts) {
|
|
1182
|
+
const { minute, second } = fold;
|
|
1183
|
+
const segments = ir.analyses.segments.hour;
|
|
1184
|
+
const plain = mixedTwelve(segments.flatMap(function entries(segment) {
|
|
1185
|
+
return segmentHours(segment).map(function entry(hour) {
|
|
1186
|
+
return { hour: +hour, minute, second };
|
|
1187
|
+
});
|
|
1188
|
+
}));
|
|
1189
|
+
const pieces = [];
|
|
1190
|
+
segments.forEach(function clock(segment) {
|
|
1191
|
+
if (segment.kind === "step") {
|
|
1192
|
+
pieces.push(...segment.fires.map(function time(hour) {
|
|
1193
|
+
return getTime({ hour, minute, second, plain }, opts);
|
|
1194
|
+
}));
|
|
1195
|
+
} else if (segment.kind === "range") {
|
|
1196
|
+
pieces.push(
|
|
1197
|
+
getTime({ hour: segment.bounds[0], minute, second, plain }, opts) + through(opts) + getTime({ hour: segment.bounds[1], minute, second, plain }, opts)
|
|
1198
|
+
);
|
|
1199
|
+
} else {
|
|
1200
|
+
pieces.push(getTime({ hour: segment.value, minute, second, plain }, opts));
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
return joinList(disambiguateTimes(pieces, segments, atContext), opts);
|
|
1204
|
+
}
|
|
1205
|
+
function disambiguateTimes(pieces, segments, atContext) {
|
|
1206
|
+
const hasRange = segments.some(function range(segment) {
|
|
1207
|
+
return segment.kind === "range";
|
|
1208
|
+
});
|
|
1209
|
+
if (!atContext || !hasRange) {
|
|
1210
|
+
return pieces;
|
|
1211
|
+
}
|
|
1212
|
+
return pieces.map(function at(piece, index) {
|
|
1213
|
+
return index === 0 ? piece : "at " + piece;
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
function joinList(items, opts) {
|
|
1217
|
+
if (items.length <= 1) {
|
|
1218
|
+
return items.join("");
|
|
1219
|
+
}
|
|
1220
|
+
if (items.length === 2) {
|
|
1221
|
+
return items[0] + " and " + items[1];
|
|
1222
|
+
}
|
|
1223
|
+
const and = opts.style.serialComma ? ", and " : " and ";
|
|
1224
|
+
return items.slice(0, -1).join(", ") + and + items[items.length - 1];
|
|
1225
|
+
}
|
|
1226
|
+
var trailingWords = { all: "", month: "in ", stepDate: "on ", weekday: "on " };
|
|
1227
|
+
var leadingWords = {
|
|
1228
|
+
all: "every day",
|
|
1229
|
+
month: "every day in ",
|
|
1230
|
+
stepDate: "",
|
|
1231
|
+
weekday: "every "
|
|
1232
|
+
};
|
|
1233
|
+
function trailingQualifier(ir, opts) {
|
|
1234
|
+
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
1235
|
+
return phrase && " " + phrase;
|
|
1236
|
+
}
|
|
1237
|
+
function interpretDayQualifier(ir, opts) {
|
|
1238
|
+
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
1239
|
+
}
|
|
1240
|
+
function dayQualifier(ir, words, opts) {
|
|
1241
|
+
const pattern = ir.pattern;
|
|
1242
|
+
if (pattern.date !== "*" && pattern.weekday !== "*") {
|
|
1243
|
+
return dateOrWeekday(ir, opts);
|
|
1244
|
+
}
|
|
1245
|
+
if (pattern.date !== "*") {
|
|
1246
|
+
return datePhrase(ir, words, opts);
|
|
1247
|
+
}
|
|
1248
|
+
if (pattern.weekday !== "*") {
|
|
1249
|
+
const weekdays = quartzWeekdayPhrase(pattern.weekday, opts) || words.weekday + weekdayPhrase(ir, opts);
|
|
1250
|
+
return weekdays + monthScope(ir, opts);
|
|
1251
|
+
}
|
|
1252
|
+
if (pattern.month !== "*") {
|
|
1253
|
+
return words.month + monthName(ir, opts);
|
|
1254
|
+
}
|
|
1255
|
+
return words.all;
|
|
1256
|
+
}
|
|
1257
|
+
function datePhrase(ir, words, opts) {
|
|
1258
|
+
const pattern = ir.pattern;
|
|
1259
|
+
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1260
|
+
if (quartzDate) {
|
|
1261
|
+
return quartzDate + monthScope(ir, opts);
|
|
1262
|
+
}
|
|
1263
|
+
if (isOpenStep(pattern.date)) {
|
|
1264
|
+
return words.stepDate + stepDates(pattern.date) + monthScope(ir, opts);
|
|
1265
|
+
}
|
|
1266
|
+
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
1267
|
+
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
1268
|
+
}
|
|
1269
|
+
if (pattern.month !== "*") {
|
|
1270
|
+
return "on " + monthDatePhrase(ir, opts);
|
|
1271
|
+
}
|
|
1272
|
+
return "on the " + dateOrdinals(ir, opts);
|
|
1273
|
+
}
|
|
1274
|
+
function monthFoldsIntoDate(ir) {
|
|
1275
|
+
return !oddEvenMonth(ir.pattern.month) && // Reached only with a restricted month, which has segments.
|
|
1276
|
+
ir.analyses.segments.month.every(function flat(segment) {
|
|
1277
|
+
return segment.kind !== "range";
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
function dateOrWeekday(ir, opts) {
|
|
1281
|
+
const pattern = ir.pattern;
|
|
1282
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1283
|
+
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
1284
|
+
if (quartzDate) {
|
|
1285
|
+
return quartzDate + monthScope(ir, opts) + " or " + weekdayPart;
|
|
1286
|
+
}
|
|
1287
|
+
if (isOpenStep(pattern.date)) {
|
|
1288
|
+
return stepDates(pattern.date) + monthScope(ir, opts) + " or " + weekdayPart;
|
|
1289
|
+
}
|
|
1290
|
+
if (pattern.month !== "*" && monthFoldsIntoDate(ir)) {
|
|
1291
|
+
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1292
|
+
}
|
|
1293
|
+
return "on the " + dateOrdinals(ir, opts) + " or " + weekdayPart + monthScope(ir, opts);
|
|
1294
|
+
}
|
|
1295
|
+
function quartzDatePhrase(dateField, opts) {
|
|
1296
|
+
if (dateField === "L") {
|
|
1297
|
+
return "on the last day of the month";
|
|
1298
|
+
}
|
|
1299
|
+
if (dateField === "LW" || dateField === "WL") {
|
|
1300
|
+
return "on the last weekday of the month";
|
|
1301
|
+
}
|
|
1302
|
+
const offset = /^L-(\d{1,2})$/.exec(dateField);
|
|
1303
|
+
if (offset) {
|
|
1304
|
+
return getNumber(+offset[1], opts) + " " + pluralize(offset[1], "day") + " before the last day of the month";
|
|
1305
|
+
}
|
|
1306
|
+
const nearest = /^(\d{1,2})W$|^W(\d{1,2})$/.exec(dateField);
|
|
1307
|
+
if (nearest) {
|
|
1308
|
+
return "on the weekday nearest the " + getOrdinal(nearest[1] || nearest[2]);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function quartzWeekdayPhrase(weekdayField, opts) {
|
|
1312
|
+
const parts = weekdayField.split("#");
|
|
1313
|
+
if (parts.length === 2) {
|
|
1314
|
+
return "on the " + nthWeekdayNames[+parts[1]] + " " + getWeekday(parts[0], opts) + " of the month";
|
|
1315
|
+
}
|
|
1316
|
+
if (/L$/.test(weekdayField)) {
|
|
1317
|
+
return "on the last " + getWeekday(weekdayField.slice(0, -1), opts) + " of the month";
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function monthDatePhrase(ir, opts) {
|
|
1321
|
+
const month = monthName(ir, opts);
|
|
1322
|
+
const days = renderSegments(
|
|
1323
|
+
ir.analyses.segments.date,
|
|
1324
|
+
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
1325
|
+
opts
|
|
1326
|
+
);
|
|
1327
|
+
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
1328
|
+
}
|
|
1329
|
+
function cardinalDay(value) {
|
|
1330
|
+
return "" + value;
|
|
1331
|
+
}
|
|
1332
|
+
function monthScope(ir, opts) {
|
|
1333
|
+
if (ir.pattern.month === "*") {
|
|
1334
|
+
return "";
|
|
1335
|
+
}
|
|
1336
|
+
return " in " + monthName(ir, opts);
|
|
1337
|
+
}
|
|
1338
|
+
function stepDates(dateField) {
|
|
1339
|
+
const parts = dateField.split("/");
|
|
1340
|
+
const interval = +parts[1];
|
|
1341
|
+
const start = parts[0];
|
|
1342
|
+
const cadence = interval === 2 ? "every other" : "every " + getOrdinal(interval);
|
|
1343
|
+
let phrase = cadence + " day of the month";
|
|
1344
|
+
if (start !== "*" && start !== "1") {
|
|
1345
|
+
phrase += " from the " + getOrdinal(start);
|
|
1346
|
+
}
|
|
1347
|
+
return phrase;
|
|
1348
|
+
}
|
|
1349
|
+
function dateOrdinals(ir, opts) {
|
|
1350
|
+
return renderSegments(ir.analyses.segments.date, getOrdinal, opts);
|
|
1351
|
+
}
|
|
1352
|
+
function monthName(ir, opts) {
|
|
1353
|
+
const oddEven = oddEvenMonth(ir.pattern.month);
|
|
1354
|
+
if (oddEven) {
|
|
1355
|
+
return oddEven;
|
|
1356
|
+
}
|
|
1357
|
+
return renderSegments(ir.analyses.segments.month, function name(value) {
|
|
1358
|
+
return getMonth(value, opts);
|
|
1359
|
+
}, opts);
|
|
1360
|
+
}
|
|
1361
|
+
function oddEvenMonth(monthField) {
|
|
1362
|
+
if (!isOpenStep(monthField)) {
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
const [start, step] = monthField.split("/");
|
|
1366
|
+
if (+step !== 2) {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
if (start === "*" || start === "1") {
|
|
1370
|
+
return "every odd-numbered month";
|
|
1371
|
+
}
|
|
1372
|
+
return start === "2" ? "every even-numbered month" : null;
|
|
1373
|
+
}
|
|
1374
|
+
function weekdayPhrase(ir, opts) {
|
|
1375
|
+
return renderSegments(ir.analyses.segments.weekday, function name(value) {
|
|
1376
|
+
return getWeekday(value, opts);
|
|
1377
|
+
}, opts);
|
|
1378
|
+
}
|
|
1379
|
+
function renderSegments(segments, word, opts) {
|
|
1380
|
+
const pieces = [];
|
|
1381
|
+
segments.forEach(function expand(segment) {
|
|
1382
|
+
if (segment.kind === "step") {
|
|
1383
|
+
pieces.push(...segment.fires.map(word));
|
|
1384
|
+
} else if (segment.kind === "range") {
|
|
1385
|
+
pieces.push(segment.bounds.map(word).join(through(opts)));
|
|
1386
|
+
} else {
|
|
1387
|
+
pieces.push(word(segment.value));
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
return joinList(pieces, opts);
|
|
1391
|
+
}
|
|
1392
|
+
function isOpenStep(field) {
|
|
1393
|
+
return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
|
|
1394
|
+
}
|
|
1395
|
+
function applyYear(description, ir, opts) {
|
|
1396
|
+
const yearField = ir.pattern.year;
|
|
1397
|
+
if (yearField === "*") {
|
|
1398
|
+
return description;
|
|
1399
|
+
}
|
|
1400
|
+
if (yearField.indexOf("/") !== -1) {
|
|
1401
|
+
return description + " " + stepYears(yearField, opts);
|
|
1402
|
+
}
|
|
1403
|
+
const label = yearLabel(yearField, opts);
|
|
1404
|
+
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
1405
|
+
const yearGlue = opts.style.dayFirst ? " " : ", ";
|
|
1406
|
+
return description.replace(" at ", yearGlue + label + " at ");
|
|
1407
|
+
}
|
|
1408
|
+
return description + " in " + label;
|
|
1409
|
+
}
|
|
1410
|
+
function yearLabel(yearField, opts) {
|
|
1411
|
+
if (yearField.indexOf(",") !== -1) {
|
|
1412
|
+
return joinList(yearField.split(","), opts);
|
|
1413
|
+
}
|
|
1414
|
+
return yearField;
|
|
1415
|
+
}
|
|
1416
|
+
function stepYears(yearField, opts) {
|
|
1417
|
+
const parts = yearField.split("/");
|
|
1418
|
+
const interval = +parts[1];
|
|
1419
|
+
const start = parts[0];
|
|
1420
|
+
if (interval <= 1) {
|
|
1421
|
+
return "every year";
|
|
1422
|
+
}
|
|
1423
|
+
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
1424
|
+
if (start !== "*" && start !== "0") {
|
|
1425
|
+
phrase += " from " + start;
|
|
1426
|
+
}
|
|
1427
|
+
return phrase;
|
|
1428
|
+
}
|
|
1429
|
+
function getTime(time, opts) {
|
|
1430
|
+
const { hour, minute, plain } = time;
|
|
1431
|
+
const second = typeof time.second === "number" && time.second > 0 ? time.second : 0;
|
|
1432
|
+
if (!opts.ampm) {
|
|
1433
|
+
return clockDigits({
|
|
1434
|
+
hour,
|
|
1435
|
+
minute,
|
|
1436
|
+
second
|
|
1437
|
+
}, { pad: true, sep: opts.style.sep });
|
|
1438
|
+
}
|
|
1439
|
+
return twelveHourTime({ hour, minute, second, plain }, opts);
|
|
1440
|
+
}
|
|
1441
|
+
function twelveHourTime(time, opts) {
|
|
1442
|
+
const { hour, minute, second, plain } = time;
|
|
1443
|
+
const style = opts.style;
|
|
1444
|
+
if (!plain && +minute === 0 && !second) {
|
|
1445
|
+
if (+hour === 0) {
|
|
1446
|
+
return style.midnight;
|
|
1447
|
+
}
|
|
1448
|
+
if (+hour === 12) {
|
|
1449
|
+
return style.midday;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const digits = clockDigits(
|
|
1453
|
+
{ hour: hour % 12 || 12, minute, second },
|
|
1454
|
+
{ lean: true, sep: style.sep }
|
|
1455
|
+
);
|
|
1456
|
+
return digits + (style.closeUp ? "" : " ") + (hour < 12 ? style.am : style.pm);
|
|
1457
|
+
}
|
|
1458
|
+
function getNumber(n, opts) {
|
|
1459
|
+
return numeral(n, numbers, opts);
|
|
1460
|
+
}
|
|
1461
|
+
function pluralize(value, unit) {
|
|
1462
|
+
return +value === 1 ? unit : unit + "s";
|
|
1463
|
+
}
|
|
1464
|
+
function through(opts) {
|
|
1465
|
+
return opts.short ? "-" : opts.style.through;
|
|
1466
|
+
}
|
|
1467
|
+
function getOrdinal(n) {
|
|
1468
|
+
let m = Math.abs(n);
|
|
1469
|
+
let suffix = suffixes[m];
|
|
1470
|
+
if (!suffix) {
|
|
1471
|
+
m = (m % 100 - 20) % 10;
|
|
1472
|
+
suffix = suffixes[m] || suffixes[0];
|
|
1473
|
+
}
|
|
1474
|
+
return n + suffix;
|
|
1475
|
+
}
|
|
1476
|
+
function getMonth(m, opts) {
|
|
1477
|
+
const month = monthNames[m] || monthAbbreviations[m];
|
|
1478
|
+
return month && month[opts.short ? 1 : 0];
|
|
1479
|
+
}
|
|
1480
|
+
function getWeekday(d, opts) {
|
|
1481
|
+
const day = d === 7 || d === "7" ? 0 : d;
|
|
1482
|
+
const weekday = weekdayNames[day] || weekdayAbbreviations[day];
|
|
1483
|
+
return weekday && weekday[opts.short ? 1 : 0];
|
|
1484
|
+
}
|
|
1485
|
+
var en = {
|
|
1486
|
+
describe,
|
|
1487
|
+
fallback: "an unrecognizable cron pattern",
|
|
1488
|
+
options: normalizeOptions,
|
|
1489
|
+
reboot: "at system startup",
|
|
1490
|
+
sentence: (description) => "Runs " + description + "."
|
|
1491
|
+
};
|
|
1492
|
+
var en_default = en;
|
|
1493
|
+
|
|
1494
|
+
// src/cronli5.ts
|
|
1495
|
+
function cronli5(cronPattern, options) {
|
|
1496
|
+
const lang = options && options.lang || en_default;
|
|
1497
|
+
const opts = lang.options(options);
|
|
1498
|
+
if (!opts.lenient) {
|
|
1499
|
+
return present(
|
|
1500
|
+
interpretCronPattern(cronPattern, lang, opts),
|
|
1501
|
+
lang,
|
|
1502
|
+
options
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
try {
|
|
1506
|
+
return present(
|
|
1507
|
+
interpretCronPattern(cronPattern, lang, opts),
|
|
1508
|
+
lang,
|
|
1509
|
+
options
|
|
1510
|
+
);
|
|
1511
|
+
} catch {
|
|
1512
|
+
return lang.fallback;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function present(description, lang, options) {
|
|
1516
|
+
return options && options.sentence ? lang.sentence(description) : description;
|
|
1517
|
+
}
|
|
1518
|
+
function interpretCronPattern(cronPattern, lang, opts) {
|
|
1519
|
+
if (typeof cronPattern === "string" && cronPattern.trim().toLowerCase() === "@reboot") {
|
|
1520
|
+
return lang.reboot;
|
|
1521
|
+
}
|
|
1522
|
+
const ir = analyze(prepare(cronPattern, opts));
|
|
1523
|
+
const plan = lang.strategy ? lang.strategy(ir, ir.plan) : ir.plan;
|
|
1524
|
+
return lang.describe({ ...ir, plan }, opts);
|
|
1525
|
+
}
|
|
1526
|
+
var cronli5_default = cronli5;
|
|
1527
|
+
export {
|
|
1528
|
+
cronli5_default as default
|
|
1529
|
+
};
|
|
1530
|
+
/**
|
|
1531
|
+
* @license MIT, Copyright (c) 2026 Andrew Brož
|
|
1532
|
+
*/
|