cronli5 0.1.5 → 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
@@ -946,7 +946,17 @@ function renderSecondsWithinMinute(ir, plan, opts) {
946
946
  }
947
947
  function composeHourCadence(ir, plan, opts) {
948
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;
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);
950
960
  }
951
961
  function renderComposeSeconds(ir, plan, opts) {
952
962
  const cadence = composeHourCadence(ir, plan, opts);
@@ -954,16 +964,14 @@ function renderComposeSeconds(ir, plan, opts) {
954
964
  return cadence;
955
965
  }
956
966
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
957
- const minute = plan.rest.times[0].minute;
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);
967
+ return clockTimesConfinement(ir, plan.rest, opts);
962
968
  }
963
969
  if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
964
970
  return "every second of every other minute" + trailingQualifier(ir, opts);
965
971
  }
966
- 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);
967
975
  }
968
976
  function durationHours(ir, plan, opts) {
969
977
  const hours = plan.times.map(function clock(time) {
@@ -1046,7 +1054,8 @@ function renderMinuteFrequency(ir, plan, opts) {
1046
1054
  opts
1047
1055
  );
1048
1056
  if (plan.hours.kind === "during") {
1049
- 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";
1050
1059
  } else if (plan.hours.kind === "window") {
1051
1060
  phrase += " " + hourWindow(plan.hours, opts);
1052
1061
  } else if (plan.hours.kind === "step") {
@@ -1061,10 +1070,13 @@ function renderMinuteSpanInHour(ir, plan, opts) {
1061
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);
1062
1071
  }
1063
1072
  function renderMinutesAcrossHours(ir, plan, opts) {
1073
+ const cadence = unevenHourCadence(ir, opts);
1064
1074
  if (plan.form === "wildcard") {
1075
+ if (cadence !== null) {
1076
+ return "every minute, " + cadence + trailingQualifier(ir, opts);
1077
+ }
1065
1078
  return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
1066
1079
  }
1067
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
1068
1080
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
1069
1081
  // The 'list' form is a minute list, which has segments; an offset/uneven
1070
1082
  // step enumerated to that list reads as a stride.
@@ -1075,6 +1087,10 @@ function renderMinutesAcrossHours(ir, plan, opts) {
1075
1087
  opts
1076
1088
  )
1077
1089
  );
1090
+ if (cadence !== null) {
1091
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
1092
+ }
1093
+ const times = hourTimesFromPlan(ir, plan.times, true, opts);
1078
1094
  return lead + ", at " + times + trailingQualifier(ir, opts);
1079
1095
  }
1080
1096
  var stepOrdinals = {
@@ -1101,7 +1117,8 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
1101
1117
  "hour",
1102
1118
  opts
1103
1119
  ) : minuteRangeLead(ir.pattern.minute, opts);
1104
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
1120
+ const cadence = unevenHourCadence(ir, opts);
1121
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
1105
1122
  }
1106
1123
  function minuteRangeLead(minuteField, opts) {
1107
1124
  const bounds = minuteField.split("-");
@@ -1138,6 +1155,10 @@ function rangeMinuteLead(ir, opts) {
1138
1155
  );
1139
1156
  }
1140
1157
  function renderHourStep(ir, plan, opts) {
1158
+ const cadence = unevenHourCadence(ir, opts);
1159
+ if (cadence !== null) {
1160
+ return cadence + trailingQualifier(ir, opts);
1161
+ }
1141
1162
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
1142
1163
  }
1143
1164
  function boundedWindow(plan) {
@@ -1148,7 +1169,8 @@ function hourWindow(window, opts) {
1148
1169
  }
1149
1170
  function renderClockTimes(ir, plan, opts) {
1150
1171
  if (ir.shapes.minute === "single") {
1151
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
1172
+ const minute = +ir.pattern.minute;
1173
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
1152
1174
  if (cadence !== null) {
1153
1175
  return cadence;
1154
1176
  }
@@ -1166,9 +1188,9 @@ function renderClockTimes(ir, plan, opts) {
1166
1188
  }
1167
1189
  function renderCompactClockTimes(ir, plan, opts) {
1168
1190
  if (plan.fold) {
1169
- const cadence = hourCadence(ir, +plan.minute, opts);
1170
- if (cadence !== null) {
1171
- return cadence;
1191
+ const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
1192
+ if (cadence2 !== null) {
1193
+ return cadence2;
1172
1194
  }
1173
1195
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
1174
1196
  return segment.kind === "range";
@@ -1179,16 +1201,18 @@ function renderCompactClockTimes(ir, plan, opts) {
1179
1201
  const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
1180
1202
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
1181
1203
  }
1182
- const phrase = (
1204
+ const minuteLead = (
1183
1205
  // The non-fold branch is a minute list, which has segments. An
1184
1206
  // offset/uneven step enumerated to that list reads as a stride.
1185
- (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1207
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1186
1208
  segmentWords(ir.analyses.segments.minute, opts),
1187
1209
  "minute",
1188
1210
  "hour",
1189
1211
  opts
1190
- )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
1212
+ )
1191
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);
1192
1216
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
1193
1217
  }
1194
1218
  function foldedHourWindows(ir, plan, opts) {
@@ -1306,16 +1330,46 @@ function hourStrideCadence(stride, opts) {
1306
1330
  }
1307
1331
  return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
1308
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
+ }
1309
1361
  function hourStride(ir) {
1310
1362
  const segments = ir.analyses.segments.hour;
1311
1363
  if (segments.length === 1 && segments[0].kind === "step") {
1312
1364
  const segment = segments[0];
1365
+ if (segment.fires.length < 2) {
1366
+ return null;
1367
+ }
1313
1368
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
1314
1369
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
1315
1370
  }
1316
1371
  const values = singleValues(segments);
1317
- const step = values && arithmeticStep(values);
1318
- return step || null;
1372
+ return values && hourListStride(values);
1319
1373
  }
1320
1374
  function subMinuteSecond(ir) {
1321
1375
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -1339,13 +1393,16 @@ function hourCadence(ir, minute, opts) {
1339
1393
  return null;
1340
1394
  }
1341
1395
  const fires = (stride.last - stride.start) / stride.interval + 1;
1342
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
1396
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
1343
1397
  return null;
1344
1398
  }
1345
1399
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
1346
1400
  if (confinement) {
1347
1401
  return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
1348
1402
  }
1403
+ if (minute === 0 && ir.pattern.second === "0") {
1404
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1405
+ }
1349
1406
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1350
1407
  }
1351
1408
  function cleanStrideSegment(ir) {
@@ -1356,6 +1413,46 @@ function cleanStrideSegment(ir) {
1356
1413
  }
1357
1414
  return segment;
1358
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
+ }
1359
1456
  function seriesNumber(values, opts) {
1360
1457
  const anyBig = values.some(function big(v) {
1361
1458
  return +v > 10;
package/dist/lang/de.cjs CHANGED
@@ -458,9 +458,17 @@ function renderMinuteSpanInHour(ir, plan, opts) {
458
458
  const sep = opts.style.sep;
459
459
  return "jede Minute von " + spanTime(plan.hour, plan.span[0], sep) + " bis " + spanTime(plan.hour, plan.span[1], sep) + " Uhr";
460
460
  }
461
+ function isEveryOtherMinuteSeconds(ir, plan) {
462
+ if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
463
+ return false;
464
+ }
465
+ const minuteStep = stepSegment(ir.analyses.segments.minute);
466
+ return minuteStep.startToken === "*" && minuteStep.interval === 2;
467
+ }
461
468
  function renderComposeSeconds(ir, plan, opts) {
462
469
  if ((plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes") && ir.shapes.minute === "single") {
463
- const cadence = hourCadence(ir, +ir.pattern.minute);
470
+ const minute = +ir.pattern.minute;
471
+ const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
464
472
  if (cadence !== null) {
465
473
  return cadence;
466
474
  }
@@ -468,13 +476,12 @@ function renderComposeSeconds(ir, plan, opts) {
468
476
  if (composeMinuteZero(ir, plan)) {
469
477
  return secondsLead(ir) + " " + clockMinuteGenitive(plan.rest.times, opts.style.sep);
470
478
  }
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
- }
479
+ if (isEveryOtherMinuteSeconds(ir, plan)) {
480
+ return secondsLead(ir) + " jeder zweiten Minute";
476
481
  }
477
- return secondsLead(ir) + ", " + render(ir, plan.rest, opts);
482
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
483
+ const lead = restOwnsLead ? "" : secondsLead(ir) + ", ";
484
+ return lead + render(ir, plan.rest, opts);
478
485
  }
479
486
  function composeMinuteZero(ir, plan) {
480
487
  return plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0);
@@ -487,31 +494,37 @@ function clockMinuteGenitive(times, sep) {
487
494
  }
488
495
  function renderMinutesAcrossHours(ir, plan, opts) {
489
496
  const sep = opts.style.sep;
497
+ const cadence = unevenHourCadence(ir);
490
498
  if (plan.form === "wildcard") {
491
- return "jede Minute " + duringHours(ir, plan.times, sep);
499
+ return cadence ? "jede Minute, " + cadence : "jede Minute " + duringHours(ir, plan.times, sep);
500
+ }
501
+ const minuteLead = strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten");
502
+ if (cadence !== null) {
503
+ return minuteLead + ", " + cadence;
492
504
  }
493
505
  const hours = plan.times.kind === "fires" ? atHours(plan.times.fires) : joinList(hourSegmentParts(ir, 0, 0, sep));
494
- return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
506
+ return minuteLead + ", " + hours;
495
507
  }
496
508
  function renderMinuteSpanAcrossHourStep(ir, plan) {
509
+ const cadence = unevenHourCadence(ir);
497
510
  if (plan.form === "wildcard") {
498
511
  return "jede Minute " + everyNthHour(stepSegment(ir.analyses.segments.hour));
499
512
  }
500
513
  const segment = stepSegment(ir.analyses.segments.hour);
501
- const hours = confinedHourStride(segment) ? everyNthHour(segment) : atHours(segment.fires);
514
+ const hours = cadence ?? (confinedHourStride(segment) ? everyNthHour(segment) : atHours(segment.fires));
502
515
  return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
503
516
  }
504
517
  function renderCompactClockTimes(ir, plan, opts) {
505
518
  const sep = opts.style.sep;
506
519
  if (plan.fold) {
507
- const cadence = hourCadence(ir, plan.minute);
520
+ const cadence = hourCadence(ir, plan.minute) ?? hourRangeCadence(ir, plan.minute);
508
521
  if (cadence !== null) {
509
522
  return cadence;
510
523
  }
511
524
  const hourly = fieldSegments(ir, "hour").some((segment) => segment.kind === "range");
512
525
  return (hourly ? "st\xFCndlich " : "t\xE4glich ") + joinList(hourSegmentParts(ir, plan.minute, ir.analyses.clockSecond, sep));
513
526
  }
514
- const hours = fieldSegments(ir, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(ir, 0, 0, sep)) : atHours(hourFires(ir));
527
+ const hours = unevenHourCadence(ir) ?? (fieldSegments(ir, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(ir, 0, 0, sep)) : atHours(hourFires(ir)));
515
528
  const lead = ir.analyses.clockSecond ? countedPhrase(ir, "second", "Sekunde", "Sekunden") + ", " : "";
516
529
  return lead + (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
517
530
  }
@@ -530,7 +543,8 @@ function renderMinuteFrequency(ir, plan, opts) {
530
543
  return clean ? base + " " + window : base + ", " + window;
531
544
  }
532
545
  if (plan.hours.kind === "during") {
533
- return base + " " + duringHours(ir, plan.hours.times, sep);
546
+ const cadence = unevenHourCadence(ir);
547
+ return cadence ? base + ", " + cadence : base + " " + duringHours(ir, plan.hours.times, sep);
534
548
  }
535
549
  if (plan.hours.kind === "step") {
536
550
  return base + " " + everyNthHour(stepSegment(ir.analyses.segments.hour));
@@ -538,6 +552,10 @@ function renderMinuteFrequency(ir, plan, opts) {
538
552
  return base;
539
553
  }
540
554
  function hourStepPhrase(ir) {
555
+ const cadence = unevenHourCadence(ir);
556
+ if (cadence !== null) {
557
+ return cadence;
558
+ }
541
559
  const segment = stepSegment(ir.analyses.segments.hour);
542
560
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
543
561
  }
@@ -553,6 +571,27 @@ function hourStrideCadence(stride) {
553
571
  }
554
572
  return cadence + " von " + start + " bis " + last + " Uhr";
555
573
  }
574
+ function hourListStride(values) {
575
+ if (values.length < 2) {
576
+ return null;
577
+ }
578
+ const interval = values[1] - values[0];
579
+ if (interval < 2) {
580
+ return null;
581
+ }
582
+ for (let i = 2; i < values.length; i += 1) {
583
+ if (values[i] - values[i - 1] !== interval) {
584
+ return null;
585
+ }
586
+ }
587
+ if (values[0] !== 0 && values.length < 5) {
588
+ return null;
589
+ }
590
+ return { interval, last: values[values.length - 1], start: values[0] };
591
+ }
592
+ function offsetCleanStride(stride) {
593
+ return stride.start < stride.interval && 24 % stride.interval === 0;
594
+ }
556
595
  function hourStride(ir) {
557
596
  const segments = fieldSegments(ir, "hour");
558
597
  if (!segments) {
@@ -560,12 +599,21 @@ function hourStride(ir) {
560
599
  }
561
600
  if (segments.length === 1 && segments[0].kind === "step") {
562
601
  const segment = segments[0];
602
+ if (segment.fires.length < 2) {
603
+ return null;
604
+ }
563
605
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
564
606
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
565
607
  }
566
608
  const values = singleValues(segments);
567
- const step = values && arithmeticStep(values);
568
- return step || null;
609
+ return values && hourListStride(values);
610
+ }
611
+ function unevenHourCadence(ir) {
612
+ const stride = hourStride(ir);
613
+ if (!stride || offsetCleanStride(stride)) {
614
+ return null;
615
+ }
616
+ return hourStrideCadence(stride);
569
617
  }
570
618
  function subMinuteSecond(ir) {
571
619
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -589,7 +637,7 @@ function hourCadence(ir, minute) {
589
637
  return null;
590
638
  }
591
639
  const fires = (stride.last - stride.start) / stride.interval + 1;
592
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
640
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
593
641
  return null;
594
642
  }
595
643
  const segment = fieldSegments(ir, "hour")[0];
@@ -597,10 +645,32 @@ function hourCadence(ir, minute) {
597
645
  if (confined) {
598
646
  return secondsClause(ir, "jeder Minute") + " f\xFCr eine Minute " + everyNthHour(segment);
599
647
  }
648
+ if (minute === 0 && ir.pattern.second === "0") {
649
+ return hourStrideCadence(stride);
650
+ }
600
651
  return hourCadenceLead(ir, minute) + ", " + hourStrideCadence(stride);
601
652
  }
602
653
  function hourCadenceApplies(ir) {
603
- return ir.shapes.minute === "single" && hourCadence(ir, +ir.pattern.minute) !== null;
654
+ if (ir.shapes.minute !== "single") {
655
+ return false;
656
+ }
657
+ const minute = +ir.pattern.minute;
658
+ return hourCadence(ir, minute) !== null || hourRangeCadence(ir, minute) !== null;
659
+ }
660
+ function hasHourWindow(ir) {
661
+ const segments = fieldSegments(ir, "hour");
662
+ return !!segments && segments.some(function range(segment) {
663
+ return segment.kind === "range";
664
+ });
665
+ }
666
+ function hourRangeCadence(ir, minute) {
667
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
668
+ return null;
669
+ }
670
+ return hourCadenceLead(ir, minute) + ", " + hourRangeWindowTail(ir);
671
+ }
672
+ function hourRangeWindowTail(ir) {
673
+ return joinList(hourSegmentParts(ir, 0, 0, ":"));
604
674
  }
605
675
  function renderHourRange(ir, plan, opts) {
606
676
  const window = hourWindow(
@@ -615,11 +685,16 @@ function renderHourRange(ir, plan, opts) {
615
685
  if (plan.minuteForm === "lead" && ir.pattern.minute === "0") {
616
686
  return "st\xFCndlich " + window;
617
687
  }
618
- return countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde, " + window;
688
+ return (strideFromSegments(
689
+ fieldSegments(ir, "minute"),
690
+ UNITS.minute,
691
+ "jeder Stunde"
692
+ ) ?? countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde") + ", " + window;
619
693
  }
620
694
  function renderClockTimes(ir, plan, opts) {
621
695
  if (ir.shapes.minute === "single") {
622
- const cadence = hourCadence(ir, +ir.pattern.minute);
696
+ const minute = +ir.pattern.minute;
697
+ const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
623
698
  if (cadence !== null) {
624
699
  return cadence;
625
700
  }