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/lang/es.js CHANGED
@@ -176,14 +176,21 @@ function secondsListAtClock(ir, rest, opts) {
176
176
  }
177
177
  function composeHourCadence(ir, plan, opts) {
178
178
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
179
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
179
+ if (!clockRest || ir.shapes.minute !== "single") {
180
+ return null;
181
+ }
182
+ const minute = +ir.pattern.minute;
183
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
184
+ }
185
+ function isPinnedMinuteSeconds(ir, plan) {
186
+ return plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step");
180
187
  }
181
188
  function renderComposeSeconds(ir, plan, opts) {
182
189
  const hourCad = composeHourCadence(ir, plan, opts);
183
190
  if (hourCad !== null) {
184
191
  return hourCad;
185
192
  }
186
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
193
+ if (isPinnedMinuteSeconds(ir, plan)) {
187
194
  return pinnedMinuteSeconds(ir, plan.rest, opts);
188
195
  }
189
196
  if (plan.rest.kind === "clockTimes" && ir.shapes.second === "list") {
@@ -199,7 +206,9 @@ function renderComposeSeconds(ir, plan, opts) {
199
206
  if (isEveryOtherMinuteSeconds(ir, plan)) {
200
207
  return secondsLeadClause(ir, opts) + " de " + render(ir, plan.rest, opts);
201
208
  }
202
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
209
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
210
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
211
+ return lead + render(ir, plan.rest, opts);
203
212
  }
204
213
  function isEveryOtherMinuteSeconds(ir, plan) {
205
214
  if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
@@ -211,7 +220,7 @@ function isEveryOtherMinuteSeconds(ir, plan) {
211
220
  function pinnedMinuteSeconds(ir, rest, opts) {
212
221
  const dayTrail = leadingQualifier(ir, opts).trimEnd();
213
222
  const trail = dayTrail ? ", " + dayTrail : "";
214
- if (+rest.times[0].minute === 0) {
223
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
215
224
  return secondsLeadClause(ir, opts) + " durante un minuto " + durationHourList(rest.times, opts) + trail;
216
225
  }
217
226
  return secondsLeadClause(ir, opts) + " de " + explicitClockList(rest.times, opts) + trail;
@@ -328,7 +337,12 @@ function renderMinuteFrequency(ir, plan, opts) {
328
337
  opts
329
338
  );
330
339
  if (plan.hours.kind === "during") {
331
- phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
340
+ const cadence = unevenHourCadence(ir, opts);
341
+ if (cadence) {
342
+ phrase += ", " + cadence;
343
+ } else {
344
+ phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
345
+ }
332
346
  } else if (plan.hours.kind === "window") {
333
347
  phrase += " " + hourWindow(plan.hours, opts);
334
348
  } else if (plan.hours.kind === "step") {
@@ -347,22 +361,30 @@ function renderMinuteSpanInHour(ir, plan, opts) {
347
361
  ) + trailingQualifier(ir, opts);
348
362
  }
349
363
  function renderMinutesAcrossHours(ir, plan, opts) {
364
+ const cadence = unevenHourCadence(ir, opts);
350
365
  if (plan.form === "wildcard") {
366
+ if (cadence !== null) {
367
+ return "cada minuto, " + cadence + trailingQualifier(ir, opts);
368
+ }
351
369
  if (singleHourStep(ir.analyses.segments.hour)) {
352
370
  return "cada minuto, " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
353
371
  }
354
372
  return "cada minuto " + hourSpanFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
355
373
  }
356
374
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir, opts);
375
+ if (cadence !== null) {
376
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
377
+ }
357
378
  return lead + ", " + atHourTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
358
379
  }
359
380
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
360
381
  const segment = stepSegment(ir.analyses.segments.hour);
382
+ const cadence = unevenHourCadence(ir, opts);
361
383
  if (plan.form === "wildcard") {
362
384
  return "cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(ir, opts);
363
385
  }
364
386
  const lead = plan.form === "list" ? minutesList(ir, opts) : minuteRangeLead(ir.pattern.minute);
365
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
387
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
366
388
  }
367
389
  function renderEveryHour(ir, plan, opts) {
368
390
  return "cada hora" + trailingQualifier(ir, opts);
@@ -382,6 +404,10 @@ function renderHourRange(ir, plan, opts) {
382
404
  return lead + ", " + window + trailingQualifier(ir, opts);
383
405
  }
384
406
  function renderHourStep(ir, plan, opts) {
407
+ const cadence = unevenHourCadence(ir, opts);
408
+ if (cadence !== null) {
409
+ return cadence + trailingQualifier(ir, opts);
410
+ }
385
411
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
386
412
  }
387
413
  function boundedWindow(plan) {
@@ -453,7 +479,8 @@ function unionYaseaSuffix(ir, opts) {
453
479
  }
454
480
  function renderClockTimes(ir, plan, opts) {
455
481
  if (ir.shapes.minute === "single") {
456
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
482
+ const minute = +ir.pattern.minute;
483
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
457
484
  if (cadence !== null) {
458
485
  return cadence;
459
486
  }
@@ -639,9 +666,9 @@ function groupClockTimesByArticle(phrases) {
639
666
  }
640
667
  function renderCompactClockTimes(ir, plan, opts) {
641
668
  if (plan.fold) {
642
- const cadence = hourCadence(ir, plan.minute, opts);
643
- if (cadence !== null) {
644
- return cadence;
669
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
670
+ if (cadence2 !== null) {
671
+ return cadence2;
645
672
  }
646
673
  const ranged = hourSegments(ir).some(function range(segment) {
647
674
  return segment.kind === "range";
@@ -651,7 +678,8 @@ function renderCompactClockTimes(ir, plan, opts) {
651
678
  }
652
679
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
653
680
  }
654
- const phrase = minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
681
+ const cadence = unevenHourCadence(ir, opts);
682
+ const phrase = cadence ? minutesList(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
655
683
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
656
684
  }
657
685
  var renderers = {
@@ -745,16 +773,46 @@ function hourStrideCadence(stride, opts) {
745
773
  }
746
774
  return cadence + " de " + timePhrase(start, 0, null, opts) + " a " + timePhrase(last, 0, null, opts);
747
775
  }
776
+ function offsetCleanStride(stride) {
777
+ return stride.start < stride.interval && 24 % stride.interval === 0;
778
+ }
779
+ function unevenHourCadence(ir, opts) {
780
+ const stride = hourStride(ir);
781
+ if (!stride || offsetCleanStride(stride)) {
782
+ return null;
783
+ }
784
+ return hourStrideCadence(stride, opts);
785
+ }
786
+ function hourListStride(values) {
787
+ if (values.length < 2) {
788
+ return null;
789
+ }
790
+ const interval = values[1] - values[0];
791
+ if (interval < 2) {
792
+ return null;
793
+ }
794
+ for (let i = 2; i < values.length; i += 1) {
795
+ if (values[i] - values[i - 1] !== interval) {
796
+ return null;
797
+ }
798
+ }
799
+ if (values[0] !== 0 && values.length < 5) {
800
+ return null;
801
+ }
802
+ return { interval, last: values[values.length - 1], start: values[0] };
803
+ }
748
804
  function hourStride(ir) {
749
805
  const segments = fieldSegments(ir, "hour");
750
806
  if (segments.length === 1 && segments[0].kind === "step") {
751
807
  const segment = segments[0];
808
+ if (segment.fires.length < 2) {
809
+ return null;
810
+ }
752
811
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
753
812
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
754
813
  }
755
814
  const values = singleValues(segments);
756
- const step = values && arithmeticStep(values);
757
- return step || null;
815
+ return values && hourListStride(values);
758
816
  }
759
817
  function subMinuteSecond(ir) {
760
818
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -778,13 +836,16 @@ function hourCadence(ir, minute, opts) {
778
836
  return null;
779
837
  }
780
838
  const fires = (stride.last - stride.start) / stride.interval + 1;
781
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
839
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
782
840
  return null;
783
841
  }
784
842
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
785
843
  if (confinement) {
786
844
  return secondsClause(ir, "minuto", opts) + " durante un minuto, " + stepHourSpan(confinement, opts) + trailingQualifier(ir, opts);
787
845
  }
846
+ if (minute === 0 && ir.pattern.second === "0") {
847
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
848
+ }
788
849
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
789
850
  }
790
851
  function cleanStrideSegment(ir) {
@@ -795,6 +856,20 @@ function cleanStrideSegment(ir) {
795
856
  }
796
857
  return segment;
797
858
  }
859
+ function hasHourWindow(ir) {
860
+ return hourSegments(ir).some(function range(segment) {
861
+ return segment.kind === "range";
862
+ });
863
+ }
864
+ function hourRangeCadence(ir, minute, opts) {
865
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
866
+ return null;
867
+ }
868
+ if (subMinuteSecond(ir)) {
869
+ return secondsClause(ir, "minuto", opts) + " durante un minuto, durante las horas " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
870
+ }
871
+ return hourCadenceLead(ir, minute, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
872
+ }
798
873
  function atTimes(hours, opts) {
799
874
  return hours.map(function each(hour) {
800
875
  return atTime(timePhrase(hour, 0, null, opts));
package/dist/lang/fi.cjs CHANGED
@@ -289,7 +289,11 @@ function composeSecondsOverMinuteStep(ir, freq, opts) {
289
289
  }
290
290
  function composeHourCadence(ir, plan, opts) {
291
291
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
292
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
292
+ if (!clockRest || ir.shapes.minute !== "single") {
293
+ return null;
294
+ }
295
+ const minute = +ir.pattern.minute;
296
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
293
297
  }
294
298
  function renderComposeSeconds(ir, plan, opts) {
295
299
  const cadence = composeHourCadence(ir, plan, opts);
@@ -305,7 +309,9 @@ function renderComposeSeconds(ir, plan, opts) {
305
309
  if (isEveryOtherMinuteSeconds(ir, plan)) {
306
310
  return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
307
311
  }
308
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
312
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
313
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
314
+ return lead + render(ir, plan.rest, opts);
309
315
  }
310
316
  function isEveryOtherMinuteSeconds(ir, plan) {
311
317
  if (plan.rest.kind !== "minuteFrequency" || ir.pattern.second !== "*" || ir.shapes.hour !== "wildcard") {
@@ -424,6 +430,10 @@ function hourSegmentTimesWithSeka(ir, minute, second, opts) {
424
430
  function renderMinuteFrequency(ir, plan, opts) {
425
431
  const seg = stepSegment(ir.analyses.segments.minute);
426
432
  if (plan.hours.kind === "during") {
433
+ const cadence = unevenHourCadence(ir, opts);
434
+ if (cadence !== null) {
435
+ return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(ir, opts);
436
+ }
427
437
  if (minuteStepIsAnchored(seg)) {
428
438
  const bareHours = kloFromTimes(ir, plan.hours.times, opts);
429
439
  return hoursFirstMinutes(bareHours, ir, opts) + trailingQualifier(ir, opts);
@@ -449,8 +459,12 @@ function renderMinuteSpanInHour(ir, plan, opts) {
449
459
  ) + trailingQualifier(ir, opts);
450
460
  }
451
461
  function renderMinutesAcrossHours(ir, plan, opts) {
462
+ const cadence = unevenHourCadence(ir, opts);
452
463
  if (plan.form === "wildcard") {
453
- return "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
464
+ return cadence ? "joka minuutti, " + cadence + trailingQualifier(ir, opts) : "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
465
+ }
466
+ if (cadence !== null) {
467
+ return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
454
468
  }
455
469
  if (hoursAreRangeIsolated(ir.analyses.segments.hour)) {
456
470
  return bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
@@ -460,12 +474,12 @@ function renderMinutesAcrossHours(ir, plan, opts) {
460
474
  }
461
475
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
462
476
  const segment = stepSegment(ir.analyses.segments.hour);
477
+ const cadence = unevenHourCadence(ir, opts);
463
478
  if (plan.form === "wildcard") {
464
479
  return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
465
480
  }
466
- if (segment.startToken.indexOf("-") !== -1) {
467
- const hoursStr = kloList(segment.fires, opts);
468
- return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
481
+ if (cadence !== null) {
482
+ return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
469
483
  }
470
484
  return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
471
485
  }
@@ -540,6 +554,10 @@ function renderHourRange(ir, plan, opts) {
540
554
  return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
541
555
  }
542
556
  function renderHourStep(ir, plan, opts) {
557
+ const cadence = unevenHourCadence(ir, opts);
558
+ if (cadence !== null) {
559
+ return cadence + trailingQualifier(ir, opts);
560
+ }
543
561
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
544
562
  }
545
563
  function boundedWindow(plan) {
@@ -554,7 +572,8 @@ function hourWindow(window, opts) {
554
572
  }
555
573
  function renderClockTimes(ir, plan, opts) {
556
574
  if (ir.shapes.minute === "single") {
557
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
575
+ const minute = +ir.pattern.minute;
576
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
558
577
  if (cadence !== null) {
559
578
  return cadence;
560
579
  }
@@ -570,9 +589,9 @@ function renderClockTimes(ir, plan, opts) {
570
589
  }
571
590
  function renderCompactClockTimes(ir, plan, opts) {
572
591
  if (plan.fold) {
573
- const cadence = hourCadence(ir, plan.minute, opts);
574
- if (cadence !== null) {
575
- return cadence;
592
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
593
+ if (cadence2 !== null) {
594
+ return cadence2;
576
595
  }
577
596
  }
578
597
  const hourSegs = ir.analyses.segments.hour;
@@ -591,8 +610,12 @@ function renderCompactClockTimes(ir, plan, opts) {
591
610
  if (plan.fold) {
592
611
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
593
612
  }
594
- const hoursStr = hourSegmentTimes(ir, 0, null, opts);
595
- const phrase = hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
613
+ const cadence = unevenHourCadence(ir, opts);
614
+ const phrase = cadence ? bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : (
615
+ // A minute list over purely enumerated hours (step fires, all singles) —
616
+ // hours-first, drop "joka tunti".
617
+ hoursFirstMinutes(hourSegmentTimes(ir, 0, null, opts), ir, opts) + trailingQualifier(ir, opts)
618
+ );
596
619
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
597
620
  }
598
621
  var renderers = {
@@ -685,6 +708,27 @@ function hourStrideCadence(stride, opts) {
685
708
  }
686
709
  return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
687
710
  }
711
+ function hourListStride(values) {
712
+ if (values.length < 2) {
713
+ return null;
714
+ }
715
+ const interval = values[1] - values[0];
716
+ if (interval < 2) {
717
+ return null;
718
+ }
719
+ for (let i = 2; i < values.length; i += 1) {
720
+ if (values[i] - values[i - 1] !== interval) {
721
+ return null;
722
+ }
723
+ }
724
+ if (values[0] !== 0 && values.length < 5) {
725
+ return null;
726
+ }
727
+ return { interval, last: values[values.length - 1], start: values[0] };
728
+ }
729
+ function offsetCleanStride(stride) {
730
+ return stride.start < stride.interval && 24 % stride.interval === 0;
731
+ }
688
732
  function hourStride(ir) {
689
733
  const segments = ir.analyses.segments.hour;
690
734
  if (!segments) {
@@ -692,12 +736,21 @@ function hourStride(ir) {
692
736
  }
693
737
  if (segments.length === 1 && segments[0].kind === "step") {
694
738
  const segment = segments[0];
739
+ if (segment.fires.length < 2) {
740
+ return null;
741
+ }
695
742
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
696
743
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
697
744
  }
698
745
  const values = singleValues(segments);
699
- const step = values && arithmeticStep(values);
700
- return step || null;
746
+ return values && hourListStride(values);
747
+ }
748
+ function unevenHourCadence(ir, opts) {
749
+ const stride = hourStride(ir);
750
+ if (!stride || offsetCleanStride(stride)) {
751
+ return null;
752
+ }
753
+ return hourStrideCadence(stride, opts);
701
754
  }
702
755
  function subMinuteSecond(ir) {
703
756
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -721,7 +774,7 @@ function hourCadence(ir, minute, opts) {
721
774
  return null;
722
775
  }
723
776
  const fires = (stride.last - stride.start) / stride.interval + 1;
724
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
777
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
725
778
  return null;
726
779
  }
727
780
  const segment = ir.analyses.segments.hour[0];
@@ -729,6 +782,9 @@ function hourCadence(ir, minute, opts) {
729
782
  if (confined) {
730
783
  return secondsLeadClause(ir, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
731
784
  }
785
+ if (minute === 0 && ir.pattern.second === "0") {
786
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
787
+ }
732
788
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
733
789
  }
734
790
  function cleanHourStride(segment) {
@@ -738,6 +794,23 @@ function cleanHourStride(segment) {
738
794
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
739
795
  return 24 % segment.interval === 0 && start < segment.interval;
740
796
  }
797
+ function hasHourWindow(ir) {
798
+ const segments = ir.analyses.segments.hour;
799
+ return !!segments && segments.some(function range(segment) {
800
+ return segment.kind === "range";
801
+ });
802
+ }
803
+ function hourRangeWindowTail(ir, opts) {
804
+ return ir.analyses.segments.hour.length === 1 ? hourSegmentTimes(ir, 0, null, opts) : hourSegmentTimesWithSeka(ir, 0, null, opts);
805
+ }
806
+ function hourRangeCadence(ir, minute, opts) {
807
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
808
+ return null;
809
+ }
810
+ const tail = hourRangeWindowTail(ir, opts);
811
+ const joiner = subMinuteSecond(ir) ? " " : ", ";
812
+ return hourCadenceLead(ir, minute, opts) + joiner + tail + trailingQualifier(ir, opts);
813
+ }
741
814
  function kloList(hours, opts) {
742
815
  if (hours.length === 1) {
743
816
  return timeWord(hours[0], 0, null, opts);
@@ -754,12 +827,16 @@ function kloFromTimes(ir, times, opts) {
754
827
  }
755
828
  function hourWindowsFromTimes(ir, times, opts) {
756
829
  if (times.kind === "fires") {
757
- return "klo " + joinList(times.fires.map(function window(hour) {
758
- return hourWindowDigits(hour, opts);
759
- }));
830
+ return kloList(times.fires, opts);
831
+ }
832
+ const segments = ir.analyses.segments.hour;
833
+ if (!segments.some(function ranged(segment) {
834
+ return segment.kind === "range";
835
+ })) {
836
+ return kloList(hourSegmentFires(segments), opts);
760
837
  }
761
838
  const pieces = [];
762
- ir.analyses.segments.hour.forEach(function window(segment) {
839
+ segments.forEach(function window(segment) {
763
840
  if (segment.kind === "range") {
764
841
  pieces.push(rangeDigits(
765
842
  { hour: +segment.bounds[0], minute: 0 },
@@ -776,6 +853,17 @@ function hourWindowsFromTimes(ir, times, opts) {
776
853
  });
777
854
  return "klo " + joinList(pieces);
778
855
  }
856
+ function hourSegmentFires(segments) {
857
+ const hours = [];
858
+ segments.forEach(function each(segment) {
859
+ if (segment.kind === "step") {
860
+ hours.push(...segment.fires);
861
+ } else if (segment.kind === "single") {
862
+ hours.push(+segment.value);
863
+ }
864
+ });
865
+ return hours;
866
+ }
779
867
  function hourWindowDigits(hour, opts) {
780
868
  return rangeDigits({ hour, minute: 0 }, { hour, minute: 59 }, opts);
781
869
  }