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/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, subMinuteSecond = false) {
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, subMinuteSecond);
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, subMinuteSecond) {
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" || subMinuteSecond) {
723
+ if (pattern.minute !== "0" || subMinuteSecond2) {
706
724
  return { kind: "singleMinute" };
707
725
  }
708
726
  }
709
- function planHours(pattern, shapes, analyses, subMinuteSecond = false) {
710
- const absorbsMinuteZero = subMinuteSecond && pattern.minute === "0";
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" && !subMinuteSecond) {
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
- const minute = plan.rest.times[0].minute;
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
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
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
- "minute",
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 minute";
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 minute";
1018
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
982
1019
  }
983
- return listPastThe(
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
- "minute",
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
- return listPastThe(
1002
- segmentWords(ir.analyses.segments.minute, opts),
1003
- "minute",
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
- phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
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
- listPastThe(
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
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
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 listPastThe(
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 phrase = (
1128
- // The non-fold branch is a minute list, which has segments.
1129
- listPastThe(
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
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
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
- const interval = segment.interval;
1188
- if (start !== 0) {
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 "every " + getNumber(interval, opts) + " " + unit + "s";
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;