cronli5 0.1.4 → 0.1.5

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,7 +944,15 @@ 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
+ return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
950
+ }
929
951
  function renderComposeSeconds(ir, plan, opts) {
952
+ const cadence = composeHourCadence(ir, plan, opts);
953
+ if (cadence !== null) {
954
+ return cadence;
955
+ }
930
956
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
931
957
  const minute = plan.rest.times[0].minute;
932
958
  if (+minute === 0) {
@@ -959,6 +985,9 @@ function clockTimesOf(ir, plan, opts) {
959
985
  return joinList(times, opts) + (trail && ", " + trail);
960
986
  }
961
987
  function secondsLeadClause(ir, opts) {
988
+ return secondsClause(ir, "minute", opts);
989
+ }
990
+ function secondsClause(ir, anchor, opts) {
962
991
  const secondField = ir.pattern.second;
963
992
  const shape = ir.shapes.second;
964
993
  if (secondField === "*") {
@@ -968,22 +997,27 @@ function secondsLeadClause(ir, opts) {
968
997
  return stepCycle60(
969
998
  ir.analyses.segments.second[0],
970
999
  "second",
971
- "minute",
1000
+ anchor,
972
1001
  opts
973
1002
  );
974
1003
  }
975
1004
  if (shape === "range") {
976
1005
  const bounds = secondField.split("-");
977
1006
  const num = seriesNumber(bounds, opts);
978
- return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
1007
+ return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
979
1008
  }
980
1009
  if (shape === "single") {
981
- return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
1010
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
982
1011
  }
983
- return listPastThe(
1012
+ return strideFromSegments(
1013
+ ir.analyses.segments.second,
1014
+ "second",
1015
+ anchor,
1016
+ opts
1017
+ ) ?? listPastThe(
984
1018
  segmentWords(ir.analyses.segments.second, opts),
985
1019
  "second",
986
- "minute",
1020
+ anchor,
987
1021
  opts
988
1022
  );
989
1023
  }
@@ -998,12 +1032,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
998
1032
  return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
999
1033
  }
1000
1034
  function renderMultipleMinutes(ir, plan, opts) {
1001
- return listPastThe(
1002
- segmentWords(ir.analyses.segments.minute, opts),
1003
- "minute",
1004
- "hour",
1035
+ const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
1036
+ return (stride ?? listPastThe(segmentWords(
1037
+ ir.analyses.segments.minute,
1005
1038
  opts
1006
- ) + trailingQualifier(ir, opts);
1039
+ ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
1007
1040
  }
1008
1041
  function renderMinuteFrequency(ir, plan, opts) {
1009
1042
  let phrase = stepCycle60(
@@ -1033,8 +1066,9 @@ function renderMinutesAcrossHours(ir, plan, opts) {
1033
1066
  }
1034
1067
  const times = hourTimesFromPlan(ir, plan.times, true, opts);
1035
1068
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
1036
- // The 'list' form is a minute list, which has segments.
1037
- listPastThe(
1069
+ // The 'list' form is a minute list, which has segments; an offset/uneven
1070
+ // step enumerated to that list reads as a stride.
1071
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1038
1072
  segmentWords(ir.analyses.segments.minute, opts),
1039
1073
  "minute",
1040
1074
  "hour",
@@ -1061,7 +1095,13 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
1061
1095
  if (plan.form === "wildcard") {
1062
1096
  return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
1063
1097
  }
1064
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
1098
+ const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1099
+ segmentWords(ir.analyses.segments.minute, opts),
1100
+ "minute",
1101
+ "hour",
1102
+ opts
1103
+ ) : minuteRangeLead(ir.pattern.minute, opts);
1104
+ return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
1065
1105
  }
1066
1106
  function minuteRangeLead(minuteField, opts) {
1067
1107
  const bounds = minuteField.split("-");
@@ -1085,7 +1125,12 @@ function rangeMinuteLead(ir, opts) {
1085
1125
  if (ir.pattern.minute === "0") {
1086
1126
  return "every hour";
1087
1127
  }
1088
- return listPastThe(
1128
+ return strideFromSegments(
1129
+ ir.analyses.segments.minute,
1130
+ "minute",
1131
+ "hour",
1132
+ opts
1133
+ ) ?? listPastThe(
1089
1134
  segmentWords(ir.analyses.segments.minute, opts),
1090
1135
  "minute",
1091
1136
  "hour",
@@ -1102,6 +1147,12 @@ function hourWindow(window, opts) {
1102
1147
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
1103
1148
  }
1104
1149
  function renderClockTimes(ir, plan, opts) {
1150
+ if (ir.shapes.minute === "single") {
1151
+ const cadence = hourCadence(ir, +ir.pattern.minute, opts);
1152
+ if (cadence !== null) {
1153
+ return cadence;
1154
+ }
1155
+ }
1105
1156
  const plain = mixedTwelve(plan.times);
1106
1157
  const times = plan.times.map(function clock(time) {
1107
1158
  return getTime({
@@ -1115,6 +1166,10 @@ function renderClockTimes(ir, plan, opts) {
1115
1166
  }
1116
1167
  function renderCompactClockTimes(ir, plan, opts) {
1117
1168
  if (plan.fold) {
1169
+ const cadence = hourCadence(ir, +plan.minute, opts);
1170
+ if (cadence !== null) {
1171
+ return cadence;
1172
+ }
1118
1173
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
1119
1174
  return segment.kind === "range";
1120
1175
  });
@@ -1125,13 +1180,14 @@ function renderCompactClockTimes(ir, plan, opts) {
1125
1180
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
1126
1181
  }
1127
1182
  const phrase = (
1128
- // The non-fold branch is a minute list, which has segments.
1129
- listPastThe(
1183
+ // The non-fold branch is a minute list, which has segments. An
1184
+ // offset/uneven step enumerated to that list reads as a stride.
1185
+ (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1130
1186
  segmentWords(ir.analyses.segments.minute, opts),
1131
1187
  "minute",
1132
1188
  "hour",
1133
1189
  opts
1134
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
1190
+ )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
1135
1191
  );
1136
1192
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
1137
1193
  }
@@ -1179,24 +1235,50 @@ var renderers = {
1179
1235
  singleMinute: renderSingleMinute,
1180
1236
  standaloneSeconds: renderStandaloneSeconds
1181
1237
  };
1238
+ function renderStride(stride, opts) {
1239
+ const { interval, start, last, cycle, unit, anchor } = stride;
1240
+ const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
1241
+ const tiles = cycle % interval === 0;
1242
+ if (start === 0 && tiles) {
1243
+ return cadence;
1244
+ }
1245
+ if (start < interval && tiles) {
1246
+ return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
1247
+ }
1248
+ const num = seriesNumber([start, last], opts);
1249
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
1250
+ }
1251
+ function singleValues(segments) {
1252
+ const values = [];
1253
+ for (const segment of segments) {
1254
+ if (segment.kind !== "single") {
1255
+ return null;
1256
+ }
1257
+ values.push(+segment.value);
1258
+ }
1259
+ return values;
1260
+ }
1261
+ function strideFromSegments(segments, unit, anchor, opts) {
1262
+ const values = singleValues(segments);
1263
+ const step = values && arithmeticStep(values);
1264
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
1265
+ }
1182
1266
  function stepCycle60(segment, unit, anchor, opts) {
1183
1267
  if (segment.startToken.indexOf("-") !== -1) {
1184
1268
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1185
1269
  }
1186
1270
  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;
1271
+ if (start !== 0 && segment.fires.length <= 3) {
1272
+ return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1198
1273
  }
1199
- return "every " + getNumber(interval, opts) + " " + unit + "s";
1274
+ return renderStride({
1275
+ interval: segment.interval,
1276
+ start,
1277
+ last: segment.fires[segment.fires.length - 1],
1278
+ cycle: 60,
1279
+ unit,
1280
+ anchor
1281
+ }, opts);
1200
1282
  }
1201
1283
  function stepHours(segment, opts) {
1202
1284
  if (segment.startToken.indexOf("-") !== -1) {
@@ -1212,6 +1294,68 @@ function stepHours(segment, opts) {
1212
1294
  }
1213
1295
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
1214
1296
  }
1297
+ function hourStrideCadence(stride, opts) {
1298
+ const { start, interval, last } = stride;
1299
+ const cadence = "every " + getNumber(interval, opts) + " hours";
1300
+ const tiles = 24 % interval === 0;
1301
+ if (start === 0 && tiles) {
1302
+ return cadence;
1303
+ }
1304
+ if (start < interval && tiles) {
1305
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
1306
+ }
1307
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
1308
+ }
1309
+ function hourStride(ir) {
1310
+ const segments = ir.analyses.segments.hour;
1311
+ if (segments.length === 1 && segments[0].kind === "step") {
1312
+ const segment = segments[0];
1313
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
1314
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
1315
+ }
1316
+ const values = singleValues(segments);
1317
+ const step = values && arithmeticStep(values);
1318
+ return step || null;
1319
+ }
1320
+ function subMinuteSecond(ir) {
1321
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
1322
+ }
1323
+ function hourCadenceLead(ir, minute, opts) {
1324
+ if (minute === 0) {
1325
+ if (subMinuteSecond(ir)) {
1326
+ return secondsClause(ir, "minute", opts) + " for one minute";
1327
+ }
1328
+ return secondsClause(ir, "hour", opts);
1329
+ }
1330
+ const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
1331
+ if (ir.pattern.second === "0") {
1332
+ return minutePhrase;
1333
+ }
1334
+ return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
1335
+ }
1336
+ function hourCadence(ir, minute, opts) {
1337
+ const stride = hourStride(ir);
1338
+ if (!stride) {
1339
+ return null;
1340
+ }
1341
+ const fires = (stride.last - stride.start) / stride.interval + 1;
1342
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
1343
+ return null;
1344
+ }
1345
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
1346
+ if (confinement) {
1347
+ return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
1348
+ }
1349
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1350
+ }
1351
+ function cleanStrideSegment(ir) {
1352
+ const segments = ir.analyses.segments.hour;
1353
+ const segment = segments.length === 1 && segments[0];
1354
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
1355
+ return null;
1356
+ }
1357
+ return segment;
1358
+ }
1215
1359
  function seriesNumber(values, opts) {
1216
1360
  const anyBig = values.some(function big(v) {
1217
1361
  return +v > 10;
package/dist/lang/de.cjs CHANGED
@@ -40,12 +40,28 @@ var weekdayNumbers = {
40
40
  FRI: 5,
41
41
  SAT: 6
42
42
  };
43
+ var maxClockTimes = 6;
43
44
 
44
45
  // src/core/util.ts
45
46
  function isNonNegativeInteger(value) {
46
47
  const digits = /^\d+$/;
47
48
  return digits.test(value);
48
49
  }
50
+ function arithmeticStep(values) {
51
+ if (values.length < 5) {
52
+ return null;
53
+ }
54
+ const interval = values[1] - values[0];
55
+ if (interval < 2) {
56
+ return null;
57
+ }
58
+ for (let i = 2; i < values.length; i += 1) {
59
+ if (values[i] - values[i - 1] !== interval) {
60
+ return null;
61
+ }
62
+ }
63
+ return { start: values[0], interval, last: values[values.length - 1] };
64
+ }
49
65
  function toFieldNumber(token, numberMap) {
50
66
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
51
67
  }
@@ -112,6 +128,49 @@ function stepSegment(segments) {
112
128
  function cleanStep(segment, cycle) {
113
129
  return (segment.startToken === "*" || +segment.startToken === 0) && cycle % segment.interval === 0;
114
130
  }
131
+ function renderStride(stride) {
132
+ const { interval, start, last, cycle, unit, anchor } = stride;
133
+ const cadence = everyN(interval, unit);
134
+ const tiles = cycle % interval === 0;
135
+ if (start === 0 && tiles) {
136
+ return cadence;
137
+ }
138
+ const tail = anchor ? " " + anchor : "";
139
+ if (start < interval && tiles) {
140
+ return cadence + " ab " + unit.singular + " " + start + tail;
141
+ }
142
+ return cadence + " von " + unit.singular + " " + start + " bis " + last + tail;
143
+ }
144
+ function stepClause(segment, unit, anchor) {
145
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
146
+ const short = start !== 0 && segment.fires.length <= 3;
147
+ if (segment.startToken.indexOf("-") !== -1 || short) {
148
+ return "in den " + unit.plural + " " + joinList(segment.fires.map(String)) + " " + anchor;
149
+ }
150
+ return renderStride({
151
+ interval: segment.interval,
152
+ start,
153
+ last: segment.fires[segment.fires.length - 1],
154
+ cycle: 60,
155
+ unit,
156
+ anchor
157
+ });
158
+ }
159
+ function singleValues(segments) {
160
+ const values = [];
161
+ for (const segment of segments) {
162
+ if (segment.kind !== "single") {
163
+ return null;
164
+ }
165
+ values.push(+segment.value);
166
+ }
167
+ return values;
168
+ }
169
+ function strideFromSegments(segments, unit, anchor) {
170
+ const values = singleValues(segments);
171
+ const step = values && arithmeticStep(values);
172
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }) : null;
173
+ }
115
174
  var weekdayNames = [
116
175
  "sonntags",
117
176
  "montags",
@@ -178,6 +237,13 @@ function everyNthHour(segment) {
178
237
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
179
238
  return start === 0 ? base : base + " ab " + start + " Uhr";
180
239
  }
240
+ function confinedHourStride(segment) {
241
+ if (segment.startToken.indexOf("-") !== -1) {
242
+ return false;
243
+ }
244
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
245
+ return 24 % segment.interval === 0 && start < segment.interval;
246
+ }
181
247
  function weekdayNoun(token) {
182
248
  return weekdayNouns[toFieldNumber(token, weekdayNumbers)];
183
249
  }
@@ -281,14 +347,17 @@ function countedPhrase(ir, field, singular, plural) {
281
347
  return "in den " + plural + " " + joinList(fieldValues(ir, field));
282
348
  }
283
349
  function secondsLead(ir) {
350
+ return secondsClause(ir, "jeder Minute");
351
+ }
352
+ function secondsClause(ir, anchor) {
284
353
  if (ir.pattern.second === "*") {
285
354
  return "jede Sekunde";
286
355
  }
287
356
  const segments = ir.analyses.segments.second;
288
- if (ir.shapes.second === "step" && cleanStep(stepSegment(segments), 60)) {
289
- return everyN(stepSegment(segments).interval, UNITS.second);
357
+ if (ir.shapes.second === "step") {
358
+ return stepClause(stepSegment(segments), UNITS.second, anchor);
290
359
  }
291
- return countedPhrase(ir, "second", "Sekunde", "Sekunden") + " jeder Minute";
360
+ return strideFromSegments(segments, UNITS.second, anchor) ?? countedPhrase(ir, "second", "Sekunde", "Sekunden") + " " + anchor;
292
361
  }
293
362
  function spanTime(hour, minute, sep) {
294
363
  return hour + sep + pad(minute);
@@ -357,8 +426,15 @@ function renderEveryHour() {
357
426
  function renderSeconds(ir) {
358
427
  return secondsLead(ir);
359
428
  }
429
+ function minutePastClause(ir) {
430
+ return strideFromSegments(
431
+ fieldSegments(ir, "minute"),
432
+ UNITS.minute,
433
+ "jeder Stunde"
434
+ ) ?? countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
435
+ }
360
436
  function renderMinutePast(ir) {
361
- return countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
437
+ return minutePastClause(ir);
362
438
  }
363
439
  function renderSecondsWithinMinute(ir, plan) {
364
440
  if (plan.singleSecond) {
@@ -383,9 +459,21 @@ function renderMinuteSpanInHour(ir, plan, opts) {
383
459
  return "jede Minute von " + spanTime(plan.hour, plan.span[0], sep) + " bis " + spanTime(plan.hour, plan.span[1], sep) + " Uhr";
384
460
  }
385
461
  function renderComposeSeconds(ir, plan, opts) {
462
+ if ((plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes") && ir.shapes.minute === "single") {
463
+ const cadence = hourCadence(ir, +ir.pattern.minute);
464
+ if (cadence !== null) {
465
+ return cadence;
466
+ }
467
+ }
386
468
  if (composeMinuteZero(ir, plan)) {
387
469
  return secondsLead(ir) + " " + clockMinuteGenitive(plan.rest.times, opts.style.sep);
388
470
  }
471
+ if (plan.rest.kind === "minuteFrequency" && ir.shapes.second === "wildcard" && ir.shapes.hour === "wildcard") {
472
+ const minuteStep = stepSegment(ir.analyses.segments.minute);
473
+ if (minuteStep.startToken === "*" && minuteStep.interval === 2) {
474
+ return secondsLead(ir) + " jeder zweiten Minute";
475
+ }
476
+ }
389
477
  return secondsLead(ir) + ", " + render(ir, plan.rest, opts);
390
478
  }
391
479
  function composeMinuteZero(ir, plan) {
@@ -403,29 +491,35 @@ function renderMinutesAcrossHours(ir, plan, opts) {
403
491
  return "jede Minute " + duringHours(ir, plan.times, sep);
404
492
  }
405
493
  const hours = plan.times.kind === "fires" ? atHours(plan.times.fires) : joinList(hourSegmentParts(ir, 0, 0, sep));
406
- return countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hours;
494
+ return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
407
495
  }
408
496
  function renderMinuteSpanAcrossHourStep(ir, plan) {
409
497
  if (plan.form === "wildcard") {
410
498
  return "jede Minute " + everyNthHour(stepSegment(ir.analyses.segments.hour));
411
499
  }
412
- return countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hourStepPhrase(ir);
500
+ const segment = stepSegment(ir.analyses.segments.hour);
501
+ const hours = confinedHourStride(segment) ? everyNthHour(segment) : atHours(segment.fires);
502
+ return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
413
503
  }
414
504
  function renderCompactClockTimes(ir, plan, opts) {
415
505
  const sep = opts.style.sep;
416
506
  if (plan.fold) {
507
+ const cadence = hourCadence(ir, plan.minute);
508
+ if (cadence !== null) {
509
+ return cadence;
510
+ }
417
511
  const hourly = fieldSegments(ir, "hour").some((segment) => segment.kind === "range");
418
512
  return (hourly ? "st\xFCndlich " : "t\xE4glich ") + joinList(hourSegmentParts(ir, plan.minute, ir.analyses.clockSecond, sep));
419
513
  }
420
514
  const hours = fieldSegments(ir, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(ir, 0, 0, sep)) : atHours(hourFires(ir));
421
515
  const lead = ir.analyses.clockSecond ? countedPhrase(ir, "second", "Sekunde", "Sekunden") + ", " : "";
422
- return lead + countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hours;
516
+ return lead + (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
423
517
  }
424
518
  function renderMinuteFrequency(ir, plan, opts) {
425
519
  const segment = stepSegment(ir.analyses.segments.minute);
426
520
  const sep = opts.style.sep;
427
521
  const clean = cleanStep(segment, 60);
428
- const base = clean ? everyN(segment.interval, UNITS.minute) : countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
522
+ const base = stepClause(segment, UNITS.minute, "jeder Stunde");
429
523
  if (plan.hours.kind === "window") {
430
524
  const window = hourWindow(
431
525
  plan.hours.from,
@@ -447,6 +541,67 @@ function hourStepPhrase(ir) {
447
541
  const segment = stepSegment(ir.analyses.segments.hour);
448
542
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
449
543
  }
544
+ function hourStrideCadence(stride) {
545
+ const { start, interval, last } = stride;
546
+ const cadence = everyN(interval, UNITS.hour);
547
+ const tiles = 24 % interval === 0;
548
+ if (start === 0 && tiles) {
549
+ return cadence;
550
+ }
551
+ if (start < interval && tiles) {
552
+ return cadence + " ab " + start + " Uhr";
553
+ }
554
+ return cadence + " von " + start + " bis " + last + " Uhr";
555
+ }
556
+ function hourStride(ir) {
557
+ const segments = fieldSegments(ir, "hour");
558
+ if (!segments) {
559
+ return null;
560
+ }
561
+ if (segments.length === 1 && segments[0].kind === "step") {
562
+ const segment = segments[0];
563
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
564
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
565
+ }
566
+ const values = singleValues(segments);
567
+ const step = values && arithmeticStep(values);
568
+ return step || null;
569
+ }
570
+ function subMinuteSecond(ir) {
571
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
572
+ }
573
+ function hourCadenceLead(ir, minute) {
574
+ if (minute === 0) {
575
+ if (subMinuteSecond(ir)) {
576
+ return secondsClause(ir, "jeder Minute") + " f\xFCr eine Minute";
577
+ }
578
+ return secondsClause(ir, "jeder Stunde");
579
+ }
580
+ const minutePhrase = "in Minute " + minute;
581
+ if (ir.pattern.second === "0") {
582
+ return minutePhrase;
583
+ }
584
+ return secondsClause(ir, "jeder Minute") + ", " + minutePhrase;
585
+ }
586
+ function hourCadence(ir, minute) {
587
+ const stride = hourStride(ir);
588
+ if (!stride) {
589
+ return null;
590
+ }
591
+ const fires = (stride.last - stride.start) / stride.interval + 1;
592
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
593
+ return null;
594
+ }
595
+ const segment = fieldSegments(ir, "hour")[0];
596
+ const confined = minute === 0 && subMinuteSecond(ir) && fieldSegments(ir, "hour").length === 1 && segment.kind === "step" && confinedHourStride(segment);
597
+ if (confined) {
598
+ return secondsClause(ir, "jeder Minute") + " f\xFCr eine Minute " + everyNthHour(segment);
599
+ }
600
+ return hourCadenceLead(ir, minute) + ", " + hourStrideCadence(stride);
601
+ }
602
+ function hourCadenceApplies(ir) {
603
+ return ir.shapes.minute === "single" && hourCadence(ir, +ir.pattern.minute) !== null;
604
+ }
450
605
  function renderHourRange(ir, plan, opts) {
451
606
  const window = hourWindow(
452
607
  plan.from,
@@ -463,6 +618,12 @@ function renderHourRange(ir, plan, opts) {
463
618
  return countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde, " + window;
464
619
  }
465
620
  function renderClockTimes(ir, plan, opts) {
621
+ if (ir.shapes.minute === "single") {
622
+ const cadence = hourCadence(ir, +ir.pattern.minute);
623
+ if (cadence !== null) {
624
+ return cadence;
625
+ }
626
+ }
466
627
  return "um " + timesPhrase(plan.times, opts.style.sep);
467
628
  }
468
629
  var renderers = {
@@ -512,6 +673,9 @@ function isComposeMinuteZero(ir) {
512
673
  return ir.plan.kind === "composeSeconds" && composeMinuteZero(ir, ir.plan);
513
674
  }
514
675
  function needsDailyFrame(ir) {
676
+ if (hourCadenceApplies(ir)) {
677
+ return false;
678
+ }
515
679
  if (ir.plan.kind === "clockTimes" || isComposeMinuteZero(ir)) {
516
680
  return true;
517
681
  }