cronli5 0.1.7 → 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 +43 -0
- package/README.md +5 -5
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +363 -75
- package/dist/cronli5.js +363 -75
- package/dist/lang/en.cjs +363 -75
- package/dist/lang/en.js +363 -75
- package/package.json +1 -1
- package/src/core/ir.ts +5 -0
- package/src/lang/en/dialects.ts +6 -2
- package/src/lang/en/index.ts +735 -102
- package/types/core/ir.d.ts +1 -0
package/dist/lang/en.cjs
CHANGED
|
@@ -104,7 +104,8 @@ var dialects = {
|
|
|
104
104
|
pm: "p.m.",
|
|
105
105
|
sep: ":",
|
|
106
106
|
serialComma: true,
|
|
107
|
-
through: " through "
|
|
107
|
+
through: " through ",
|
|
108
|
+
untilWindow: true
|
|
108
109
|
},
|
|
109
110
|
house: {
|
|
110
111
|
am: "AM",
|
|
@@ -121,7 +122,7 @@ var dialects = {
|
|
|
121
122
|
};
|
|
122
123
|
function resolveDialect(dialect) {
|
|
123
124
|
if (typeof dialect === "object" && dialect !== null) {
|
|
124
|
-
return { ...dialects.us, ...dialect };
|
|
125
|
+
return { ...dialects.us, untilWindow: false, ...dialect };
|
|
125
126
|
}
|
|
126
127
|
const name = dialect === "uk" ? "gb" : dialect;
|
|
127
128
|
return dialects[name] || dialects.us;
|
|
@@ -193,7 +194,9 @@ function normalizeOptions(options) {
|
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
196
|
function describe(ir, opts) {
|
|
196
|
-
|
|
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);
|
|
197
200
|
}
|
|
198
201
|
function render(ir, plan, opts) {
|
|
199
202
|
const renderer = renderers[plan.kind];
|
|
@@ -286,7 +289,7 @@ function secondsClause(ir, anchor, opts) {
|
|
|
286
289
|
}
|
|
287
290
|
if (shape === "range") {
|
|
288
291
|
const bounds = secondField.split("-");
|
|
289
|
-
const num = seriesNumber(
|
|
292
|
+
const num = seriesNumber();
|
|
290
293
|
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
291
294
|
}
|
|
292
295
|
if (shape === "single") {
|
|
@@ -352,15 +355,21 @@ function renderMinutesAcrossHours(ir, plan, opts) {
|
|
|
352
355
|
}
|
|
353
356
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
354
357
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
|
|
363
|
-
)
|
|
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
|
|
364
373
|
);
|
|
365
374
|
if (cadence !== null) {
|
|
366
375
|
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
@@ -397,7 +406,7 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
397
406
|
}
|
|
398
407
|
function minuteRangeLead(minuteField, opts) {
|
|
399
408
|
const bounds = minuteField.split("-");
|
|
400
|
-
const num = seriesNumber(
|
|
409
|
+
const num = seriesNumber();
|
|
401
410
|
return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
|
|
402
411
|
}
|
|
403
412
|
function renderEveryHour(ir, plan, opts) {
|
|
@@ -440,8 +449,15 @@ function boundedWindow(plan) {
|
|
|
440
449
|
const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
|
|
441
450
|
return { from: plan.from, last, to: plan.to };
|
|
442
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);
|
|
458
|
+
}
|
|
443
459
|
function hourWindow(window, opts) {
|
|
444
|
-
return
|
|
460
|
+
return rangeWindow(window.from, window.to, window.last, opts);
|
|
445
461
|
}
|
|
446
462
|
function renderClockTimes(ir, plan, opts) {
|
|
447
463
|
if (ir.shapes.minute === "single") {
|
|
@@ -460,7 +476,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
460
476
|
plain
|
|
461
477
|
}, opts);
|
|
462
478
|
});
|
|
463
|
-
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) : "";
|
|
464
483
|
}
|
|
465
484
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
466
485
|
if (plan.fold) {
|
|
@@ -475,7 +494,7 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
475
494
|
return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
|
|
476
495
|
}
|
|
477
496
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
478
|
-
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
497
|
+
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
|
|
479
498
|
}
|
|
480
499
|
const minuteLead = (
|
|
481
500
|
// The non-fold branch is a minute list, which has segments. An
|
|
@@ -494,26 +513,161 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
494
513
|
function foldedHourWindows(ir, plan, opts) {
|
|
495
514
|
const minute = plan.minute;
|
|
496
515
|
const windows = [];
|
|
497
|
-
const
|
|
516
|
+
const outliers = collectHourOutliers(ir);
|
|
517
|
+
const times = outliers.hours.map(function time(hour) {
|
|
518
|
+
return getTime({ hour, minute }, opts);
|
|
519
|
+
});
|
|
498
520
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
499
521
|
if (segment.kind === "range") {
|
|
500
|
-
windows.push(
|
|
501
|
-
|
|
522
|
+
windows.push(rangeWindow(
|
|
523
|
+
+segment.bounds[0],
|
|
524
|
+
+segment.bounds[1],
|
|
525
|
+
minute,
|
|
502
526
|
opts
|
|
503
|
-
)
|
|
504
|
-
} else if (segment.kind === "step") {
|
|
505
|
-
singles.push(...segment.fires);
|
|
506
|
-
} else {
|
|
507
|
-
singles.push(+segment.value);
|
|
527
|
+
));
|
|
508
528
|
}
|
|
509
529
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
return { hours, pureStrays };
|
|
545
|
+
}
|
|
546
|
+
function outlierTail(times, pureStrays, opts) {
|
|
547
|
+
if (!times.length) {
|
|
548
|
+
return "";
|
|
515
549
|
}
|
|
516
|
-
|
|
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);
|
|
517
671
|
}
|
|
518
672
|
var renderers = {
|
|
519
673
|
clockTimes: renderClockTimes,
|
|
@@ -545,7 +699,7 @@ function renderStride(stride, opts) {
|
|
|
545
699
|
if (start < interval && tiles) {
|
|
546
700
|
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
547
701
|
}
|
|
548
|
-
const num = seriesNumber(
|
|
702
|
+
const num = seriesNumber();
|
|
549
703
|
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
550
704
|
}
|
|
551
705
|
function singleValues(segments) {
|
|
@@ -672,9 +826,9 @@ function hourCadence(ir, minute, opts) {
|
|
|
672
826
|
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
673
827
|
return null;
|
|
674
828
|
}
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
677
|
-
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);
|
|
678
832
|
}
|
|
679
833
|
if (minute === 0 && ir.pattern.second === "0") {
|
|
680
834
|
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
@@ -696,26 +850,22 @@ function hasHourWindow(ir) {
|
|
|
696
850
|
}
|
|
697
851
|
function hourRangeWindowTail(ir, opts) {
|
|
698
852
|
const windows = [];
|
|
699
|
-
const
|
|
853
|
+
const outliers = collectHourOutliers(ir);
|
|
700
854
|
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
701
855
|
if (segment.kind === "range") {
|
|
702
|
-
windows.push(
|
|
703
|
-
|
|
856
|
+
windows.push(rangeWindow(
|
|
857
|
+
+segment.bounds[0],
|
|
858
|
+
+segment.bounds[1],
|
|
859
|
+
0,
|
|
704
860
|
opts
|
|
705
|
-
)
|
|
706
|
-
} else if (segment.kind === "step") {
|
|
707
|
-
singles.push(...segment.fires);
|
|
708
|
-
} else {
|
|
709
|
-
singles.push(+segment.value);
|
|
861
|
+
));
|
|
710
862
|
}
|
|
711
863
|
});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
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);
|
|
719
869
|
}
|
|
720
870
|
function hourRangeCadence(ir, minute, opts) {
|
|
721
871
|
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
@@ -729,25 +879,29 @@ function hourRangeCadence(ir, minute, opts) {
|
|
|
729
879
|
}
|
|
730
880
|
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
731
881
|
}
|
|
732
|
-
function seriesNumber(
|
|
733
|
-
const anyBig = values.some(function big(v) {
|
|
734
|
-
return +v > 10;
|
|
735
|
-
});
|
|
882
|
+
function seriesNumber() {
|
|
736
883
|
return function format(n) {
|
|
737
|
-
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);
|
|
738
892
|
};
|
|
739
893
|
}
|
|
740
894
|
function numberWords(fires, opts) {
|
|
741
|
-
return fires.map(
|
|
895
|
+
return fires.map(listNumber(fires.length, opts));
|
|
742
896
|
}
|
|
743
897
|
function segmentWords(segments, opts) {
|
|
744
|
-
const
|
|
898
|
+
const count = segments.reduce(function tally(sum, segment) {
|
|
745
899
|
if (segment.kind === "range") {
|
|
746
|
-
return
|
|
900
|
+
return sum + 1;
|
|
747
901
|
}
|
|
748
|
-
return segment.kind === "step" ? segment.fires :
|
|
749
|
-
});
|
|
750
|
-
const num =
|
|
902
|
+
return sum + (segment.kind === "step" ? segment.fires.length : 1);
|
|
903
|
+
}, 0);
|
|
904
|
+
const num = listNumber(count, opts);
|
|
751
905
|
return segments.flatMap(function word(segment) {
|
|
752
906
|
if (segment.kind === "range") {
|
|
753
907
|
return [num(segment.bounds[0]) + through(opts) + num(segment.bounds[1])];
|
|
@@ -779,6 +933,9 @@ function hourTimes(hours, opts) {
|
|
|
779
933
|
});
|
|
780
934
|
return joinList(times, opts);
|
|
781
935
|
}
|
|
936
|
+
function singleHourFire(times) {
|
|
937
|
+
return times.kind === "fires" && times.fires.length === 1;
|
|
938
|
+
}
|
|
782
939
|
function hourTimesFromPlan(ir, times, atContext, opts) {
|
|
783
940
|
if (times.kind === "fires") {
|
|
784
941
|
return hourTimes(times.fires, opts);
|
|
@@ -826,28 +983,47 @@ function disambiguateTimes(pieces, segments, atContext) {
|
|
|
826
983
|
return index === 0 ? piece : "at " + piece;
|
|
827
984
|
});
|
|
828
985
|
}
|
|
829
|
-
function
|
|
986
|
+
function joinWith(items, conjunction, opts) {
|
|
830
987
|
if (items.length <= 1) {
|
|
831
988
|
return items.join("");
|
|
832
989
|
}
|
|
833
990
|
if (items.length === 2) {
|
|
834
|
-
return items[0] +
|
|
991
|
+
return items[0] + conjunction + items[1];
|
|
835
992
|
}
|
|
836
|
-
const
|
|
837
|
-
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];
|
|
995
|
+
}
|
|
996
|
+
function joinList(items, opts) {
|
|
997
|
+
return joinWith(items, " and ", opts);
|
|
998
|
+
}
|
|
999
|
+
function joinOr(items, opts) {
|
|
1000
|
+
return joinWith(items, " or ", opts);
|
|
838
1001
|
}
|
|
839
|
-
var trailingWords = {
|
|
1002
|
+
var trailingWords = {
|
|
1003
|
+
all: "",
|
|
1004
|
+
month: "in ",
|
|
1005
|
+
recurringWeekday: true,
|
|
1006
|
+
stepDate: "on ",
|
|
1007
|
+
weekday: "on "
|
|
1008
|
+
};
|
|
840
1009
|
var leadingWords = {
|
|
841
1010
|
all: "every day",
|
|
842
1011
|
month: "every day in ",
|
|
1012
|
+
recurringWeekday: false,
|
|
843
1013
|
stepDate: "",
|
|
844
1014
|
weekday: "every "
|
|
845
1015
|
};
|
|
846
1016
|
function trailingQualifier(ir, opts) {
|
|
1017
|
+
if (isDayUnion(ir, opts)) {
|
|
1018
|
+
return dayUnionCondition(ir, opts);
|
|
1019
|
+
}
|
|
847
1020
|
const phrase = dayQualifier(ir, trailingWords, opts);
|
|
848
1021
|
return phrase && " " + phrase;
|
|
849
1022
|
}
|
|
850
1023
|
function interpretDayQualifier(ir, opts) {
|
|
1024
|
+
if (isDayUnion(ir, opts)) {
|
|
1025
|
+
return "";
|
|
1026
|
+
}
|
|
851
1027
|
return dayQualifier(ir, leadingWords, opts) + " ";
|
|
852
1028
|
}
|
|
853
1029
|
function dayQualifier(ir, words, opts) {
|
|
@@ -859,7 +1035,11 @@ function dayQualifier(ir, words, opts) {
|
|
|
859
1035
|
return datePhrase(ir, words, opts);
|
|
860
1036
|
}
|
|
861
1037
|
if (pattern.weekday !== "*") {
|
|
862
|
-
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);
|
|
863
1043
|
return weekdays + monthScope(ir, opts);
|
|
864
1044
|
}
|
|
865
1045
|
if (pattern.month !== "*") {
|
|
@@ -871,10 +1051,14 @@ function datePhrase(ir, words, opts) {
|
|
|
871
1051
|
const pattern = ir.pattern;
|
|
872
1052
|
const quartzDate = quartzDatePhrase(pattern.date, opts);
|
|
873
1053
|
if (quartzDate) {
|
|
874
|
-
return quartzDate
|
|
1054
|
+
return monthScopeForRecurrence(quartzDate, ir, opts);
|
|
875
1055
|
}
|
|
876
1056
|
if (isOpenStep(pattern.date)) {
|
|
877
|
-
return
|
|
1057
|
+
return monthScopeForRecurrence(
|
|
1058
|
+
words.stepDate + stepDates(pattern.date),
|
|
1059
|
+
ir,
|
|
1060
|
+
opts
|
|
1061
|
+
);
|
|
878
1062
|
}
|
|
879
1063
|
if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
|
|
880
1064
|
return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
|
|
@@ -890,9 +1074,84 @@ function monthFoldsIntoDate(ir) {
|
|
|
890
1074
|
return segment.kind !== "range";
|
|
891
1075
|
});
|
|
892
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
|
+
}
|
|
893
1152
|
function dateOrWeekday(ir, opts) {
|
|
894
1153
|
const pattern = ir.pattern;
|
|
895
|
-
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, opts);
|
|
1154
|
+
const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
|
|
896
1155
|
if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
|
|
897
1156
|
return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
|
|
898
1157
|
}
|
|
@@ -947,6 +1206,9 @@ function monthDatePhrase(ir, opts) {
|
|
|
947
1206
|
opts.style.ordinals ? getOrdinal : cardinalDay,
|
|
948
1207
|
opts
|
|
949
1208
|
);
|
|
1209
|
+
if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
|
|
1210
|
+
return "the " + getOrdinal(ir.pattern.date) + " of " + month;
|
|
1211
|
+
}
|
|
950
1212
|
return opts.style.dayFirst ? days + " " + month : month + " " + days;
|
|
951
1213
|
}
|
|
952
1214
|
function cardinalDay(value) {
|
|
@@ -958,6 +1220,19 @@ function monthScope(ir, opts) {
|
|
|
958
1220
|
}
|
|
959
1221
|
return " in " + monthName(ir, opts);
|
|
960
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
|
+
}
|
|
961
1236
|
function stepDates(dateField) {
|
|
962
1237
|
const parts = dateField.split("/");
|
|
963
1238
|
const interval = +parts[1];
|
|
@@ -994,11 +1269,21 @@ function oddEvenMonth(monthField) {
|
|
|
994
1269
|
}
|
|
995
1270
|
return start === "2" ? "every even-numbered month" : null;
|
|
996
1271
|
}
|
|
997
|
-
function weekdayPhrase(ir, opts) {
|
|
1272
|
+
function weekdayPhrase(ir, recurring, opts) {
|
|
998
1273
|
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
|
|
999
|
-
|
|
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) {
|
|
1000
1280
|
return getWeekday(value, opts);
|
|
1001
|
-
}
|
|
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";
|
|
1002
1287
|
}
|
|
1003
1288
|
function renderSegments(segments, word, opts) {
|
|
1004
1289
|
const pieces = [];
|
|
@@ -1022,7 +1307,7 @@ function applyYear(description, ir, opts) {
|
|
|
1022
1307
|
return description;
|
|
1023
1308
|
}
|
|
1024
1309
|
if (yearField.indexOf("/") !== -1) {
|
|
1025
|
-
return description + " " + stepYears(yearField, opts);
|
|
1310
|
+
return description + ", " + stepYears(yearField, opts);
|
|
1026
1311
|
}
|
|
1027
1312
|
const label = yearLabel(yearField, opts);
|
|
1028
1313
|
if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
|
|
@@ -1035,6 +1320,9 @@ function yearLabel(yearField, opts) {
|
|
|
1035
1320
|
if (yearField.indexOf(",") !== -1) {
|
|
1036
1321
|
return joinList(yearField.split(","), opts);
|
|
1037
1322
|
}
|
|
1323
|
+
if (yearField.indexOf("-") !== -1) {
|
|
1324
|
+
return yearField.split("-").join(through(opts));
|
|
1325
|
+
}
|
|
1038
1326
|
return yearField;
|
|
1039
1327
|
}
|
|
1040
1328
|
function stepYears(yearField, opts) {
|
|
@@ -1044,7 +1332,7 @@ function stepYears(yearField, opts) {
|
|
|
1044
1332
|
if (interval <= 1) {
|
|
1045
1333
|
return "every year";
|
|
1046
1334
|
}
|
|
1047
|
-
let phrase = "every " + getNumber(interval, opts) + " years";
|
|
1335
|
+
let phrase = interval === 2 ? "every other year" : "every " + getNumber(interval, opts) + " years";
|
|
1048
1336
|
if (start !== "*" && start !== "0") {
|
|
1049
1337
|
phrase += " from " + start;
|
|
1050
1338
|
}
|