cronli5 0.1.4 → 0.1.6
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 +53 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +286 -45
- package/dist/cronli5.js +286 -45
- package/dist/lang/de.cjs +252 -13
- package/dist/lang/de.js +252 -13
- package/dist/lang/en.cjs +281 -38
- package/dist/lang/en.js +281 -38
- package/dist/lang/es.cjs +259 -29
- package/dist/lang/es.js +259 -29
- package/dist/lang/fi.cjs +285 -49
- package/dist/lang/fi.js +285 -49
- package/dist/lang/zh.cjs +225 -42
- package/dist/lang/zh.js +225 -42
- package/package.json +3 -2
- 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 +561 -30
- package/src/lang/en/index.ts +593 -59
- package/src/lang/es/index.ts +576 -52
- package/src/lang/fi/index.ts +633 -95
- package/src/lang/zh/index.ts +484 -77
- 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,11 +213,28 @@ 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);
|
|
225
|
+
}
|
|
226
|
+
function hourSegmentWords(segment) {
|
|
227
|
+
if (segment.kind === "range") {
|
|
228
|
+
return [hourWord(+segment.bounds[0]) + "\u81F3" + hourWord(+segment.bounds[1])];
|
|
229
|
+
}
|
|
230
|
+
if (segment.kind === "step") {
|
|
231
|
+
return segment.fires.map(hourWord);
|
|
232
|
+
}
|
|
233
|
+
return [hourWord(+segment.value)];
|
|
162
234
|
}
|
|
163
235
|
function hourList(ir) {
|
|
164
|
-
|
|
236
|
+
const words = fieldSegments(ir, "hour").flatMap(hourSegmentWords);
|
|
237
|
+
return joinAnd(words);
|
|
165
238
|
}
|
|
166
239
|
function hourFrame(ir) {
|
|
167
240
|
if (ir.shapes.hour === "range") {
|
|
@@ -172,11 +245,13 @@ function hourFrame(ir) {
|
|
|
172
245
|
}
|
|
173
246
|
function renderMinuteFrequency(ir, plan) {
|
|
174
247
|
const minuteStep = stepSegment(ir, "minute");
|
|
175
|
-
const base =
|
|
248
|
+
const base = stepClause(minuteStep, UNITS.minute, "\u5206", "\u6BCF\u5C0F\u65F6");
|
|
176
249
|
const { hours } = plan;
|
|
177
|
-
if (hours.kind === "step") {
|
|
178
|
-
const
|
|
179
|
-
|
|
250
|
+
if (hours.kind === "step" || hours.kind === "during") {
|
|
251
|
+
const hourCad = hourCadencePhrase(ir);
|
|
252
|
+
if (hourCad !== null) {
|
|
253
|
+
return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "" : "\uFF0C") + base;
|
|
254
|
+
}
|
|
180
255
|
}
|
|
181
256
|
if (hours.kind === "single" || hours.kind === "window" && hours.from === hours.to) {
|
|
182
257
|
return "\u5728" + hourWord(hours.from) + "\u81F3" + hours.from + "\u70B9" + hours.last + "\u5206\u4E4B\u95F4\uFF0C" + base;
|
|
@@ -198,17 +273,21 @@ function renderMinuteSpanInHour(ir, plan) {
|
|
|
198
273
|
}
|
|
199
274
|
function renderMinutesAcrossHours(ir, plan) {
|
|
200
275
|
const { form } = plan;
|
|
276
|
+
const hourCad = hourCadencePhrase(ir);
|
|
201
277
|
if (form === "wildcard") {
|
|
202
|
-
return "\u5728" + hourList(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
278
|
+
return hourCad === null ? "\u5728" + hourList(ir) + "\uFF0C\u6BCF\u5206\u949F" : hourCad + "\uFF0C\u6BCF\u5206\u949F";
|
|
203
279
|
}
|
|
204
|
-
return hourList(ir) + "\uFF0C
|
|
280
|
+
return (hourCad ?? hourList(ir)) + "\uFF0C" + minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
205
281
|
}
|
|
206
282
|
function renderMinuteSpanAcrossHourStep(ir, plan) {
|
|
207
283
|
const hourStep = stepSegment(ir, "hour");
|
|
208
284
|
const { form } = plan;
|
|
209
|
-
|
|
285
|
+
if (form === "list") {
|
|
286
|
+
return hourCadencePhrase(ir) + "\uFF0C" + renderMinutePast(ir);
|
|
287
|
+
}
|
|
288
|
+
const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
210
289
|
if (hourStep.startToken !== "*") {
|
|
211
|
-
return
|
|
290
|
+
return hourCadencePhrase(ir) + "\uFF0C" + minuteTail;
|
|
212
291
|
}
|
|
213
292
|
if (hourStep.interval === 2 && form === "wildcard") {
|
|
214
293
|
return "\u5728\u5076\u6570\u5C0F\u65F6\uFF0C" + minuteTail;
|
|
@@ -217,15 +296,27 @@ function renderMinuteSpanAcrossHourStep(ir, plan) {
|
|
|
217
296
|
return form === "wildcard" ? cad + "\u5185\uFF0C" + minuteTail : cad + "\uFF0C" + minuteTail;
|
|
218
297
|
}
|
|
219
298
|
function renderClockTimes(ir, plan, opts) {
|
|
299
|
+
const cad = hourCadenceText(ir);
|
|
300
|
+
if (cad !== null) {
|
|
301
|
+
return cad;
|
|
302
|
+
}
|
|
220
303
|
const { times } = plan;
|
|
221
304
|
return joinAnd(times.map((t) => clockTime(t.hour, t.minute, t.second, opts)));
|
|
222
305
|
}
|
|
223
306
|
function renderCompactClockTimes(ir, plan) {
|
|
224
|
-
const
|
|
307
|
+
const cad = hourCadenceText(ir);
|
|
308
|
+
if (cad !== null) {
|
|
309
|
+
return cad;
|
|
310
|
+
}
|
|
311
|
+
const compact = plan;
|
|
225
312
|
const secs = fieldSegments(ir, "second");
|
|
226
313
|
const tail = secs.length && ir.pattern.second !== "0" ? "\uFF0C\u7B2C" + valueText(secs) + "\u79D2" : "";
|
|
227
|
-
if (
|
|
228
|
-
|
|
314
|
+
if (!compact.fold) {
|
|
315
|
+
const hourCad = hourCadencePhrase(ir);
|
|
316
|
+
return hourCad === null ? minuteHourClause(ir) + "\uFF0C\u5728" + hourList(ir) + tail : hourCad + "\uFF0C" + minuteHourClause(ir) + tail;
|
|
317
|
+
}
|
|
318
|
+
if (compact.minute > 0) {
|
|
319
|
+
return minuteHourClause(ir) + "\uFF0C\u5728" + hourList(ir) + tail;
|
|
229
320
|
}
|
|
230
321
|
return hourList(ir) + tail;
|
|
231
322
|
}
|
|
@@ -233,7 +324,7 @@ function renderHourRange(ir, plan) {
|
|
|
233
324
|
const range = plan;
|
|
234
325
|
if (range.minuteForm === "lead") {
|
|
235
326
|
const minuteSegs = fieldSegments(ir, "minute");
|
|
236
|
-
const past = minuteSegs.length && ir.pattern.minute !== "0" ?
|
|
327
|
+
const past = minuteSegs.length && ir.pattern.minute !== "0" ? minuteHourClause(ir) : "\u6BCF\u5C0F\u65F6";
|
|
237
328
|
return "\u5728" + hourWord(range.from) + "\u81F3" + hourWord(range.to) + "\u4E4B\u95F4\uFF0C" + past;
|
|
238
329
|
}
|
|
239
330
|
if (range.minuteForm === "range") {
|
|
@@ -243,16 +334,68 @@ function renderHourRange(ir, plan) {
|
|
|
243
334
|
}
|
|
244
335
|
function renderHourStep(ir) {
|
|
245
336
|
const segment = stepSegment(ir, "hour");
|
|
246
|
-
if (segment.startToken !== "*") {
|
|
247
|
-
return hourList(ir);
|
|
248
|
-
}
|
|
249
337
|
if (segment.fires.length <= 2) {
|
|
250
338
|
return joinAnd(segment.fires.map(hourWord));
|
|
251
339
|
}
|
|
252
|
-
return
|
|
340
|
+
return hourCadencePhrase(ir);
|
|
341
|
+
}
|
|
342
|
+
function hourStride(ir) {
|
|
343
|
+
const segments = fieldSegments(ir, "hour");
|
|
344
|
+
if (segments.length === 1 && segments[0].kind === "step") {
|
|
345
|
+
const { fires, interval } = segments[0];
|
|
346
|
+
return { interval, start: fires[0], last: fires[fires.length - 1] };
|
|
347
|
+
}
|
|
348
|
+
const values = singleValues(segments);
|
|
349
|
+
return values && arithmeticStep(values);
|
|
350
|
+
}
|
|
351
|
+
function hourCadencePhrase(ir) {
|
|
352
|
+
const stride = hourStride(ir);
|
|
353
|
+
return stride && renderStride({
|
|
354
|
+
...stride,
|
|
355
|
+
cycle: 24,
|
|
356
|
+
unit: UNITS.hour,
|
|
357
|
+
mark: "\u70B9",
|
|
358
|
+
anchor: ""
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function hourCadence(ir) {
|
|
362
|
+
const stride = hourStride(ir);
|
|
363
|
+
if (!stride) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
367
|
+
if (ir.pattern.second === "0" && fires <= maxClockTimes) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const prefix = hourCadencePhrase(ir);
|
|
371
|
+
if (prefix.indexOf("\u81F3") !== -1) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const minute = +ir.pattern.minute;
|
|
375
|
+
const subMinute = ir.pattern.second === "*" || ir.shapes.second === "step";
|
|
376
|
+
if (minute === 0 && subMinute) {
|
|
377
|
+
return stride.interval === 2 && stride.start === 0 ? "\u5728\u5076\u6570\u5C0F\u65F60\u5206" + secondTail(ir) : null;
|
|
378
|
+
}
|
|
379
|
+
if (minute === 0) {
|
|
380
|
+
return prefix + "0\u5206" + secondTail(ir);
|
|
381
|
+
}
|
|
382
|
+
return ir.pattern.second === "0" ? prefix + minute + "\u5206" : prefix + minute + "\u5206" + secondTail(ir);
|
|
383
|
+
}
|
|
384
|
+
function hourCadenceText(ir) {
|
|
385
|
+
if (ir.shapes.minute !== "single") {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
if (+ir.pattern.minute === 0 && ir.pattern.second === "0") {
|
|
389
|
+
return hourCadencePhrase(ir);
|
|
390
|
+
}
|
|
391
|
+
return hourCadence(ir);
|
|
392
|
+
}
|
|
393
|
+
function secondTail(ir) {
|
|
394
|
+
const sec = secondClause(ir);
|
|
395
|
+
return sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
|
|
253
396
|
}
|
|
254
397
|
function renderRangeOfMinutes(ir) {
|
|
255
|
-
return
|
|
398
|
+
return minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
|
|
256
399
|
}
|
|
257
400
|
function renderStandaloneSeconds(ir) {
|
|
258
401
|
const segs = fieldSegments(ir, "second");
|
|
@@ -260,7 +403,7 @@ function renderStandaloneSeconds(ir) {
|
|
|
260
403
|
if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
|
|
261
404
|
return cadence(first.interval, UNITS.second);
|
|
262
405
|
}
|
|
263
|
-
return "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
|
|
406
|
+
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
|
|
264
407
|
}
|
|
265
408
|
function renderSecondPastMinute(ir) {
|
|
266
409
|
return "\u6BCF\u5206\u949F\u7B2C" + valueText(fieldSegments(ir, "second")) + "\u79D2";
|
|
@@ -283,7 +426,7 @@ function secondClause(ir) {
|
|
|
283
426
|
if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
|
|
284
427
|
return cadence(first.interval, UNITS.second);
|
|
285
428
|
}
|
|
286
|
-
return "\u7B2C" + valueText(segs) + "\u79D2";
|
|
429
|
+
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u7B2C" + valueText(segs) + "\u79D2";
|
|
287
430
|
}
|
|
288
431
|
function minuteClause(ir) {
|
|
289
432
|
if (ir.pattern.minute === "*") {
|
|
@@ -294,56 +437,91 @@ function minuteClause(ir) {
|
|
|
294
437
|
}
|
|
295
438
|
return valueList(fieldSegments(ir, "minute"), "\u5206");
|
|
296
439
|
}
|
|
297
|
-
function
|
|
298
|
-
return
|
|
440
|
+
function clockRestCarriesSecond(rest) {
|
|
441
|
+
return rest.kind === "clockTimes" && rest.times.some((time) => Boolean(time.second));
|
|
299
442
|
}
|
|
300
443
|
function composeSecondsOnHour(ir, plan, opts) {
|
|
301
444
|
const sec = secondClause(ir);
|
|
302
445
|
const { rest } = plan;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
446
|
+
const composedClock = rest.kind === "clockTimes" || rest.kind === "compactClockTimes";
|
|
447
|
+
if (composedClock) {
|
|
448
|
+
const hourCad = hourCadence(ir);
|
|
449
|
+
if (hourCad !== null) {
|
|
450
|
+
return hourCad;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (composedClock && ir.pattern.minute === "0") {
|
|
454
|
+
return composeMinuteZeroClocks(ir, sec);
|
|
310
455
|
}
|
|
311
456
|
const restText = render(ir, rest, opts);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
457
|
+
const secTail = clockRestCarriesSecond(rest) ? "" : sec;
|
|
458
|
+
if (composedClock && isDaily(ir)) {
|
|
459
|
+
return "\u6BCF\u5929" + restText + secTail;
|
|
316
460
|
}
|
|
317
461
|
if (rest.kind === "singleMinute") {
|
|
318
462
|
return restText + "\uFF0C" + sec;
|
|
319
463
|
}
|
|
320
|
-
return restText +
|
|
464
|
+
return restText + secTail;
|
|
465
|
+
}
|
|
466
|
+
function composeMinuteZeroClocks(ir, sec) {
|
|
467
|
+
if (hasHourWindow(ir)) {
|
|
468
|
+
return isDaily(ir) ? "\u6BCF\u5929" + hourRangeWindow(ir, sec) : hourRangeWindow(ir, sec);
|
|
469
|
+
}
|
|
470
|
+
const clocks = hourFires(ir).map(function clock(hour) {
|
|
471
|
+
return hour === 12 ? "\u6B63\u5348" : hourWord(hour) + "0\u5206";
|
|
472
|
+
});
|
|
473
|
+
const nested = strideFromSegments(fieldSegments(ir, "second"), "\u79D2", "\u79D2", "");
|
|
474
|
+
const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + (nested ?? sec);
|
|
475
|
+
const core = joinAnd(clocks) + tail;
|
|
476
|
+
return isDaily(ir) ? "\u6BCF\u5929" + core : core;
|
|
477
|
+
}
|
|
478
|
+
function hasHourWindow(ir) {
|
|
479
|
+
return fieldSegments(ir, "hour").some(function range(segment) {
|
|
480
|
+
return segment.kind === "range";
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function hourRangeWindow(ir, sec) {
|
|
484
|
+
const span = hourList(ir);
|
|
485
|
+
if (ir.pattern.second === "*" || ir.shapes.second === "step") {
|
|
486
|
+
return span + "0\u5206" + (sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec);
|
|
487
|
+
}
|
|
488
|
+
return span + "\uFF0C" + sec;
|
|
321
489
|
}
|
|
322
490
|
function composeSecondsCadence(ir) {
|
|
323
491
|
const sec = secondClause(ir);
|
|
324
492
|
const tail = minuteClause(ir) + sec;
|
|
325
|
-
|
|
326
|
-
|
|
493
|
+
const hourCad = hourCadencePhrase(ir);
|
|
494
|
+
if (hourCad !== null) {
|
|
495
|
+
return hourCad + (hourCad.indexOf("\u81F3") === -1 ? "\u7684" : "\uFF0C") + tail;
|
|
327
496
|
}
|
|
328
497
|
if (ir.shapes.hour === "single") {
|
|
329
498
|
return hourWord(hourFires(ir)[0]) + "\u7684" + tail;
|
|
330
499
|
}
|
|
331
500
|
if (ir.shapes.hour === "wildcard") {
|
|
501
|
+
if (ir.shapes.minute === "step" && sec === "\u6BCF\u79D2") {
|
|
502
|
+
const minuteStep = stepSegment(ir, "minute");
|
|
503
|
+
if (minuteStep.startToken === "*" && minuteStep.interval === 2) {
|
|
504
|
+
return "\u6BCF\u5076\u6570\u5206\u949F\u7684\u6BCF\u4E00\u79D2";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
332
507
|
return sec + "\uFF0C" + minuteClause(ir);
|
|
333
508
|
}
|
|
334
509
|
return hourFrame(ir) + tail;
|
|
335
510
|
}
|
|
336
511
|
function composeSecondsListed(ir) {
|
|
337
512
|
const sec = secondClause(ir);
|
|
338
|
-
const minutes =
|
|
513
|
+
const minutes = minuteHourClause(ir);
|
|
339
514
|
if (ir.shapes.hour === "single" && sec === "\u6BCF\u79D2") {
|
|
340
|
-
|
|
515
|
+
const minuteSegs = fieldSegments(ir, "minute");
|
|
516
|
+
const minuteCad = strideFromSegments(minuteSegs, "\u5206\u949F", "\u5206", "") ?? valueList(minuteSegs, "\u5206");
|
|
517
|
+
return hourWord(hourFires(ir)[0]) + minuteCad + "\u7684\u6BCF\u4E00\u79D2";
|
|
341
518
|
}
|
|
342
519
|
if (ir.shapes.hour === "wildcard") {
|
|
343
520
|
return minutes + "\uFF0C" + sec;
|
|
344
521
|
}
|
|
345
|
-
|
|
346
|
-
|
|
522
|
+
const hourCad = hourCadencePhrase(ir);
|
|
523
|
+
if (hourCad !== null) {
|
|
524
|
+
return hourCad + "\uFF0C" + minutes + "\uFF0C" + sec;
|
|
347
525
|
}
|
|
348
526
|
return hourFrame(ir) + minutes + "\uFF0C" + sec;
|
|
349
527
|
}
|
|
@@ -554,11 +732,16 @@ function composeCadence(ir, core) {
|
|
|
554
732
|
function composeWindow(ir, core) {
|
|
555
733
|
return qualifier(ir) + core;
|
|
556
734
|
}
|
|
735
|
+
function hourCadenceApplies(ir) {
|
|
736
|
+
return hourCadenceText(ir) !== null;
|
|
737
|
+
}
|
|
557
738
|
function describe(ir, opts) {
|
|
558
739
|
const { kind } = ir.plan;
|
|
559
740
|
const core = render(ir, ir.plan, opts);
|
|
560
741
|
let composed = core;
|
|
561
|
-
if (
|
|
742
|
+
if (hourCadenceApplies(ir)) {
|
|
743
|
+
composed = composeCadence(ir, core);
|
|
744
|
+
} else if (kind === "clockTimes" || kind === "compactClockTimes" && ir.pattern.minute === "0") {
|
|
562
745
|
composed = composePoint(ir, core);
|
|
563
746
|
} else if (kind === "hourStep" || kind === "minuteFrequency" || kind === "minuteSpanAcrossHourStep" || kind === "compactClockTimes") {
|
|
564
747
|
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.6",
|
|
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",
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
"test:types": "npm run types && tsd",
|
|
70
71
|
"typecheck": "tsc -p tsconfig.json",
|
|
71
72
|
"coverage": "c8 mocha",
|
|
72
|
-
"verify": "npm run lint && npm run typecheck && npm run test:types && npm test && npm run coverage && npm run docs -- --check && npm run build",
|
|
73
|
+
"verify": "npm run lint && npm run typecheck && npm run test:types && npm test && npm run coverage && npm run conciseness && npm run docs -- --check && npm run build",
|
|
73
74
|
"prepare": "node scripts/install-hooks.mjs",
|
|
74
75
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test:types && npm run build && npm test"
|
|
75
76
|
},
|
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
|
+
};
|