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.cjs
CHANGED
|
@@ -95,6 +95,21 @@ function isNonNegativeInteger(value) {
|
|
|
95
95
|
const digits = /^\d+$/;
|
|
96
96
|
return digits.test(value);
|
|
97
97
|
}
|
|
98
|
+
function arithmeticStep(values) {
|
|
99
|
+
if (values.length < 5) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const interval = values[1] - values[0];
|
|
103
|
+
if (interval < 2) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
107
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { start: values[0], interval, last: values[values.length - 1] };
|
|
112
|
+
}
|
|
98
113
|
function toFieldNumber(token, numberMap) {
|
|
99
114
|
return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
|
|
100
115
|
}
|
|
@@ -625,7 +640,7 @@ function planStandaloneSeconds(pattern, shapes) {
|
|
|
625
640
|
}
|
|
626
641
|
return { kind: "standaloneSeconds" };
|
|
627
642
|
}
|
|
628
|
-
function planMinutes(pattern, shapes, analyses,
|
|
643
|
+
function planMinutes(pattern, shapes, analyses, subMinuteSecond2 = false) {
|
|
629
644
|
if (shapes.minute === "step") {
|
|
630
645
|
return {
|
|
631
646
|
hours: planFrequencyHours(pattern, shapes, analyses),
|
|
@@ -648,7 +663,7 @@ function planMinutes(pattern, shapes, analyses, subMinuteSecond = false) {
|
|
|
648
663
|
return underStep;
|
|
649
664
|
}
|
|
650
665
|
if (pattern.hour === "*") {
|
|
651
|
-
return planMinutesUnderOpenHour(pattern, shapes,
|
|
666
|
+
return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond2);
|
|
652
667
|
}
|
|
653
668
|
}
|
|
654
669
|
function cleanHourStride(hourField) {
|
|
@@ -670,6 +685,9 @@ function planMinuteUnderHourStep(pattern, shapes) {
|
|
|
670
685
|
if (shapes.minute === "range") {
|
|
671
686
|
return { form: "range", kind: "minuteSpanAcrossHourStep" };
|
|
672
687
|
}
|
|
688
|
+
if (shapes.minute === "list" && cleanHourStride(pattern.hour)) {
|
|
689
|
+
return { form: "list", kind: "minuteSpanAcrossHourStep" };
|
|
690
|
+
}
|
|
673
691
|
return null;
|
|
674
692
|
}
|
|
675
693
|
function planFrequencyHours(pattern, shapes, analyses) {
|
|
@@ -718,7 +736,7 @@ function planMinutesAcrossHours(pattern, shapes) {
|
|
|
718
736
|
}
|
|
719
737
|
return null;
|
|
720
738
|
}
|
|
721
|
-
function planMinutesUnderOpenHour(pattern, shapes,
|
|
739
|
+
function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond2) {
|
|
722
740
|
if (shapes.minute === "range") {
|
|
723
741
|
return { kind: "rangeOfMinutes" };
|
|
724
742
|
}
|
|
@@ -728,16 +746,16 @@ function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond) {
|
|
|
728
746
|
if (pattern.minute === "*") {
|
|
729
747
|
return { kind: "everyMinute" };
|
|
730
748
|
}
|
|
731
|
-
if (pattern.minute !== "0" ||
|
|
749
|
+
if (pattern.minute !== "0" || subMinuteSecond2) {
|
|
732
750
|
return { kind: "singleMinute" };
|
|
733
751
|
}
|
|
734
752
|
}
|
|
735
|
-
function planHours(pattern, shapes, analyses,
|
|
736
|
-
const absorbsMinuteZero =
|
|
753
|
+
function planHours(pattern, shapes, analyses, subMinuteSecond2 = false) {
|
|
754
|
+
const absorbsMinuteZero = subMinuteSecond2 && pattern.minute === "0";
|
|
737
755
|
if (shapes.hour === "range" && !absorbsMinuteZero) {
|
|
738
756
|
return planHourRange(pattern, shapes, analyses);
|
|
739
757
|
}
|
|
740
|
-
if (shapes.hour === "step" && pattern.minute === "0" && !
|
|
758
|
+
if (shapes.hour === "step" && pattern.minute === "0" && !subMinuteSecond2) {
|
|
741
759
|
return { kind: "hourStep" };
|
|
742
760
|
}
|
|
743
761
|
if (pattern.hour === "*" && !absorbsMinuteZero) {
|
|
@@ -952,18 +970,34 @@ function renderSecondsWithinMinute(ir, plan, opts) {
|
|
|
952
970
|
}
|
|
953
971
|
return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
|
|
954
972
|
}
|
|
973
|
+
function composeHourCadence(ir, plan, opts) {
|
|
974
|
+
const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
|
|
975
|
+
if (!clockRest || ir.shapes.minute !== "single") {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
const minute = +ir.pattern.minute;
|
|
979
|
+
return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
|
|
980
|
+
}
|
|
981
|
+
function clockTimesConfinement(ir, rest, opts) {
|
|
982
|
+
if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
|
|
983
|
+
return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
|
|
984
|
+
}
|
|
985
|
+
return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
|
|
986
|
+
}
|
|
955
987
|
function renderComposeSeconds(ir, plan, opts) {
|
|
988
|
+
const cadence = composeHourCadence(ir, plan, opts);
|
|
989
|
+
if (cadence !== null) {
|
|
990
|
+
return cadence;
|
|
991
|
+
}
|
|
956
992
|
if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
|
|
957
|
-
|
|
958
|
-
if (+minute === 0) {
|
|
959
|
-
return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
|
|
960
|
-
}
|
|
961
|
-
return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
|
|
993
|
+
return clockTimesConfinement(ir, plan.rest, opts);
|
|
962
994
|
}
|
|
963
995
|
if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
|
|
964
996
|
return "every second of every other minute" + trailingQualifier(ir, opts);
|
|
965
997
|
}
|
|
966
|
-
|
|
998
|
+
const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
|
|
999
|
+
const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
|
|
1000
|
+
return lead + render(ir, plan.rest, opts);
|
|
967
1001
|
}
|
|
968
1002
|
function durationHours(ir, plan, opts) {
|
|
969
1003
|
const hours = plan.times.map(function clock(time) {
|
|
@@ -985,6 +1019,9 @@ function clockTimesOf(ir, plan, opts) {
|
|
|
985
1019
|
return joinList(times, opts) + (trail && ", " + trail);
|
|
986
1020
|
}
|
|
987
1021
|
function secondsLeadClause(ir, opts) {
|
|
1022
|
+
return secondsClause(ir, "minute", opts);
|
|
1023
|
+
}
|
|
1024
|
+
function secondsClause(ir, anchor, opts) {
|
|
988
1025
|
const secondField = ir.pattern.second;
|
|
989
1026
|
const shape = ir.shapes.second;
|
|
990
1027
|
if (secondField === "*") {
|
|
@@ -994,22 +1031,27 @@ function secondsLeadClause(ir, opts) {
|
|
|
994
1031
|
return stepCycle60(
|
|
995
1032
|
ir.analyses.segments.second[0],
|
|
996
1033
|
"second",
|
|
997
|
-
|
|
1034
|
+
anchor,
|
|
998
1035
|
opts
|
|
999
1036
|
);
|
|
1000
1037
|
}
|
|
1001
1038
|
if (shape === "range") {
|
|
1002
1039
|
const bounds = secondField.split("-");
|
|
1003
1040
|
const num = seriesNumber(bounds, opts);
|
|
1004
|
-
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the
|
|
1041
|
+
return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
|
|
1005
1042
|
}
|
|
1006
1043
|
if (shape === "single") {
|
|
1007
|
-
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the
|
|
1044
|
+
return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
|
|
1008
1045
|
}
|
|
1009
|
-
return
|
|
1046
|
+
return strideFromSegments(
|
|
1047
|
+
ir.analyses.segments.second,
|
|
1048
|
+
"second",
|
|
1049
|
+
anchor,
|
|
1050
|
+
opts
|
|
1051
|
+
) ?? listPastThe(
|
|
1010
1052
|
segmentWords(ir.analyses.segments.second, opts),
|
|
1011
1053
|
"second",
|
|
1012
|
-
|
|
1054
|
+
anchor,
|
|
1013
1055
|
opts
|
|
1014
1056
|
);
|
|
1015
1057
|
}
|
|
@@ -1024,12 +1066,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
|
|
|
1024
1066
|
return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
|
|
1025
1067
|
}
|
|
1026
1068
|
function renderMultipleMinutes(ir, plan, opts) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
"hour",
|
|
1069
|
+
const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
|
|
1070
|
+
return (stride ?? listPastThe(segmentWords(
|
|
1071
|
+
ir.analyses.segments.minute,
|
|
1031
1072
|
opts
|
|
1032
|
-
) + trailingQualifier(ir, opts);
|
|
1073
|
+
), "minute", "hour", opts)) + trailingQualifier(ir, opts);
|
|
1033
1074
|
}
|
|
1034
1075
|
function renderMinuteFrequency(ir, plan, opts) {
|
|
1035
1076
|
let phrase = stepCycle60(
|
|
@@ -1039,7 +1080,8 @@ function renderMinuteFrequency(ir, plan, opts) {
|
|
|
1039
1080
|
opts
|
|
1040
1081
|
);
|
|
1041
1082
|
if (plan.hours.kind === "during") {
|
|
1042
|
-
|
|
1083
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1084
|
+
phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
|
|
1043
1085
|
} else if (plan.hours.kind === "window") {
|
|
1044
1086
|
phrase += " " + hourWindow(plan.hours, opts);
|
|
1045
1087
|
} else if (plan.hours.kind === "step") {
|
|
@@ -1054,19 +1096,27 @@ function renderMinuteSpanInHour(ir, plan, opts) {
|
|
|
1054
1096
|
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);
|
|
1055
1097
|
}
|
|
1056
1098
|
function renderMinutesAcrossHours(ir, plan, opts) {
|
|
1099
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1057
1100
|
if (plan.form === "wildcard") {
|
|
1101
|
+
if (cadence !== null) {
|
|
1102
|
+
return "every minute, " + cadence + trailingQualifier(ir, opts);
|
|
1103
|
+
}
|
|
1058
1104
|
return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1059
1105
|
}
|
|
1060
|
-
const times = hourTimesFromPlan(ir, plan.times, true, opts);
|
|
1061
1106
|
const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
|
|
1062
|
-
// The 'list' form is a minute list, which has segments
|
|
1063
|
-
|
|
1107
|
+
// The 'list' form is a minute list, which has segments; an offset/uneven
|
|
1108
|
+
// step enumerated to that list reads as a stride.
|
|
1109
|
+
strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1064
1110
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1065
1111
|
"minute",
|
|
1066
1112
|
"hour",
|
|
1067
1113
|
opts
|
|
1068
1114
|
)
|
|
1069
1115
|
);
|
|
1116
|
+
if (cadence !== null) {
|
|
1117
|
+
return lead + ", " + cadence + trailingQualifier(ir, opts);
|
|
1118
|
+
}
|
|
1119
|
+
const times = hourTimesFromPlan(ir, plan.times, true, opts);
|
|
1070
1120
|
return lead + ", at " + times + trailingQualifier(ir, opts);
|
|
1071
1121
|
}
|
|
1072
1122
|
var stepOrdinals = {
|
|
@@ -1087,7 +1137,14 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
|
|
|
1087
1137
|
if (plan.form === "wildcard") {
|
|
1088
1138
|
return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
|
|
1089
1139
|
}
|
|
1090
|
-
|
|
1140
|
+
const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1141
|
+
segmentWords(ir.analyses.segments.minute, opts),
|
|
1142
|
+
"minute",
|
|
1143
|
+
"hour",
|
|
1144
|
+
opts
|
|
1145
|
+
) : minuteRangeLead(ir.pattern.minute, opts);
|
|
1146
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1147
|
+
return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
|
|
1091
1148
|
}
|
|
1092
1149
|
function minuteRangeLead(minuteField, opts) {
|
|
1093
1150
|
const bounds = minuteField.split("-");
|
|
@@ -1111,7 +1168,12 @@ function rangeMinuteLead(ir, opts) {
|
|
|
1111
1168
|
if (ir.pattern.minute === "0") {
|
|
1112
1169
|
return "every hour";
|
|
1113
1170
|
}
|
|
1114
|
-
return
|
|
1171
|
+
return strideFromSegments(
|
|
1172
|
+
ir.analyses.segments.minute,
|
|
1173
|
+
"minute",
|
|
1174
|
+
"hour",
|
|
1175
|
+
opts
|
|
1176
|
+
) ?? listPastThe(
|
|
1115
1177
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1116
1178
|
"minute",
|
|
1117
1179
|
"hour",
|
|
@@ -1119,6 +1181,10 @@ function rangeMinuteLead(ir, opts) {
|
|
|
1119
1181
|
);
|
|
1120
1182
|
}
|
|
1121
1183
|
function renderHourStep(ir, plan, opts) {
|
|
1184
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1185
|
+
if (cadence !== null) {
|
|
1186
|
+
return cadence + trailingQualifier(ir, opts);
|
|
1187
|
+
}
|
|
1122
1188
|
return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
|
|
1123
1189
|
}
|
|
1124
1190
|
function boundedWindow(plan) {
|
|
@@ -1128,6 +1194,13 @@ function hourWindow(window, opts) {
|
|
|
1128
1194
|
return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
|
|
1129
1195
|
}
|
|
1130
1196
|
function renderClockTimes(ir, plan, opts) {
|
|
1197
|
+
if (ir.shapes.minute === "single") {
|
|
1198
|
+
const minute = +ir.pattern.minute;
|
|
1199
|
+
const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
|
|
1200
|
+
if (cadence !== null) {
|
|
1201
|
+
return cadence;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1131
1204
|
const plain = mixedTwelve(plan.times);
|
|
1132
1205
|
const times = plan.times.map(function clock(time) {
|
|
1133
1206
|
return getTime({
|
|
@@ -1141,6 +1214,10 @@ function renderClockTimes(ir, plan, opts) {
|
|
|
1141
1214
|
}
|
|
1142
1215
|
function renderCompactClockTimes(ir, plan, opts) {
|
|
1143
1216
|
if (plan.fold) {
|
|
1217
|
+
const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
|
|
1218
|
+
if (cadence2 !== null) {
|
|
1219
|
+
return cadence2;
|
|
1220
|
+
}
|
|
1144
1221
|
const hasRange = ir.analyses.segments.hour.some(function range(segment) {
|
|
1145
1222
|
return segment.kind === "range";
|
|
1146
1223
|
});
|
|
@@ -1150,15 +1227,18 @@ function renderCompactClockTimes(ir, plan, opts) {
|
|
|
1150
1227
|
const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
|
|
1151
1228
|
return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
|
|
1152
1229
|
}
|
|
1153
|
-
const
|
|
1154
|
-
// The non-fold branch is a minute list, which has segments.
|
|
1155
|
-
|
|
1230
|
+
const minuteLead = (
|
|
1231
|
+
// The non-fold branch is a minute list, which has segments. An
|
|
1232
|
+
// offset/uneven step enumerated to that list reads as a stride.
|
|
1233
|
+
strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
|
|
1156
1234
|
segmentWords(ir.analyses.segments.minute, opts),
|
|
1157
1235
|
"minute",
|
|
1158
1236
|
"hour",
|
|
1159
1237
|
opts
|
|
1160
|
-
)
|
|
1238
|
+
)
|
|
1161
1239
|
);
|
|
1240
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
1241
|
+
const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
|
|
1162
1242
|
return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
|
|
1163
1243
|
}
|
|
1164
1244
|
function foldedHourWindows(ir, plan, opts) {
|
|
@@ -1205,24 +1285,50 @@ var renderers = {
|
|
|
1205
1285
|
singleMinute: renderSingleMinute,
|
|
1206
1286
|
standaloneSeconds: renderStandaloneSeconds
|
|
1207
1287
|
};
|
|
1288
|
+
function renderStride(stride, opts) {
|
|
1289
|
+
const { interval, start, last, cycle, unit, anchor } = stride;
|
|
1290
|
+
const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
|
|
1291
|
+
const tiles = cycle % interval === 0;
|
|
1292
|
+
if (start === 0 && tiles) {
|
|
1293
|
+
return cadence;
|
|
1294
|
+
}
|
|
1295
|
+
if (start < interval && tiles) {
|
|
1296
|
+
return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1297
|
+
}
|
|
1298
|
+
const num = seriesNumber([start, last], opts);
|
|
1299
|
+
return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
|
|
1300
|
+
}
|
|
1301
|
+
function singleValues(segments) {
|
|
1302
|
+
const values = [];
|
|
1303
|
+
for (const segment of segments) {
|
|
1304
|
+
if (segment.kind !== "single") {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
values.push(+segment.value);
|
|
1308
|
+
}
|
|
1309
|
+
return values;
|
|
1310
|
+
}
|
|
1311
|
+
function strideFromSegments(segments, unit, anchor, opts) {
|
|
1312
|
+
const values = singleValues(segments);
|
|
1313
|
+
const step = values && arithmeticStep(values);
|
|
1314
|
+
return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
|
|
1315
|
+
}
|
|
1208
1316
|
function stepCycle60(segment, unit, anchor, opts) {
|
|
1209
1317
|
if (segment.startToken.indexOf("-") !== -1) {
|
|
1210
1318
|
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1211
1319
|
}
|
|
1212
1320
|
const start = segment.startToken === "*" ? 0 : +segment.startToken;
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
if (segment.fires.length <= 3) {
|
|
1216
|
-
return listPastThe(
|
|
1217
|
-
numberWords(segment.fires, opts),
|
|
1218
|
-
unit,
|
|
1219
|
-
anchor,
|
|
1220
|
-
opts
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
|
|
1321
|
+
if (start !== 0 && segment.fires.length <= 3) {
|
|
1322
|
+
return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
|
|
1224
1323
|
}
|
|
1225
|
-
return
|
|
1324
|
+
return renderStride({
|
|
1325
|
+
interval: segment.interval,
|
|
1326
|
+
start,
|
|
1327
|
+
last: segment.fires[segment.fires.length - 1],
|
|
1328
|
+
cycle: 60,
|
|
1329
|
+
unit,
|
|
1330
|
+
anchor
|
|
1331
|
+
}, opts);
|
|
1226
1332
|
}
|
|
1227
1333
|
function stepHours(segment, opts) {
|
|
1228
1334
|
if (segment.startToken.indexOf("-") !== -1) {
|
|
@@ -1238,6 +1344,141 @@ function stepHours(segment, opts) {
|
|
|
1238
1344
|
}
|
|
1239
1345
|
return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
|
|
1240
1346
|
}
|
|
1347
|
+
function hourStrideCadence(stride, opts) {
|
|
1348
|
+
const { start, interval, last } = stride;
|
|
1349
|
+
const cadence = "every " + getNumber(interval, opts) + " hours";
|
|
1350
|
+
const tiles = 24 % interval === 0;
|
|
1351
|
+
if (start === 0 && tiles) {
|
|
1352
|
+
return cadence;
|
|
1353
|
+
}
|
|
1354
|
+
if (start < interval && tiles) {
|
|
1355
|
+
return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
|
|
1356
|
+
}
|
|
1357
|
+
return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
|
|
1358
|
+
}
|
|
1359
|
+
function offsetCleanStride(stride) {
|
|
1360
|
+
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
1361
|
+
}
|
|
1362
|
+
function unevenHourCadence(ir, opts) {
|
|
1363
|
+
const stride = hourStride(ir);
|
|
1364
|
+
if (!stride || offsetCleanStride(stride)) {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
return hourStrideCadence(stride, opts);
|
|
1368
|
+
}
|
|
1369
|
+
function hourListStride(values) {
|
|
1370
|
+
if (values.length < 2) {
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
const interval = values[1] - values[0];
|
|
1374
|
+
if (interval < 2) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
1378
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (values[0] !== 0 && values.length < 5) {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
return { interval, last: values[values.length - 1], start: values[0] };
|
|
1386
|
+
}
|
|
1387
|
+
function hourStride(ir) {
|
|
1388
|
+
const segments = ir.analyses.segments.hour;
|
|
1389
|
+
if (segments.length === 1 && segments[0].kind === "step") {
|
|
1390
|
+
const segment = segments[0];
|
|
1391
|
+
if (segment.fires.length < 2) {
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
|
|
1395
|
+
return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
|
|
1396
|
+
}
|
|
1397
|
+
const values = singleValues(segments);
|
|
1398
|
+
return values && hourListStride(values);
|
|
1399
|
+
}
|
|
1400
|
+
function subMinuteSecond(ir) {
|
|
1401
|
+
return ir.pattern.second === "*" || ir.shapes.second === "step";
|
|
1402
|
+
}
|
|
1403
|
+
function hourCadenceLead(ir, minute, opts) {
|
|
1404
|
+
if (minute === 0) {
|
|
1405
|
+
if (subMinuteSecond(ir)) {
|
|
1406
|
+
return secondsClause(ir, "minute", opts) + " for one minute";
|
|
1407
|
+
}
|
|
1408
|
+
return secondsClause(ir, "hour", opts);
|
|
1409
|
+
}
|
|
1410
|
+
const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
|
|
1411
|
+
if (ir.pattern.second === "0") {
|
|
1412
|
+
return minutePhrase;
|
|
1413
|
+
}
|
|
1414
|
+
return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
|
|
1415
|
+
}
|
|
1416
|
+
function hourCadence(ir, minute, opts) {
|
|
1417
|
+
const stride = hourStride(ir);
|
|
1418
|
+
if (!stride) {
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
1422
|
+
if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
|
|
1426
|
+
if (confinement) {
|
|
1427
|
+
return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
|
|
1428
|
+
}
|
|
1429
|
+
if (minute === 0 && ir.pattern.second === "0") {
|
|
1430
|
+
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1431
|
+
}
|
|
1432
|
+
return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1433
|
+
}
|
|
1434
|
+
function cleanStrideSegment(ir) {
|
|
1435
|
+
const segments = ir.analyses.segments.hour;
|
|
1436
|
+
const segment = segments.length === 1 && segments[0];
|
|
1437
|
+
if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
return segment;
|
|
1441
|
+
}
|
|
1442
|
+
function hasHourWindow(ir) {
|
|
1443
|
+
return ir.analyses.segments.hour.some(function range(segment) {
|
|
1444
|
+
return segment.kind === "range";
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function hourRangeWindowTail(ir, opts) {
|
|
1448
|
+
const windows = [];
|
|
1449
|
+
const singles = [];
|
|
1450
|
+
ir.analyses.segments.hour.forEach(function classify(segment) {
|
|
1451
|
+
if (segment.kind === "range") {
|
|
1452
|
+
windows.push("from " + getTime(
|
|
1453
|
+
{ hour: +segment.bounds[0], minute: 0 },
|
|
1454
|
+
opts
|
|
1455
|
+
) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
|
|
1456
|
+
} else if (segment.kind === "step") {
|
|
1457
|
+
singles.push(...segment.fires);
|
|
1458
|
+
} else {
|
|
1459
|
+
singles.push(+segment.value);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
let phrase = "every hour " + joinList(windows, opts);
|
|
1463
|
+
if (singles.length) {
|
|
1464
|
+
phrase += " and at " + joinList(singles.map(function time(hour) {
|
|
1465
|
+
return getTime({ hour, minute: 0 }, opts);
|
|
1466
|
+
}), opts);
|
|
1467
|
+
}
|
|
1468
|
+
return phrase;
|
|
1469
|
+
}
|
|
1470
|
+
function hourRangeCadence(ir, minute, opts) {
|
|
1471
|
+
if (minute !== 0 || !hasHourWindow(ir)) {
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
if (ir.pattern.second === "0") {
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
if (subMinuteSecond(ir)) {
|
|
1478
|
+
return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
|
|
1479
|
+
}
|
|
1480
|
+
return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
|
|
1481
|
+
}
|
|
1241
1482
|
function seriesNumber(values, opts) {
|
|
1242
1483
|
const anyBig = values.some(function big(v) {
|
|
1243
1484
|
return +v > 10;
|