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/cronli5.js
CHANGED
|
@@ -69,6 +69,21 @@ function isNonNegativeInteger(value) {
|
|
|
69
69
|
const digits = /^\d+$/;
|
|
70
70
|
return digits.test(value);
|
|
71
71
|
}
|
|
72
|
+
function arithmeticStep(values) {
|
|
73
|
+
if (values.length < 5) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const interval = values[1] - values[0];
|
|
77
|
+
if (interval < 2) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
81
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { start: values[0], interval, last: values[values.length - 1] };
|
|
86
|
+
}
|
|
72
87
|
function toFieldNumber(token, numberMap) {
|
|
73
88
|
return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
|
|
74
89
|
}
|
|
@@ -599,7 +614,7 @@ function planStandaloneSeconds(pattern, shapes) {
|
|
|
599
614
|
}
|
|
600
615
|
return { kind: "standaloneSeconds" };
|
|
601
616
|
}
|
|
602
|
-
function planMinutes(pattern, shapes, analyses,
|
|
617
|
+
function planMinutes(pattern, shapes, analyses, subMinuteSecond2 = false) {
|
|
603
618
|
if (shapes.minute === "step") {
|
|
604
619
|
return {
|
|
605
620
|
hours: planFrequencyHours(pattern, shapes, analyses),
|
|
@@ -622,7 +637,7 @@ function planMinutes(pattern, shapes, analyses, subMinuteSecond = false) {
|
|
|
622
637
|
return underStep;
|
|
623
638
|
}
|
|
624
639
|
if (pattern.hour === "*") {
|
|
625
|
-
return planMinutesUnderOpenHour(pattern, shapes,
|
|
640
|
+
return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond2);
|
|
626
641
|
}
|
|
627
642
|
}
|
|
628
643
|
function cleanHourStride(hourField) {
|
|
@@ -644,6 +659,9 @@ function planMinuteUnderHourStep(pattern, shapes) {
|
|
|
644
659
|
if (shapes.minute === "range") {
|
|
645
660
|
return { form: "range", kind: "minuteSpanAcrossHourStep" };
|
|
646
661
|
}
|
|
662
|
+
if (shapes.minute === "list" && cleanHourStride(pattern.hour)) {
|
|
663
|
+
return { form: "list", kind: "minuteSpanAcrossHourStep" };
|
|
664
|
+
}
|
|
647
665
|
return null;
|
|
648
666
|
}
|
|
649
667
|
function planFrequencyHours(pattern, shapes, analyses) {
|
|
@@ -692,7 +710,7 @@ function planMinutesAcrossHours(pattern, shapes) {
|
|
|
692
710
|
}
|
|
693
711
|
return null;
|
|
694
712
|
}
|
|
695
|
-
function planMinutesUnderOpenHour(pattern, shapes,
|
|
713
|
+
function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond2) {
|
|
696
714
|
if (shapes.minute === "range") {
|
|
697
715
|
return { kind: "rangeOfMinutes" };
|
|
698
716
|
}
|
|
@@ -702,16 +720,16 @@ function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond) {
|
|
|
702
720
|
if (pattern.minute === "*") {
|
|
703
721
|
return { kind: "everyMinute" };
|
|
704
722
|
}
|
|
705
|
-
if (pattern.minute !== "0" ||
|
|
723
|
+
if (pattern.minute !== "0" || subMinuteSecond2) {
|
|
706
724
|
return { kind: "singleMinute" };
|
|
707
725
|
}
|
|
708
726
|
}
|
|
709
|
-
function planHours(pattern, shapes, analyses,
|
|
710
|
-
const absorbsMinuteZero =
|
|
727
|
+
function planHours(pattern, shapes, analyses, subMinuteSecond2 = false) {
|
|
728
|
+
const absorbsMinuteZero = subMinuteSecond2 && pattern.minute === "0";
|
|
711
729
|
if (shapes.hour === "range" && !absorbsMinuteZero) {
|
|
712
730
|
return planHourRange(pattern, shapes, analyses);
|
|
713
731
|
}
|
|
714
|
-
if (shapes.hour === "step" && pattern.minute === "0" && !
|
|
732
|
+
if (shapes.hour === "step" && pattern.minute === "0" && !subMinuteSecond2) {
|
|
715
733
|
return { kind: "hourStep" };
|
|
716
734
|
}
|
|
717
735
|
if (pattern.hour === "*" && !absorbsMinuteZero) {
|
|
@@ -926,18 +944,34 @@ function renderSecondsWithinMinute(ir, plan, opts) {
|
|
|
926
944
|
}
|
|
927
945
|
return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
|
|
928
946
|
}
|
|
947
|
+
function composeHourCadence(ir, plan, opts) {
|
|
948
|
+
const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
|
|
949
|
+
if (!clockRest || ir.shapes.minute !== "single") {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
const minute = +ir.pattern.minute;
|
|
953
|
+
return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
|
|
954
|
+
}
|
|
955
|
+
function clockTimesConfinement(ir, rest, opts) {
|
|
956
|
+
if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
|
|
957
|
+
return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
|
|
958
|
+
}
|
|
959
|
+
return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
|
|
960
|
+
}
|
|
929
961
|
function renderComposeSeconds(ir, plan, opts) {
|
|
962
|
+
const cadence = composeHourCadence(ir, plan, opts);
|
|
963
|
+
if (cadence !== null) {
|
|
964
|
+
return cadence;
|
|
965
|
+
}
|
|
930
966
|
if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
|
|
931
|
-
|
|
932
|
-
if (+minute === 0) {
|
|
933
|
-
return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
|
|
934
|
-
}
|
|
935
|
-
return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
|
|
967
|
+
return clockTimesConfinement(ir, plan.rest, opts);
|
|
936
968
|
}
|
|
937
969
|
if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
|
|
938
970
|
return "every second of every other minute" + trailingQualifier(ir, opts);
|
|
939
971
|
}
|
|
940
|
-
|
|
972
|
+
const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
|
|
973
|
+
const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
|
|
974
|
+
return lead + render(ir, plan.rest, opts);
|
|
941
975
|
}
|
|
942
976
|
function durationHours(ir, plan, opts) {
|
|
943
977
|
const hours = plan.times.map(function clock(time) {
|
|
@@ -959,6 +993,9 @@ function clockTimesOf(ir, plan, opts) {
|
|
|
959
993
|
return joinList(times, opts) + (trail && ", " + trail);
|
|
960
994
|
}
|
|
961
995
|
function secondsLeadClause(ir, opts) {
|
|
996
|
+
return secondsClause(ir, "minute", opts);
|
|
997
|
+
}
|
|
998
|
+
function secondsClause(ir, anchor, opts) {
|
|
962
999
|
const secondField = ir.pattern.second;
|
|
963
1000
|
const shape = ir.shapes.second;
|
|
964
1001
|
if (secondField === "*") {
|
|
@@ -968,22 +1005,27 @@ function secondsLeadClause(ir, opts) {
|
|
|
968
1005
|
return stepCycle60(
|
|
969
1006
|
ir.analyses.segments.second[0],
|
|
970
1007
|
"second",
|
|
971
|
-
|
|
1008
|
+
anchor,
|
|
972
1009
|
opts
|
|
973
1010
|
);
|
|
974
1011
|
}
|
|
975
1012
|
if (shape === "range") {
|
|
976
1013
|
const bounds = secondField.split("-");
|
|
977
1014
|
const num = seriesNumber(bounds, opts);
|
|
978
|
-
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the
|
|
1015
|
+
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
979
1016
|
}
|
|
980
1017
|
if (shape === "single") {
|
|
981
|
-
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the
|
|
1018
|
+
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
|
|
982
1019
|
}
|
|
983
|
-
return
|
|
1020
|
+
return strideFromSegments(
|
|
1021
|
+
ir.analyses.segments.second,
|
|
1022
|
+
"second",
|
|
1023
|
+
anchor,
|
|
1024
|
+
opts
|
|
1025
|
+
) ?? listPastThe(
|
|
984
1026
|
segmentWords(ir.analyses.segments.second, opts),
|
|
985
1027
|
"second",
|
|
986
|
-
|
|
1028
|
+
anchor,
|
|
987
1029
|
opts
|
|
988
1030
|
);
|
|
989
1031
|
}
|
|
@@ -998,12 +1040,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
|
|
|
998
1040
|
return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
|
|
999
1041
|
}
|
|
1000
1042
|
function renderMultipleMinutes(ir, plan, opts) {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
"hour",
|
|
1043
|
+
const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
|
|
1044
|
+
return (stride ?? listPastThe(segmentWords(
|
|
1045
|
+
ir.analyses.segments.minute,
|
|
1005
1046
|
opts
|
|
1006
|
-
) + trailingQualifier(ir, opts);
|
|
1047
|
+
), "minute", "hour", opts)) + trailingQualifier(ir, opts);
|
|
1007
1048
|
}
|
|
1008
1049
|
function renderMinuteFrequency(ir, plan, opts) {
|
|
1009
1050
|
let phrase = stepCycle60(
|
|
@@ -1013,7 +1054,8 @@ function renderMinuteFrequency(ir, plan, opts) {
|
|
|
1013
1054
|
opts
|
|
1014
1055
|
);
|
|
1015
1056
|
if (plan.hours.kind === "during") {
|
|
1016
|
-
|
|
1057
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1058
|
+
phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
|
|
1017
1059
|
} else if (plan.hours.kind === "window") {
|
|
1018
1060
|
phrase += " " + hourWindow(plan.hours, opts);
|
|
1019
1061
|
} else if (plan.hours.kind === "step") {
|
|
@@ -1028,19 +1070,27 @@ function renderMinuteSpanInHour(ir, plan, opts) {
|
|
|
1028
1070
|
return "every minute from " + getTime({ hour: plan.hour, minute: plan.span[0] }, opts) + through(opts) + getTime({ hour: plan.hour, minute: plan.span[1] }, opts) + trailingQualifier(ir, opts);
|
|
1029
1071
|
}
|
|
1030
1072
|
function renderMinutesAcrossHours(ir, plan, opts) {
|
|
1073
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1031
1074
|
if (plan.form === "wildcard") {
|
|
1075
|
+
if (cadence !== null) {
|
|
1076
|
+
return "every minute, " + cadence + trailingQualifier(ir, opts);
|
|
1077
|
+
}
|
|
1032
1078
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1033
1079
|
}
|
|
1034
|
-
const times = hourTimesFromPlan(ir, plan.times, true, opts);
|
|
1035
1080
|
const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
|
|
1036
|
-
// The 'list' form is a minute list, which has segments
|
|
1037
|
-
|
|
1081
|
+
// The 'list' form is a minute list, which has segments; an offset/uneven
|
|
1082
|
+
// step enumerated to that list reads as a stride.
|
|
1083
|
+
strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1038
1084
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1039
1085
|
"minute",
|
|
1040
1086
|
"hour",
|
|
1041
1087
|
opts
|
|
1042
1088
|
)
|
|
1043
1089
|
);
|
|
1090
|
+
if (cadence !== null) {
|
|
1091
|
+
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
1092
|
+
}
|
|
1093
|
+
const times = hourTimesFromPlan(ir, plan.times, true, opts);
|
|
1044
1094
|
return lead + ", at " + times + trailingQualifier(ir, opts);
|
|
1045
1095
|
}
|
|
1046
1096
|
var stepOrdinals = {
|
|
@@ -1061,7 +1111,14 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1061
1111
|
if (plan.form === "wildcard") {
|
|
1062
1112
|
return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
|
|
1063
1113
|
}
|
|
1064
|
-
|
|
1114
|
+
const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1115
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
1116
|
+
"minute",
|
|
1117
|
+
"hour",
|
|
1118
|
+
opts
|
|
1119
|
+
) : minuteRangeLead(ir.pattern.minute, opts);
|
|
1120
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1121
|
+
return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
|
|
1065
1122
|
}
|
|
1066
1123
|
function minuteRangeLead(minuteField, opts) {
|
|
1067
1124
|
const bounds = minuteField.split("-");
|
|
@@ -1085,7 +1142,12 @@ function rangeMinuteLead(ir, opts) {
|
|
|
1085
1142
|
if (ir.pattern.minute === "0") {
|
|
1086
1143
|
return "every hour";
|
|
1087
1144
|
}
|
|
1088
|
-
return
|
|
1145
|
+
return strideFromSegments(
|
|
1146
|
+
ir.analyses.segments.minute,
|
|
1147
|
+
"minute",
|
|
1148
|
+
"hour",
|
|
1149
|
+
opts
|
|
1150
|
+
) ?? listPastThe(
|
|
1089
1151
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1090
1152
|
"minute",
|
|
1091
1153
|
"hour",
|
|
@@ -1093,6 +1155,10 @@ function rangeMinuteLead(ir, opts) {
|
|
|
1093
1155
|
);
|
|
1094
1156
|
}
|
|
1095
1157
|
function renderHourStep(ir, plan, opts) {
|
|
1158
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1159
|
+
if (cadence !== null) {
|
|
1160
|
+
return cadence + trailingQualifier(ir, opts);
|
|
1161
|
+
}
|
|
1096
1162
|
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
1097
1163
|
}
|
|
1098
1164
|
function boundedWindow(plan) {
|
|
@@ -1102,6 +1168,13 @@ function hourWindow(window, opts) {
|
|
|
1102
1168
|
return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
|
|
1103
1169
|
}
|
|
1104
1170
|
function renderClockTimes(ir, plan, opts) {
|
|
1171
|
+
if (ir.shapes.minute === "single") {
|
|
1172
|
+
const minute = +ir.pattern.minute;
|
|
1173
|
+
const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
|
|
1174
|
+
if (cadence !== null) {
|
|
1175
|
+
return cadence;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1105
1178
|
const plain = mixedTwelve(plan.times);
|
|
1106
1179
|
const times = plan.times.map(function clock(time) {
|
|
1107
1180
|
return getTime({
|
|
@@ -1115,6 +1188,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1115
1188
|
}
|
|
1116
1189
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1117
1190
|
if (plan.fold) {
|
|
1191
|
+
const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
|
|
1192
|
+
if (cadence2 !== null) {
|
|
1193
|
+
return cadence2;
|
|
1194
|
+
}
|
|
1118
1195
|
const hasRange = ir.analyses.segments.hour.some(function range(segment) {
|
|
1119
1196
|
return segment.kind === "range";
|
|
1120
1197
|
});
|
|
@@ -1124,15 +1201,18 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1124
1201
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1125
1202
|
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1126
1203
|
}
|
|
1127
|
-
const
|
|
1128
|
-
// The non-fold branch is a minute list, which has segments.
|
|
1129
|
-
|
|
1204
|
+
const minuteLead = (
|
|
1205
|
+
// The non-fold branch is a minute list, which has segments. An
|
|
1206
|
+
// offset/uneven step enumerated to that list reads as a stride.
|
|
1207
|
+
strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1130
1208
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1131
1209
|
"minute",
|
|
1132
1210
|
"hour",
|
|
1133
1211
|
opts
|
|
1134
|
-
)
|
|
1212
|
+
)
|
|
1135
1213
|
);
|
|
1214
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1215
|
+
const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
|
|
1136
1216
|
return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
|
|
1137
1217
|
}
|
|
1138
1218
|
function foldedHourWindows(ir, plan, opts) {
|
|
@@ -1179,24 +1259,50 @@ var renderers = {
|
|
|
1179
1259
|
singleMinute: renderSingleMinute,
|
|
1180
1260
|
standaloneSeconds: renderStandaloneSeconds
|
|
1181
1261
|
};
|
|
1262
|
+
function renderStride(stride, opts) {
|
|
1263
|
+
const { interval, start, last, cycle, unit, anchor } = stride;
|
|
1264
|
+
const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
|
|
1265
|
+
const tiles = cycle % interval === 0;
|
|
1266
|
+
if (start === 0 && tiles) {
|
|
1267
|
+
return cadence;
|
|
1268
|
+
}
|
|
1269
|
+
if (start < interval && tiles) {
|
|
1270
|
+
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1271
|
+
}
|
|
1272
|
+
const num = seriesNumber([start, last], opts);
|
|
1273
|
+
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1274
|
+
}
|
|
1275
|
+
function singleValues(segments) {
|
|
1276
|
+
const values = [];
|
|
1277
|
+
for (const segment of segments) {
|
|
1278
|
+
if (segment.kind !== "single") {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
values.push(+segment.value);
|
|
1282
|
+
}
|
|
1283
|
+
return values;
|
|
1284
|
+
}
|
|
1285
|
+
function strideFromSegments(segments, unit, anchor, opts) {
|
|
1286
|
+
const values = singleValues(segments);
|
|
1287
|
+
const step = values && arithmeticStep(values);
|
|
1288
|
+
return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
|
|
1289
|
+
}
|
|
1182
1290
|
function stepCycle60(segment, unit, anchor, opts) {
|
|
1183
1291
|
if (segment.startToken.indexOf("-") !== -1) {
|
|
1184
1292
|
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1185
1293
|
}
|
|
1186
1294
|
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (segment.fires.length <= 3) {
|
|
1190
|
-
return listPastThe(
|
|
1191
|
-
numberWords(segment.fires, opts),
|
|
1192
|
-
unit,
|
|
1193
|
-
anchor,
|
|
1194
|
-
opts
|
|
1195
|
-
);
|
|
1196
|
-
}
|
|
1197
|
-
return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1295
|
+
if (start !== 0 && segment.fires.length <= 3) {
|
|
1296
|
+
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1198
1297
|
}
|
|
1199
|
-
return
|
|
1298
|
+
return renderStride({
|
|
1299
|
+
interval: segment.interval,
|
|
1300
|
+
start,
|
|
1301
|
+
last: segment.fires[segment.fires.length - 1],
|
|
1302
|
+
cycle: 60,
|
|
1303
|
+
unit,
|
|
1304
|
+
anchor
|
|
1305
|
+
}, opts);
|
|
1200
1306
|
}
|
|
1201
1307
|
function stepHours(segment, opts) {
|
|
1202
1308
|
if (segment.startToken.indexOf("-") !== -1) {
|
|
@@ -1212,6 +1318,141 @@ function stepHours(segment, opts) {
|
|
|
1212
1318
|
}
|
|
1213
1319
|
return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
|
|
1214
1320
|
}
|
|
1321
|
+
function hourStrideCadence(stride, opts) {
|
|
1322
|
+
const { start, interval, last } = stride;
|
|
1323
|
+
const cadence = "every " + getNumber(interval, opts) + " hours";
|
|
1324
|
+
const tiles = 24 % interval === 0;
|
|
1325
|
+
if (start === 0 && tiles) {
|
|
1326
|
+
return cadence;
|
|
1327
|
+
}
|
|
1328
|
+
if (start < interval && tiles) {
|
|
1329
|
+
return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
|
|
1330
|
+
}
|
|
1331
|
+
return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
|
|
1332
|
+
}
|
|
1333
|
+
function offsetCleanStride(stride) {
|
|
1334
|
+
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
1335
|
+
}
|
|
1336
|
+
function unevenHourCadence(ir, opts) {
|
|
1337
|
+
const stride = hourStride(ir);
|
|
1338
|
+
if (!stride || offsetCleanStride(stride)) {
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
return hourStrideCadence(stride, opts);
|
|
1342
|
+
}
|
|
1343
|
+
function hourListStride(values) {
|
|
1344
|
+
if (values.length < 2) {
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
const interval = values[1] - values[0];
|
|
1348
|
+
if (interval < 2) {
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
1352
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (values[0] !== 0 && values.length < 5) {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
return { interval, last: values[values.length - 1], start: values[0] };
|
|
1360
|
+
}
|
|
1361
|
+
function hourStride(ir) {
|
|
1362
|
+
const segments = ir.analyses.segments.hour;
|
|
1363
|
+
if (segments.length === 1 && segments[0].kind === "step") {
|
|
1364
|
+
const segment = segments[0];
|
|
1365
|
+
if (segment.fires.length < 2) {
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
|
|
1369
|
+
return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
|
|
1370
|
+
}
|
|
1371
|
+
const values = singleValues(segments);
|
|
1372
|
+
return values && hourListStride(values);
|
|
1373
|
+
}
|
|
1374
|
+
function subMinuteSecond(ir) {
|
|
1375
|
+
return ir.pattern.second === "*" || ir.shapes.second === "step";
|
|
1376
|
+
}
|
|
1377
|
+
function hourCadenceLead(ir, minute, opts) {
|
|
1378
|
+
if (minute === 0) {
|
|
1379
|
+
if (subMinuteSecond(ir)) {
|
|
1380
|
+
return secondsClause(ir, "minute", opts) + " for one minute";
|
|
1381
|
+
}
|
|
1382
|
+
return secondsClause(ir, "hour", opts);
|
|
1383
|
+
}
|
|
1384
|
+
const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
|
|
1385
|
+
if (ir.pattern.second === "0") {
|
|
1386
|
+
return minutePhrase;
|
|
1387
|
+
}
|
|
1388
|
+
return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
|
|
1389
|
+
}
|
|
1390
|
+
function hourCadence(ir, minute, opts) {
|
|
1391
|
+
const stride = hourStride(ir);
|
|
1392
|
+
if (!stride) {
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
1396
|
+
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
1400
|
+
if (confinement) {
|
|
1401
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
|
|
1402
|
+
}
|
|
1403
|
+
if (minute === 0 && ir.pattern.second === "0") {
|
|
1404
|
+
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1405
|
+
}
|
|
1406
|
+
return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1407
|
+
}
|
|
1408
|
+
function cleanStrideSegment(ir) {
|
|
1409
|
+
const segments = ir.analyses.segments.hour;
|
|
1410
|
+
const segment = segments.length === 1 && segments[0];
|
|
1411
|
+
if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
return segment;
|
|
1415
|
+
}
|
|
1416
|
+
function hasHourWindow(ir) {
|
|
1417
|
+
return ir.analyses.segments.hour.some(function range(segment) {
|
|
1418
|
+
return segment.kind === "range";
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function hourRangeWindowTail(ir, opts) {
|
|
1422
|
+
const windows = [];
|
|
1423
|
+
const singles = [];
|
|
1424
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1425
|
+
if (segment.kind === "range") {
|
|
1426
|
+
windows.push("from " + getTime(
|
|
1427
|
+
{ hour: +segment.bounds[0], minute: 0 },
|
|
1428
|
+
opts
|
|
1429
|
+
) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
|
|
1430
|
+
} else if (segment.kind === "step") {
|
|
1431
|
+
singles.push(...segment.fires);
|
|
1432
|
+
} else {
|
|
1433
|
+
singles.push(+segment.value);
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
let phrase = "every hour " + joinList(windows, opts);
|
|
1437
|
+
if (singles.length) {
|
|
1438
|
+
phrase += " and at " + joinList(singles.map(function time(hour) {
|
|
1439
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
1440
|
+
}), opts);
|
|
1441
|
+
}
|
|
1442
|
+
return phrase;
|
|
1443
|
+
}
|
|
1444
|
+
function hourRangeCadence(ir, minute, opts) {
|
|
1445
|
+
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
if (ir.pattern.second === "0") {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
if (subMinuteSecond(ir)) {
|
|
1452
|
+
return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1453
|
+
}
|
|
1454
|
+
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1455
|
+
}
|
|
1215
1456
|
function seriesNumber(values, opts) {
|
|
1216
1457
|
const anyBig = values.some(function big(v) {
|
|
1217
1458
|
return +v > 10;
|