cronli5 0.1.4 → 0.1.5
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 +25 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +180 -36
- package/dist/cronli5.js +180 -36
- package/dist/lang/de.cjs +172 -8
- package/dist/lang/de.js +172 -8
- package/dist/lang/en.cjs +175 -29
- package/dist/lang/en.js +175 -29
- package/dist/lang/es.cjs +180 -25
- package/dist/lang/es.js +180 -25
- package/dist/lang/fi.cjs +188 -40
- package/dist/lang/fi.js +188 -40
- package/dist/lang/zh.cjs +165 -19
- package/dist/lang/zh.js +165 -19
- package/package.json +2 -1
- package/src/core/analyze.ts +7 -0
- package/src/core/ir.ts +1 -1
- package/src/core/util.ts +31 -1
- package/src/lang/de/index.ts +360 -16
- package/src/lang/en/index.ts +333 -33
- package/src/lang/es/index.ts +373 -40
- package/src/lang/fi/index.ts +404 -72
- package/src/lang/zh/index.ts +327 -35
- package/types/core/ir.d.ts +1 -1
- package/types/core/util.d.ts +6 -1
package/dist/lang/zh.js
CHANGED
|
@@ -3,6 +3,21 @@ function isNonNegativeInteger(value) {
|
|
|
3
3
|
const digits = /^\d+$/;
|
|
4
4
|
return digits.test(value);
|
|
5
5
|
}
|
|
6
|
+
function arithmeticStep(values) {
|
|
7
|
+
if (values.length < 5) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const interval = values[1] - values[0];
|
|
11
|
+
if (interval < 2) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
15
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { start: values[0], interval, last: values[values.length - 1] };
|
|
20
|
+
}
|
|
6
21
|
function toFieldNumber(token, numberMap) {
|
|
7
22
|
return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
|
|
8
23
|
}
|
|
@@ -31,6 +46,7 @@ var monthNumbers = {
|
|
|
31
46
|
NOV: 11,
|
|
32
47
|
DEC: 12
|
|
33
48
|
};
|
|
49
|
+
var maxClockTimes = 6;
|
|
34
50
|
|
|
35
51
|
// src/lang/zh/dialects.ts
|
|
36
52
|
var zh = { variant: "Hans" };
|
|
@@ -61,6 +77,46 @@ function stepSegment(ir, field) {
|
|
|
61
77
|
function cadence(interval, unit) {
|
|
62
78
|
return interval === 1 ? "\u6BCF" + unit : "\u6BCF" + interval + unit;
|
|
63
79
|
}
|
|
80
|
+
function renderStride(stride) {
|
|
81
|
+
const { interval, start, last, cycle, unit, mark, anchor } = stride;
|
|
82
|
+
const tiles = cycle % interval === 0;
|
|
83
|
+
if (start === 0 && tiles) {
|
|
84
|
+
return cadence(interval, unit);
|
|
85
|
+
}
|
|
86
|
+
const lead = anchor + "\u4ECE" + start + mark + "\u8D77" + cadence(interval, unit);
|
|
87
|
+
return start < interval && tiles ? lead : lead + "\uFF0C\u81F3" + last + mark;
|
|
88
|
+
}
|
|
89
|
+
function singleValues(segments) {
|
|
90
|
+
const values = [];
|
|
91
|
+
for (const segment of segments) {
|
|
92
|
+
if (segment.kind !== "single") {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
values.push(+segment.value);
|
|
96
|
+
}
|
|
97
|
+
return values;
|
|
98
|
+
}
|
|
99
|
+
function strideFromSegments(segments, unit, mark, anchor) {
|
|
100
|
+
const values = singleValues(segments);
|
|
101
|
+
const step = values && arithmeticStep(values);
|
|
102
|
+
return step ? renderStride({ ...step, cycle: 60, unit, mark, anchor }) : null;
|
|
103
|
+
}
|
|
104
|
+
function stepClause(segment, unit, mark, anchor) {
|
|
105
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
106
|
+
const short = start !== 0 && segment.fires.length <= 3;
|
|
107
|
+
if (segment.startToken.indexOf("-") !== -1 || short) {
|
|
108
|
+
return anchor + segment.fires.join("\u3001") + mark;
|
|
109
|
+
}
|
|
110
|
+
return renderStride({
|
|
111
|
+
interval: segment.interval,
|
|
112
|
+
start,
|
|
113
|
+
last: segment.fires[segment.fires.length - 1],
|
|
114
|
+
cycle: 60,
|
|
115
|
+
unit,
|
|
116
|
+
mark,
|
|
117
|
+
anchor
|
|
118
|
+
});
|
|
119
|
+
}
|
|
64
120
|
function dayPeriod(hour) {
|
|
65
121
|
if (hour < 6) {
|
|
66
122
|
return "\u51CC\u6668";
|
|
@@ -157,8 +213,15 @@ function renderEveryMinute() {
|
|
|
157
213
|
function renderEveryHour() {
|
|
158
214
|
return "\u6BCF\u5C0F\u65F6";
|
|
159
215
|
}
|
|
216
|
+
function minuteHourClause(ir) {
|
|
217
|
+
const segments = fieldSegments(ir, "minute");
|
|
218
|
+
if (ir.shapes.minute === "step") {
|
|
219
|
+
return stepClause(stepSegment(ir, "minute"), "\u5206\u949F", "\u5206", "\u6BCF\u5C0F\u65F6");
|
|
220
|
+
}
|
|
221
|
+
return strideFromSegments(segments, "\u5206\u949F", "\u5206", "\u6BCF\u5C0F\u65F6") ?? "\u6BCF\u5C0F\u65F6" + valueList(segments, "\u5206");
|
|
222
|
+
}
|
|
160
223
|
function renderMinutePast(ir) {
|
|
161
|
-
return
|
|
224
|
+
return minuteHourClause(ir);
|
|
162
225
|
}
|
|
163
226
|
function hourList(ir) {
|
|
164
227
|
return joinAnd(hourFires(ir).map(hourWord));
|
|
@@ -172,7 +235,7 @@ function hourFrame(ir) {
|
|
|
172
235
|
}
|
|
173
236
|
function renderMinuteFrequency(ir, plan) {
|
|
174
237
|
const minuteStep = stepSegment(ir, "minute");
|
|
175
|
-
const base =
|
|
238
|
+
const base = stepClause(minuteStep, UNITS.minute, "\u5206", "\u6BCF\u5C0F\u65F6");
|
|
176
239
|
const { hours } = plan;
|
|
177
240
|
if (hours.kind === "step") {
|
|
178
241
|
const hourStep = stepSegment(ir, "hour");
|
|
@@ -201,12 +264,15 @@ function renderMinutesAcrossHours(ir, plan) {
|
|
|
201
264
|
if (form === "wildcard") {
|
|
202
265
|
return "\u5728" + hourList(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
203
266
|
}
|
|
204
|
-
return hourList(ir) + "\uFF0C
|
|
267
|
+
return hourList(ir) + "\uFF0C" + minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
205
268
|
}
|
|
206
269
|
function renderMinuteSpanAcrossHourStep(ir, plan) {
|
|
207
270
|
const hourStep = stepSegment(ir, "hour");
|
|
208
271
|
const { form } = plan;
|
|
209
|
-
|
|
272
|
+
if (form === "list") {
|
|
273
|
+
return renderMinutePast(ir) + "\uFF0C\u5728" + hourList(ir);
|
|
274
|
+
}
|
|
275
|
+
const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
210
276
|
if (hourStep.startToken !== "*") {
|
|
211
277
|
return form === "wildcard" ? "\u5728" + hourList(ir) + "\uFF0C" + minuteTail : hourList(ir) + "\uFF0C" + minuteTail;
|
|
212
278
|
}
|
|
@@ -217,15 +283,27 @@ function renderMinuteSpanAcrossHourStep(ir, plan) {
|
|
|
217
283
|
return form === "wildcard" ? cad + "\u5185\uFF0C" + minuteTail : cad + "\uFF0C" + minuteTail;
|
|
218
284
|
}
|
|
219
285
|
function renderClockTimes(ir, plan, opts) {
|
|
286
|
+
if (ir.shapes.minute === "single") {
|
|
287
|
+
const cad = hourCadence(ir);
|
|
288
|
+
if (cad !== null) {
|
|
289
|
+
return cad;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
220
292
|
const { times } = plan;
|
|
221
293
|
return joinAnd(times.map((t) => clockTime(t.hour, t.minute, t.second, opts)));
|
|
222
294
|
}
|
|
223
295
|
function renderCompactClockTimes(ir, plan) {
|
|
296
|
+
if (ir.shapes.minute === "single") {
|
|
297
|
+
const cad = hourCadence(ir);
|
|
298
|
+
if (cad !== null) {
|
|
299
|
+
return cad;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
224
302
|
const { minute } = plan;
|
|
225
303
|
const secs = fieldSegments(ir, "second");
|
|
226
304
|
const tail = secs.length && ir.pattern.second !== "0" ? "\uFF0C\u7B2C" + valueText(secs) + "\u79D2" : "";
|
|
227
305
|
if (minute > 0) {
|
|
228
|
-
return
|
|
306
|
+
return minuteHourClause(ir) + "\uFF0C\u5728" + hourList(ir) + tail;
|
|
229
307
|
}
|
|
230
308
|
return hourList(ir) + tail;
|
|
231
309
|
}
|
|
@@ -233,7 +311,7 @@ function renderHourRange(ir, plan) {
|
|
|
233
311
|
const range = plan;
|
|
234
312
|
if (range.minuteForm === "lead") {
|
|
235
313
|
const minuteSegs = fieldSegments(ir, "minute");
|
|
236
|
-
const past = minuteSegs.length && ir.pattern.minute !== "0" ?
|
|
314
|
+
const past = minuteSegs.length && ir.pattern.minute !== "0" ? minuteHourClause(ir) : "\u6BCF\u5C0F\u65F6";
|
|
237
315
|
return "\u5728" + hourWord(range.from) + "\u81F3" + hourWord(range.to) + "\u4E4B\u95F4\uFF0C" + past;
|
|
238
316
|
}
|
|
239
317
|
if (range.minuteForm === "range") {
|
|
@@ -251,8 +329,52 @@ function renderHourStep(ir) {
|
|
|
251
329
|
}
|
|
252
330
|
return cadence(segment.interval, UNITS.hour);
|
|
253
331
|
}
|
|
332
|
+
function hourStride(ir) {
|
|
333
|
+
const segments = fieldSegments(ir, "hour");
|
|
334
|
+
if (segments.length === 1 && segments[0].kind === "step") {
|
|
335
|
+
const segment = segments[0];
|
|
336
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
337
|
+
if (start !== 0 || 24 % segment.interval !== 0) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
interval: segment.interval,
|
|
342
|
+
last: segment.fires[segment.fires.length - 1]
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const values = singleValues(segments);
|
|
346
|
+
const step = values && arithmeticStep(values);
|
|
347
|
+
if (!step || step.start !== 0 || 24 % step.interval !== 0) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
return { interval: step.interval, last: step.last };
|
|
351
|
+
}
|
|
352
|
+
function hourCadence(ir) {
|
|
353
|
+
const stride = hourStride(ir);
|
|
354
|
+
if (!stride) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const fires = stride.last / stride.interval + 1;
|
|
358
|
+
if (ir.pattern.second === "0" && fires <= maxClockTimes) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const prefix = cadence(stride.interval, UNITS.hour);
|
|
362
|
+
const minute = +ir.pattern.minute;
|
|
363
|
+
const subMinute = ir.pattern.second === "*" || ir.shapes.second === "step";
|
|
364
|
+
if (minute === 0 && subMinute) {
|
|
365
|
+
return stride.interval === 2 ? "\u5728\u5076\u6570\u5C0F\u65F60\u5206" + secondTail(ir) : null;
|
|
366
|
+
}
|
|
367
|
+
if (minute === 0) {
|
|
368
|
+
return prefix + "0\u5206" + secondTail(ir);
|
|
369
|
+
}
|
|
370
|
+
return ir.pattern.second === "0" ? prefix + minute + "\u5206" : prefix + minute + "\u5206" + secondTail(ir);
|
|
371
|
+
}
|
|
372
|
+
function secondTail(ir) {
|
|
373
|
+
const sec = secondClause(ir);
|
|
374
|
+
return sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
|
|
375
|
+
}
|
|
254
376
|
function renderRangeOfMinutes(ir) {
|
|
255
|
-
return
|
|
377
|
+
return minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
256
378
|
}
|
|
257
379
|
function renderStandaloneSeconds(ir) {
|
|
258
380
|
const segs = fieldSegments(ir, "second");
|
|
@@ -260,7 +382,7 @@ function renderStandaloneSeconds(ir) {
|
|
|
260
382
|
if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
|
|
261
383
|
return cadence(first.interval, UNITS.second);
|
|
262
384
|
}
|
|
263
|
-
return "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
|
|
385
|
+
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
|
|
264
386
|
}
|
|
265
387
|
function renderSecondPastMinute(ir) {
|
|
266
388
|
return "\u6BCF\u5206\u949F\u7B2C" + valueText(fieldSegments(ir, "second")) + "\u79D2";
|
|
@@ -283,7 +405,7 @@ function secondClause(ir) {
|
|
|
283
405
|
if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
|
|
284
406
|
return cadence(first.interval, UNITS.second);
|
|
285
407
|
}
|
|
286
|
-
return "\u7B2C" + valueText(segs) + "\u79D2";
|
|
408
|
+
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u7B2C" + valueText(segs) + "\u79D2";
|
|
287
409
|
}
|
|
288
410
|
function minuteClause(ir) {
|
|
289
411
|
if (ir.pattern.minute === "*") {
|
|
@@ -300,13 +422,15 @@ function isHourCadence(ir) {
|
|
|
300
422
|
function composeSecondsOnHour(ir, plan, opts) {
|
|
301
423
|
const sec = secondClause(ir);
|
|
302
424
|
const { rest } = plan;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
425
|
+
const composedClock = rest.kind === "clockTimes" || rest.kind === "compactClockTimes";
|
|
426
|
+
if (composedClock) {
|
|
427
|
+
const hourCad = hourCadence(ir);
|
|
428
|
+
if (hourCad !== null) {
|
|
429
|
+
return hourCad;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (composedClock && ir.pattern.minute === "0") {
|
|
433
|
+
return composeMinuteZeroClocks(ir, sec);
|
|
310
434
|
}
|
|
311
435
|
const restText = render(ir, rest, opts);
|
|
312
436
|
if (rest.kind === "clockTimes" || rest.kind === "compactClockTimes") {
|
|
@@ -319,6 +443,15 @@ function composeSecondsOnHour(ir, plan, opts) {
|
|
|
319
443
|
}
|
|
320
444
|
return restText + sec;
|
|
321
445
|
}
|
|
446
|
+
function composeMinuteZeroClocks(ir, sec) {
|
|
447
|
+
const clocks = hourFires(ir).map(function clock(hour) {
|
|
448
|
+
return hour === 12 ? "\u6B63\u5348" : hourWord(hour) + "0\u5206";
|
|
449
|
+
});
|
|
450
|
+
const nested = strideFromSegments(fieldSegments(ir, "second"), "\u79D2", "\u79D2", "");
|
|
451
|
+
const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + (nested ?? sec);
|
|
452
|
+
const core = joinAnd(clocks) + tail;
|
|
453
|
+
return isDaily(ir) ? "\u6BCF\u5929" + core : core;
|
|
454
|
+
}
|
|
322
455
|
function composeSecondsCadence(ir) {
|
|
323
456
|
const sec = secondClause(ir);
|
|
324
457
|
const tail = minuteClause(ir) + sec;
|
|
@@ -329,15 +462,23 @@ function composeSecondsCadence(ir) {
|
|
|
329
462
|
return hourWord(hourFires(ir)[0]) + "\u7684" + tail;
|
|
330
463
|
}
|
|
331
464
|
if (ir.shapes.hour === "wildcard") {
|
|
465
|
+
if (ir.shapes.minute === "step" && sec === "\u6BCF\u79D2") {
|
|
466
|
+
const minuteStep = stepSegment(ir, "minute");
|
|
467
|
+
if (minuteStep.startToken === "*" && minuteStep.interval === 2) {
|
|
468
|
+
return "\u6BCF\u5076\u6570\u5206\u949F\u7684\u6BCF\u4E00\u79D2";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
332
471
|
return sec + "\uFF0C" + minuteClause(ir);
|
|
333
472
|
}
|
|
334
473
|
return hourFrame(ir) + tail;
|
|
335
474
|
}
|
|
336
475
|
function composeSecondsListed(ir) {
|
|
337
476
|
const sec = secondClause(ir);
|
|
338
|
-
const minutes =
|
|
477
|
+
const minutes = minuteHourClause(ir);
|
|
339
478
|
if (ir.shapes.hour === "single" && sec === "\u6BCF\u79D2") {
|
|
340
|
-
|
|
479
|
+
const minuteSegs = fieldSegments(ir, "minute");
|
|
480
|
+
const minuteCad = strideFromSegments(minuteSegs, "\u5206\u949F", "\u5206", "") ?? valueList(minuteSegs, "\u5206");
|
|
481
|
+
return hourWord(hourFires(ir)[0]) + minuteCad + "\u7684\u6BCF\u4E00\u79D2";
|
|
341
482
|
}
|
|
342
483
|
if (ir.shapes.hour === "wildcard") {
|
|
343
484
|
return minutes + "\uFF0C" + sec;
|
|
@@ -554,11 +695,16 @@ function composeCadence(ir, core) {
|
|
|
554
695
|
function composeWindow(ir, core) {
|
|
555
696
|
return qualifier(ir) + core;
|
|
556
697
|
}
|
|
698
|
+
function hourCadenceApplies(ir) {
|
|
699
|
+
return ir.shapes.minute === "single" && hourCadence(ir) !== null;
|
|
700
|
+
}
|
|
557
701
|
function describe(ir, opts) {
|
|
558
702
|
const { kind } = ir.plan;
|
|
559
703
|
const core = render(ir, ir.plan, opts);
|
|
560
704
|
let composed = core;
|
|
561
|
-
if (
|
|
705
|
+
if (hourCadenceApplies(ir)) {
|
|
706
|
+
composed = composeCadence(ir, core);
|
|
707
|
+
} else if (kind === "clockTimes" || kind === "compactClockTimes" && ir.pattern.minute === "0") {
|
|
562
708
|
composed = composePoint(ir, core);
|
|
563
709
|
} else if (kind === "hourStep" || kind === "minuteFrequency" || kind === "minuteSpanAcrossHourStep" || kind === "compactClockTimes") {
|
|
564
710
|
composed = composeCadence(ir, core);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cronli5",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Cron Like I'm Five: A Cron to English Utility",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "node scripts/build.mjs && npm run types",
|
|
62
62
|
"docs": "node --import tsx scripts/docs.mjs",
|
|
63
|
+
"conciseness": "node --import tsx tooling/scripts/conciseness.mjs",
|
|
63
64
|
"fuzz": "node --import tsx scripts/fuzz-lang.mjs",
|
|
64
65
|
"lint": "eslint src test cli.js eslint.config.js scripts tooling/scripts",
|
|
65
66
|
"metamorphic": "node --import tsx tooling/scripts/metamorphic.mjs",
|
package/src/core/analyze.ts
CHANGED
|
@@ -376,6 +376,13 @@ function planMinuteUnderHourStep(
|
|
|
376
376
|
return {form: 'range', kind: 'minuteSpanAcrossHourStep'};
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
// A minute list under a clean stride keeps the cadence too, so the hour
|
|
380
|
+
// reads the same whatever the minute's shape. An unclean stride falls
|
|
381
|
+
// through to compactClockTimes and enumerates its hours.
|
|
382
|
+
if (shapes.minute === 'list' && cleanHourStride(pattern.hour)) {
|
|
383
|
+
return {form: 'list', kind: 'minuteSpanAcrossHourStep'};
|
|
384
|
+
}
|
|
385
|
+
|
|
379
386
|
return null;
|
|
380
387
|
}
|
|
381
388
|
|
package/src/core/ir.ts
CHANGED
|
@@ -71,7 +71,7 @@ export type PlanNode =
|
|
|
71
71
|
form: 'wildcard' | 'range' | 'list';
|
|
72
72
|
times: HourTimesPlan;
|
|
73
73
|
}
|
|
74
|
-
| {kind: 'minuteSpanAcrossHourStep'; form: 'wildcard' | 'range'}
|
|
74
|
+
| {kind: 'minuteSpanAcrossHourStep'; form: 'wildcard' | 'range' | 'list'}
|
|
75
75
|
| {kind: 'everyHour'}
|
|
76
76
|
| {
|
|
77
77
|
kind: 'hourRange';
|
package/src/core/util.ts
CHANGED
|
@@ -16,6 +16,34 @@ function isNonNegativeInteger(value: string): boolean {
|
|
|
16
16
|
return digits.test(value);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Recognize an arithmetic progression in a sorted, distinct numeric set: a
|
|
20
|
+
// run of length >= 5 whose consecutive gaps are all equal and >= 2. Returns
|
|
21
|
+
// its {start, interval, last}; null for anything shorter, with a gap of one
|
|
22
|
+
// (a plain run, which reads as a range), or irregular. Output-neutral and
|
|
23
|
+
// language-agnostic: renderers use it to speak a bounded/offset step cadence
|
|
24
|
+
// ("every N from M [through K]") instead of enumerating the fires. The set is
|
|
25
|
+
// the field's full value list, which the core has already sorted and deduped.
|
|
26
|
+
function arithmeticStep(values: number[]):
|
|
27
|
+
{start: number; interval: number; last: number} | null {
|
|
28
|
+
if (values.length < 5) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const interval = values[1] - values[0];
|
|
33
|
+
|
|
34
|
+
if (interval < 2) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
39
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {start: values[0], interval, last: values[values.length - 1]};
|
|
45
|
+
}
|
|
46
|
+
|
|
19
47
|
// Resolve a numeric or named field token (e.g. '5' or 'FRI') to its number.
|
|
20
48
|
function toFieldNumber(
|
|
21
49
|
token: string,
|
|
@@ -25,4 +53,6 @@ function toFieldNumber(
|
|
|
25
53
|
// weekday) reach here. They always have an associated `numberMap`.
|
|
26
54
|
return isNonNegativeInteger(token) ? +token : numberMap![token.toUpperCase()];
|
|
27
55
|
}
|
|
28
|
-
export {
|
|
56
|
+
export {
|
|
57
|
+
arithmeticStep, includes, isNonNegativeInteger, toFieldNumber, unique
|
|
58
|
+
};
|