cronli5 0.1.6 → 0.2.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 +86 -0
- package/README.md +6 -6
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +401 -81
- package/dist/cronli5.js +401 -81
- package/dist/lang/de.cjs +63 -17
- package/dist/lang/de.js +63 -17
- package/dist/lang/en.cjs +401 -81
- package/dist/lang/en.js +401 -81
- package/dist/lang/es.cjs +68 -5
- package/dist/lang/es.js +68 -5
- package/dist/lang/fi.cjs +21 -1
- package/dist/lang/fi.js +21 -1
- package/dist/lang/zh.cjs +38 -7
- package/dist/lang/zh.js +38 -7
- package/package.json +1 -1
- package/src/core/ir.ts +5 -0
- package/src/core/util.ts +52 -1
- package/src/lang/de/index.ts +95 -25
- package/src/lang/en/dialects.ts +6 -2
- package/src/lang/en/index.ts +781 -117
- package/src/lang/es/index.ts +85 -9
- package/src/lang/fi/index.ts +6 -2
- package/src/lang/zh/index.ts +44 -18
- package/types/core/ir.d.ts +1 -0
- package/types/core/util.d.ts +10 -1
package/dist/lang/en.cjs
CHANGED
|
@@ -40,6 +40,26 @@ function arithmeticStep(values) {
|
|
|
40
40
|
}
|
|
41
41
|
return { start: values[0], interval, last: values[values.length - 1] };
|
|
42
42
|
}
|
|
43
|
+
function weekdayDisplayKey(value) {
|
|
44
|
+
return value === 0 ? 7 : value;
|
|
45
|
+
}
|
|
46
|
+
function orderWeekdaysForDisplay(segments) {
|
|
47
|
+
const flattened = segments.flatMap(function flat(segment) {
|
|
48
|
+
return segment.kind === "step" ? segment.fires.map(function single(value) {
|
|
49
|
+
return { kind: "single", value: "" + value };
|
|
50
|
+
}) : [segment];
|
|
51
|
+
});
|
|
52
|
+
function key(segment) {
|
|
53
|
+
return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
|
|
54
|
+
}
|
|
55
|
+
return flattened.map(function index(segment, position) {
|
|
56
|
+
return [segment, position];
|
|
57
|
+
}).sort(function byDisplayKey(a, b) {
|
|
58
|
+
return key(a[0]) - key(b[0]) || a[1] - b[1];
|
|
59
|
+
}).map(function unwrap(pair) {
|
|
60
|
+
return pair[0];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
43
63
|
|
|
44
64
|
// src/core/specs.ts
|
|
45
65
|
var maxClockTimes = 6;
|
|
@@ -84,7 +104,8 @@ var dialects = {
|
|
|
84
104
|
pm: "p.m.",
|
|
85
105
|
sep: ":",
|
|
86
106
|
serialComma: true,
|
|
87
|
-
through: " through "
|
|
107
|
+
through: " through ",
|
|
108
|
+
untilWindow: true
|
|
88
109
|
},
|
|
89
110
|
house: {
|
|
90
111
|
am: "AM",
|
|
@@ -101,7 +122,7 @@ var dialects = {
|
|
|
101
122
|
};
|
|
102
123
|
function resolveDialect(dialect) {
|
|
103
124
|
if (typeof dialect === "object" && dialect !== null) {
|
|
104
|
-
return { ...dialects.us, ...dialect };
|
|
125
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
105
126
|
}
|
|
106
127
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
107
128
|
return dialects[name] || dialects.us;
|
|
@@ -173,7 +194,9 @@ function normalizeOptions(options) {
|
|
|
173
194
|
};
|
|
174
195
|
}
|
|
175
196
|
function describe(ir, opts) {
|
|
176
|
-
|
|
197
|
+
const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
|
|
198
|
+
const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
|
|
199
|
+
return applyYear(lead + body, ir, opts);
|
|
177
200
|
}
|
|
178
201
|
function render(ir, plan, opts) {
|
|
179
202
|
const renderer = renderers[plan.kind];
|
|
@@ -266,7 +289,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
266
289
|
}
|
|
267
290
|
if (shape === "range") {
|
|
268
291
|
const bounds = secondField.split("-");
|
|
269
|
-
const num = seriesNumber(
|
|
292
|
+
const num = seriesNumber();
|
|
270
293
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
271
294
|
}
|
|
272
295
|
if (shape === "single") {
|
|
@@ -332,15 +355,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
332
355
|
}
|
|
333
356
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
334
357
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"
|
|
342
|
-
|
|
343
|
-
)
|
|
358
|
+
if (plan.form === "range") {
|
|
359
|
+
const lead2 = minuteRangeLead(ir.pattern.minute, opts);
|
|
360
|
+
if (cadence !== null) {
|
|
361
|
+
return lead2 + ", " + cadence + trailingQualifier(ir, opts);
|
|
362
|
+
}
|
|
363
|
+
if (singleHourFire(plan.times)) {
|
|
364
|
+
return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
|
|
365
|
+
}
|
|
366
|
+
return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
367
|
+
}
|
|
368
|
+
const lead = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
369
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
370
|
+
"minute",
|
|
371
|
+
"hour",
|
|
372
|
+
opts
|
|
344
373
|
);
|
|
345
374
|
if (cadence !== null) {
|
|
346
375
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -377,7 +406,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
377
406
|
}
|
|
378
407
|
function minuteRangeLead(minuteField, opts) {
|
|
379
408
|
const bounds = minuteField.split("-");
|
|
380
|
-
const num = seriesNumber(
|
|
409
|
+
const num = seriesNumber();
|
|
381
410
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
382
411
|
}
|
|
383
412
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -417,10 +446,18 @@ function renderHourStep(ir, plan, opts) {
|
|
|
417
446
|
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
418
447
|
}
|
|
419
448
|
function boundedWindow(plan) {
|
|
420
|
-
|
|
449
|
+
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
450
|
+
return { from: plan.from, last, to: plan.to };
|
|
451
|
+
}
|
|
452
|
+
function rangeWindow(from, to, throughMinute, opts) {
|
|
453
|
+
const open = "from " + getTime({ hour: from, minute: 0 }, opts);
|
|
454
|
+
if (opts.style.untilWindow && !opts.short && from !== to) {
|
|
455
|
+
return open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts);
|
|
456
|
+
}
|
|
457
|
+
return open + through(opts) + getTime({ hour: to, minute: throughMinute }, opts);
|
|
421
458
|
}
|
|
422
459
|
function hourWindow(window, opts) {
|
|
423
|
-
return
|
|
460
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
424
461
|
}
|
|
425
462
|
function renderClockTimes(ir, plan, opts) {
|
|
426
463
|
if (ir.shapes.minute === "single") {
|
|
@@ -439,7 +476,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
439
476
|
plain
|
|
440
477
|
}, opts);
|
|
441
478
|
});
|
|
442
|
-
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts);
|
|
479
|
+
return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
|
|
480
|
+
}
|
|
481
|
+
function dayUnionTrail(ir, opts) {
|
|
482
|
+
return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
|
|
443
483
|
}
|
|
444
484
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
445
485
|
if (plan.fold) {
|
|
@@ -454,7 +494,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
454
494
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
455
495
|
}
|
|
456
496
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
457
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
497
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
458
498
|
}
|
|
459
499
|
const minuteLead = (
|
|
460
500
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -473,26 +513,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
473
513
|
function foldedHourWindows(ir, plan, opts) {
|
|
474
514
|
const minute = plan.minute;
|
|
475
515
|
const windows = [];
|
|
476
|
-
const
|
|
516
|
+
const outliers = collectHourOutliers(ir);
|
|
517
|
+
const times = outliers.hours.map(function time(hour) {
|
|
518
|
+
return getTime({ hour, minute }, opts);
|
|
519
|
+
});
|
|
477
520
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
478
521
|
if (segment.kind === "range") {
|
|
479
|
-
windows.push(
|
|
480
|
-
|
|
522
|
+
windows.push(rangeWindow(
|
|
523
|
+
+segment.bounds[0],
|
|
524
|
+
+segment.bounds[1],
|
|
525
|
+
minute,
|
|
481
526
|
opts
|
|
482
|
-
)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
527
|
+
));
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
|
|
531
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
532
|
+
}
|
|
533
|
+
function collectHourOutliers(ir) {
|
|
534
|
+
const hours = [];
|
|
535
|
+
let pureStrays = true;
|
|
536
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
537
|
+
if (segment.kind === "step") {
|
|
538
|
+
hours.push(...segment.fires);
|
|
539
|
+
pureStrays = false;
|
|
540
|
+
} else if (segment.kind !== "range") {
|
|
541
|
+
hours.push(+segment.value);
|
|
487
542
|
}
|
|
488
543
|
});
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
544
|
+
return { hours, pureStrays };
|
|
545
|
+
}
|
|
546
|
+
function outlierTail(times, pureStrays, opts) {
|
|
547
|
+
if (!times.length) {
|
|
548
|
+
return "";
|
|
494
549
|
}
|
|
495
|
-
|
|
550
|
+
const connector = pureStrays && opts.style.untilWindow && !opts.short ? " plus " : " and at ";
|
|
551
|
+
return connector + joinList(times, opts);
|
|
552
|
+
}
|
|
553
|
+
function isCadenceField(token) {
|
|
554
|
+
return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
|
|
555
|
+
}
|
|
556
|
+
function leadingCadence(ir, opts) {
|
|
557
|
+
const { second, minute } = ir.pattern;
|
|
558
|
+
if (isCadenceField(second)) {
|
|
559
|
+
return { secondLead: true, text: secondsClause(ir, "minute", opts) };
|
|
560
|
+
}
|
|
561
|
+
if (second === "0" && isCadenceField(minute)) {
|
|
562
|
+
const text = minute === "*" ? "every minute" : (
|
|
563
|
+
// A clean minute step's first segment is a step segment.
|
|
564
|
+
stepCycle60(
|
|
565
|
+
ir.analyses.segments.minute[0],
|
|
566
|
+
"minute",
|
|
567
|
+
"hour",
|
|
568
|
+
opts
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
return { secondLead: false, text };
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
function minuteConfinement(ir, opts) {
|
|
576
|
+
const minute = ir.pattern.minute;
|
|
577
|
+
if (minute === "*") {
|
|
578
|
+
return "";
|
|
579
|
+
}
|
|
580
|
+
if (isCadenceField(minute)) {
|
|
581
|
+
return " of every other minute";
|
|
582
|
+
}
|
|
583
|
+
const segments = ir.analyses.segments.minute;
|
|
584
|
+
if (ir.shapes.minute === "single") {
|
|
585
|
+
return " during minute :" + pad(minute);
|
|
586
|
+
}
|
|
587
|
+
if (ir.shapes.minute === "range") {
|
|
588
|
+
const bounds = minute.split("-");
|
|
589
|
+
return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
|
|
590
|
+
}
|
|
591
|
+
const values = segmentWords(segments, opts).map(function colon(word) {
|
|
592
|
+
return ":" + pad(word);
|
|
593
|
+
});
|
|
594
|
+
return " during minutes " + joinList(values, opts);
|
|
595
|
+
}
|
|
596
|
+
function hourConfinement(ir, opts) {
|
|
597
|
+
const hour = ir.pattern.hour;
|
|
598
|
+
if (hour === "*") {
|
|
599
|
+
const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
|
|
600
|
+
return minutePinned ? " of every hour" : "";
|
|
601
|
+
}
|
|
602
|
+
if (isCadenceField(hour)) {
|
|
603
|
+
return hour === "*/2" ? " of every other hour" : "";
|
|
604
|
+
}
|
|
605
|
+
if (ir.shapes.hour === "single") {
|
|
606
|
+
const h = +hour;
|
|
607
|
+
if (ir.shapes.minute === "step") {
|
|
608
|
+
return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
|
|
609
|
+
}
|
|
610
|
+
if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
|
|
611
|
+
return " at " + getTime({ hour: h, minute: 0 }, opts);
|
|
612
|
+
}
|
|
613
|
+
return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
|
|
614
|
+
}
|
|
615
|
+
if (ir.shapes.hour === "range") {
|
|
616
|
+
const bounds = hour.split("-");
|
|
617
|
+
return " " + rangeWindow(+bounds[0], +bounds[1], 0, opts);
|
|
618
|
+
}
|
|
619
|
+
return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
|
|
620
|
+
}
|
|
621
|
+
function isContiguousHourRange(ir) {
|
|
622
|
+
return ir.shapes.hour === "range";
|
|
623
|
+
}
|
|
624
|
+
function confinableHour(ir) {
|
|
625
|
+
if (ir.shapes.hour !== "step") {
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
const segment = ir.analyses.segments.hour[0];
|
|
629
|
+
return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
|
|
630
|
+
}
|
|
631
|
+
function isMinuteStride(ir) {
|
|
632
|
+
if (ir.shapes.minute !== "list") {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const values = singleValues(ir.analyses.segments.minute);
|
|
636
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
637
|
+
}
|
|
638
|
+
function confinementEligible(ir, lead) {
|
|
639
|
+
const { minute, hour } = ir.pattern;
|
|
640
|
+
const minuteStep = isCadenceField(minute) && minute !== "*";
|
|
641
|
+
if (!confinableHour(ir)) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
if (lead.secondLead) {
|
|
645
|
+
if (minuteStep) {
|
|
646
|
+
return minute === "*/2" && !isContiguousHourRange(ir);
|
|
647
|
+
}
|
|
648
|
+
if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
if (hour === "*/2") {
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
return ir.shapes.hour === "single" && minute === "*/2";
|
|
657
|
+
}
|
|
658
|
+
function confinement(ir, opts) {
|
|
659
|
+
if (!opts.style.untilWindow || opts.short) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const lead = leadingCadence(ir, opts);
|
|
666
|
+
if (!lead || !confinementEligible(ir, lead)) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
|
|
670
|
+
return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
|
|
496
671
|
}
|
|
497
672
|
var renderers = {
|
|
498
673
|
clockTimes: renderClockTimes,
|
|
@@ -524,7 +699,7 @@ function renderStride(stride, opts) {
|
|
|
524
699
|
if (start < interval && tiles) {
|
|
525
700
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
526
701
|
}
|
|
527
|
-
const num = seriesNumber(
|
|
702
|
+
const num = seriesNumber();
|
|
528
703
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
529
704
|
}
|
|
530
705
|
function singleValues(segments) {
|
|
@@ -651,9 +826,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
651
826
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
652
827
|
return null;
|
|
653
828
|
}
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(
|
|
829
|
+
const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
830
|
+
if (minuteZeroStride) {
|
|
831
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
|
|
657
832
|
}
|
|
658
833
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
659
834
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -675,26 +850,22 @@ function hasHourWindow(ir) {
|
|
|
675
850
|
}
|
|
676
851
|
function hourRangeWindowTail(ir, opts) {
|
|
677
852
|
const windows = [];
|
|
678
|
-
const
|
|
853
|
+
const outliers = collectHourOutliers(ir);
|
|
679
854
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
680
855
|
if (segment.kind === "range") {
|
|
681
|
-
windows.push(
|
|
682
|
-
|
|
856
|
+
windows.push(rangeWindow(
|
|
857
|
+
+segment.bounds[0],
|
|
858
|
+
+segment.bounds[1],
|
|
859
|
+
0,
|
|
683
860
|
opts
|
|
684
|
-
)
|
|
685
|
-
} else if (segment.kind === "step") {
|
|
686
|
-
singles.push(...segment.fires);
|
|
687
|
-
} else {
|
|
688
|
-
singles.push(+segment.value);
|
|
861
|
+
));
|
|
689
862
|
}
|
|
690
863
|
});
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
return phrase;
|
|
864
|
+
const phrase = "every hour " + joinList(windows, opts);
|
|
865
|
+
const times = outliers.hours.map(function time(hour) {
|
|
866
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
867
|
+
});
|
|
868
|
+
return phrase + outlierTail(times, outliers.pureStrays, opts);
|
|
698
869
|
}
|
|
699
870
|
function hourRangeCadence(ir, minute, opts) {
|
|
700
871
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -708,25 +879,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
708
879
|
}
|
|
709
880
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
710
881
|
}
|
|
711
|
-
function seriesNumber(
|
|
712
|
-
const anyBig = values.some(function big(v) {
|
|
713
|
-
return +v > 10;
|
|
714
|
-
});
|
|
882
|
+
function seriesNumber() {
|
|
715
883
|
return function format(n) {
|
|
716
|
-
return
|
|
884
|
+
return "" + n;
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function listNumber(count, opts) {
|
|
888
|
+
return count > 1 ? function asNumeral(n) {
|
|
889
|
+
return "" + n;
|
|
890
|
+
} : function spelled(n) {
|
|
891
|
+
return getNumber(n, opts);
|
|
717
892
|
};
|
|
718
893
|
}
|
|
719
894
|
function numberWords(fires, opts) {
|
|
720
|
-
return fires.map(
|
|
895
|
+
return fires.map(listNumber(fires.length, opts));
|
|
721
896
|
}
|
|
722
897
|
function segmentWords(segments, opts) {
|
|
723
|
-
const
|
|
898
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
724
899
|
if (segment.kind === "range") {
|
|
725
|
-
return
|
|
900
|
+
return sum + 1;
|
|
726
901
|
}
|
|
727
|
-
return segment.kind === "step" ? segment.fires :
|
|
728
|
-
});
|
|
729
|
-
const num =
|
|
902
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
903
|
+
}, 0);
|
|
904
|
+
const num = listNumber(count, opts);
|
|
730
905
|
return segments.flatMap(function word(segment) {
|
|
731
906
|
if (segment.kind === "range") {
|
|
732
907
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -758,6 +933,9 @@ function hourTimes(hours, opts) {
|
|
|
758
933
|
});
|
|
759
934
|
return joinList(times, opts);
|
|
760
935
|
}
|
|
936
|
+
function singleHourFire(times) {
|
|
937
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
938
|
+
}
|
|
761
939
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
762
940
|
if (times.kind === "fires") {
|
|
763
941
|
return hourTimes(times.fires, opts);
|
|
@@ -805,28 +983,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
805
983
|
return index === 0 ? piece : "at " + piece;
|
|
806
984
|
});
|
|
807
985
|
}
|
|
808
|
-
function
|
|
986
|
+
function joinWith(items, conjunction, opts) {
|
|
809
987
|
if (items.length <= 1) {
|
|
810
988
|
return items.join("");
|
|
811
989
|
}
|
|
812
990
|
if (items.length === 2) {
|
|
813
|
-
return items[0] +
|
|
991
|
+
return items[0] + conjunction + items[1];
|
|
814
992
|
}
|
|
815
|
-
const
|
|
816
|
-
return items.slice(0, -1).join(", ") +
|
|
993
|
+
const tail = opts.style.serialComma ? "," + conjunction : conjunction;
|
|
994
|
+
return items.slice(0, -1).join(", ") + tail + items[items.length - 1];
|
|
817
995
|
}
|
|
818
|
-
|
|
996
|
+
function joinList(items, opts) {
|
|
997
|
+
return joinWith(items, " and ", opts);
|
|
998
|
+
}
|
|
999
|
+
function joinOr(items, opts) {
|
|
1000
|
+
return joinWith(items, " or ", opts);
|
|
1001
|
+
}
|
|
1002
|
+
var trailingWords = {
|
|
1003
|
+
all: "",
|
|
1004
|
+
month: "in ",
|
|
1005
|
+
recurringWeekday: true,
|
|
1006
|
+
stepDate: "on ",
|
|
1007
|
+
weekday: "on "
|
|
1008
|
+
};
|
|
819
1009
|
var leadingWords = {
|
|
820
1010
|
all: "every day",
|
|
821
1011
|
month: "every day in ",
|
|
1012
|
+
recurringWeekday: false,
|
|
822
1013
|
stepDate: "",
|
|
823
1014
|
weekday: "every "
|
|
824
1015
|
};
|
|
825
1016
|
function trailingQualifier(ir, opts) {
|
|
1017
|
+
if (isDayUnion(ir, opts)) {
|
|
1018
|
+
return dayUnionCondition(ir, opts);
|
|
1019
|
+
}
|
|
826
1020
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
827
1021
|
return phrase && " " + phrase;
|
|
828
1022
|
}
|
|
829
1023
|
function interpretDayQualifier(ir, opts) {
|
|
1024
|
+
if (isDayUnion(ir, opts)) {
|
|
1025
|
+
return "";
|
|
1026
|
+
}
|
|
830
1027
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
831
1028
|
}
|
|
832
1029
|
function dayQualifier(ir, words, opts) {
|
|
@@ -838,7 +1035,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
838
1035
|
return datePhrase(ir, words, opts);
|
|
839
1036
|
}
|
|
840
1037
|
if (pattern.weekday !== "*") {
|
|
841
|
-
const
|
|
1038
|
+
const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
|
|
1039
|
+
if (quartzWeekday) {
|
|
1040
|
+
return monthScopeForRecurrence(quartzWeekday, ir, opts);
|
|
1041
|
+
}
|
|
1042
|
+
const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
|
|
842
1043
|
return weekdays + monthScope(ir, opts);
|
|
843
1044
|
}
|
|
844
1045
|
if (pattern.month !== "*") {
|
|
@@ -850,10 +1051,14 @@ function datePhrase(ir, words, opts) {
|
|
|
850
1051
|
const pattern = ir.pattern;
|
|
851
1052
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
852
1053
|
if (quartzDate) {
|
|
853
|
-
return quartzDate
|
|
1054
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
854
1055
|
}
|
|
855
1056
|
if (isOpenStep(pattern.date)) {
|
|
856
|
-
return
|
|
1057
|
+
return monthScopeForRecurrence(
|
|
1058
|
+
words.stepDate + stepDates(pattern.date),
|
|
1059
|
+
ir,
|
|
1060
|
+
opts
|
|
1061
|
+
);
|
|
857
1062
|
}
|
|
858
1063
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
859
1064
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -869,20 +1074,105 @@ function monthFoldsIntoDate(ir) {
|
|
|
869
1074
|
return segment.kind !== "range";
|
|
870
1075
|
});
|
|
871
1076
|
}
|
|
1077
|
+
function isDayUnion(ir, opts) {
|
|
1078
|
+
return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
|
|
1079
|
+
}
|
|
1080
|
+
function dayUnionCondition(ir, opts) {
|
|
1081
|
+
const pieces = [
|
|
1082
|
+
...dayUnionDatePieces(ir, opts),
|
|
1083
|
+
...dayUnionWeekdayPieces(ir, opts)
|
|
1084
|
+
];
|
|
1085
|
+
return " whenever the day is " + joinOr(pieces, opts);
|
|
1086
|
+
}
|
|
1087
|
+
function dayUnionMonthLead(ir, opts) {
|
|
1088
|
+
if (ir.pattern.month === "*") {
|
|
1089
|
+
return "";
|
|
1090
|
+
}
|
|
1091
|
+
return "in " + monthName(ir, opts) + " ";
|
|
1092
|
+
}
|
|
1093
|
+
function dayUnionDatePieces(ir, opts) {
|
|
1094
|
+
const dateField = ir.pattern.date;
|
|
1095
|
+
const quartz = quartzDatePhrase(dateField, opts);
|
|
1096
|
+
if (quartz) {
|
|
1097
|
+
return [quartz.replace(/^on /, "")];
|
|
1098
|
+
}
|
|
1099
|
+
const oddEven = oddEvenDay(dateField);
|
|
1100
|
+
if (oddEven) {
|
|
1101
|
+
return [oddEven];
|
|
1102
|
+
}
|
|
1103
|
+
const pieces = [];
|
|
1104
|
+
ir.analyses.segments.date.forEach(function expand(segment) {
|
|
1105
|
+
if (segment.kind === "range") {
|
|
1106
|
+
pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
|
|
1107
|
+
} else if (segment.kind === "step") {
|
|
1108
|
+
segment.fires.forEach(function fire(value) {
|
|
1109
|
+
pieces.push("the " + getOrdinal(value));
|
|
1110
|
+
});
|
|
1111
|
+
} else {
|
|
1112
|
+
pieces.push("the " + getOrdinal(segment.value));
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
return pieces;
|
|
1116
|
+
}
|
|
1117
|
+
function dayUnionWeekdayPieces(ir, opts) {
|
|
1118
|
+
const weekdayField = ir.pattern.weekday;
|
|
1119
|
+
const quartz = quartzWeekdayPhrase(weekdayField, opts);
|
|
1120
|
+
if (quartz) {
|
|
1121
|
+
return [quartz.replace(/^on /, "")];
|
|
1122
|
+
}
|
|
1123
|
+
const pieces = [];
|
|
1124
|
+
ir.analyses.segments.weekday.forEach(function expand(segment) {
|
|
1125
|
+
if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
|
|
1126
|
+
pieces.push("a weekday");
|
|
1127
|
+
} else if (segment.kind === "range") {
|
|
1128
|
+
pieces.push("a " + getWeekday(segment.bounds[0], opts) + through(opts) + "a " + getWeekday(segment.bounds[1], opts));
|
|
1129
|
+
} else if (segment.kind === "step") {
|
|
1130
|
+
segment.fires.forEach(function fire(value) {
|
|
1131
|
+
pieces.push("a " + getWeekday(value, opts));
|
|
1132
|
+
});
|
|
1133
|
+
} else {
|
|
1134
|
+
pieces.push("a " + getWeekday(segment.value, opts));
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
return pieces;
|
|
1138
|
+
}
|
|
1139
|
+
function oddEvenDay(dateField) {
|
|
1140
|
+
if (!isOpenStep(dateField)) {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
const [start, step] = dateField.split("/");
|
|
1144
|
+
if (+step !== 2) {
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
if (start === "*" || start === "1") {
|
|
1148
|
+
return "an odd-numbered day";
|
|
1149
|
+
}
|
|
1150
|
+
return start === "2" ? "an even-numbered day" : null;
|
|
1151
|
+
}
|
|
872
1152
|
function dateOrWeekday(ir, opts) {
|
|
873
1153
|
const pattern = ir.pattern;
|
|
874
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1154
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
1155
|
+
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
1156
|
+
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
1157
|
+
}
|
|
1158
|
+
return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
|
|
1159
|
+
}
|
|
1160
|
+
function datePart(ir, opts) {
|
|
1161
|
+
const pattern = ir.pattern;
|
|
875
1162
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
876
1163
|
if (quartzDate) {
|
|
877
|
-
return quartzDate
|
|
1164
|
+
return quartzDate;
|
|
878
1165
|
}
|
|
879
1166
|
if (isOpenStep(pattern.date)) {
|
|
880
|
-
return stepDates(pattern.date)
|
|
1167
|
+
return stepDates(pattern.date);
|
|
881
1168
|
}
|
|
882
|
-
|
|
883
|
-
|
|
1169
|
+
return "on the " + dateOrdinals(ir, opts);
|
|
1170
|
+
}
|
|
1171
|
+
function orMonthScope(ir, opts) {
|
|
1172
|
+
if (ir.pattern.month === "*") {
|
|
1173
|
+
return "";
|
|
884
1174
|
}
|
|
885
|
-
return "
|
|
1175
|
+
return ", in " + monthName(ir, opts);
|
|
886
1176
|
}
|
|
887
1177
|
function quartzDatePhrase(dateField, opts) {
|
|
888
1178
|
if (dateField === "L") {
|
|
@@ -916,6 +1206,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
916
1206
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
917
1207
|
opts
|
|
918
1208
|
);
|
|
1209
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1210
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1211
|
+
}
|
|
919
1212
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
920
1213
|
}
|
|
921
1214
|
function cardinalDay(value) {
|
|
@@ -927,6 +1220,19 @@ function monthScope(ir, opts) {
|
|
|
927
1220
|
}
|
|
928
1221
|
return " in " + monthName(ir, opts);
|
|
929
1222
|
}
|
|
1223
|
+
function monthScopeForRecurrence(phrase, ir, opts) {
|
|
1224
|
+
if (ir.pattern.month === "*") {
|
|
1225
|
+
return phrase;
|
|
1226
|
+
}
|
|
1227
|
+
const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
|
|
1228
|
+
if (carriesRecurrence && ir.shapes.month === "range") {
|
|
1229
|
+
return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
|
|
1230
|
+
}
|
|
1231
|
+
if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
|
|
1232
|
+
return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
|
|
1233
|
+
}
|
|
1234
|
+
return phrase + " in " + monthName(ir, opts);
|
|
1235
|
+
}
|
|
930
1236
|
function stepDates(dateField) {
|
|
931
1237
|
const parts = dateField.split("/");
|
|
932
1238
|
const interval = +parts[1];
|
|
@@ -963,10 +1269,21 @@ function oddEvenMonth(monthField) {
|
|
|
963
1269
|
}
|
|
964
1270
|
return start === "2" ? "every even-numbered month" : null;
|
|
965
1271
|
}
|
|
966
|
-
function weekdayPhrase(ir, opts) {
|
|
967
|
-
|
|
1272
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
1273
|
+
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
1274
|
+
const hasRange = segments.some(function range(segment) {
|
|
1275
|
+
return segment.kind === "range";
|
|
1276
|
+
});
|
|
1277
|
+
const name = recurring && !hasRange ? function plural(value) {
|
|
1278
|
+
return pluralWeekday(value, opts);
|
|
1279
|
+
} : function singular(value) {
|
|
968
1280
|
return getWeekday(value, opts);
|
|
969
|
-
}
|
|
1281
|
+
};
|
|
1282
|
+
return renderSegments(segments, name, opts);
|
|
1283
|
+
}
|
|
1284
|
+
function pluralWeekday(value, opts) {
|
|
1285
|
+
const name = getWeekday(value, opts);
|
|
1286
|
+
return opts.short ? name : name + "s";
|
|
970
1287
|
}
|
|
971
1288
|
function renderSegments(segments, word, opts) {
|
|
972
1289
|
const pieces = [];
|
|
@@ -990,7 +1307,7 @@ function applyYear(description, ir, opts) {
|
|
|
990
1307
|
return description;
|
|
991
1308
|
}
|
|
992
1309
|
if (yearField.indexOf("/") !== -1) {
|
|
993
|
-
return description + " " + stepYears(yearField, opts);
|
|
1310
|
+
return description + ", " + stepYears(yearField, opts);
|
|
994
1311
|
}
|
|
995
1312
|
const label = yearLabel(yearField, opts);
|
|
996
1313
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1003,6 +1320,9 @@ function yearLabel(yearField, opts) {
|
|
|
1003
1320
|
if (yearField.indexOf(",") !== -1) {
|
|
1004
1321
|
return joinList(yearField.split(","), opts);
|
|
1005
1322
|
}
|
|
1323
|
+
if (yearField.indexOf("-") !== -1) {
|
|
1324
|
+
return yearField.split("-").join(through(opts));
|
|
1325
|
+
}
|
|
1006
1326
|
return yearField;
|
|
1007
1327
|
}
|
|
1008
1328
|
function stepYears(yearField, opts) {
|
|
@@ -1012,7 +1332,7 @@ function stepYears(yearField, opts) {
|
|
|
1012
1332
|
if (interval <= 1) {
|
|
1013
1333
|
return "every year";
|
|
1014
1334
|
}
|
|
1015
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
1335
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1016
1336
|
if (start !== "*" && start !== "0") {
|
|
1017
1337
|
phrase += " from " + start;
|
|
1018
1338
|
}
|