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/lang/fi.js CHANGED
@@ -24,12 +24,28 @@ var weekdayNumbers = {
24
24
  FRI: 5,
25
25
  SAT: 6
26
26
  };
27
+ var maxClockTimes = 6;
27
28
 
28
29
  // src/core/util.ts
29
30
  function isNonNegativeInteger(value) {
30
31
  const digits = /^\d+$/;
31
32
  return digits.test(value);
32
33
  }
34
+ function arithmeticStep(values) {
35
+ if (values.length < 5) {
36
+ return null;
37
+ }
38
+ const interval = values[1] - values[0];
39
+ if (interval < 2) {
40
+ return null;
41
+ }
42
+ for (let i = 2; i < values.length; i += 1) {
43
+ if (values[i] - values[i - 1] !== interval) {
44
+ return null;
45
+ }
46
+ }
47
+ return { start: values[0], interval, last: values[values.length - 1] };
48
+ }
33
49
  function toFieldNumber(token, numberMap) {
34
50
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
35
51
  }
@@ -165,12 +181,14 @@ var units = {
165
181
  mark: "joka tunti",
166
182
  anchor: "jokaisen tunnin",
167
183
  ela: "minuutista",
184
+ ill: "minuuttiin",
168
185
  gen: "minuutin"
169
186
  },
170
187
  second: {
171
188
  mark: "joka minuutti",
172
189
  anchor: "jokaisen minuutin",
173
190
  ela: "sekunnista",
191
+ ill: "sekuntiin",
174
192
  gen: "sekunnin"
175
193
  }
176
194
  };
@@ -226,28 +244,55 @@ function renderSecondsWithinMinute(ir, plan, opts) {
226
244
  }
227
245
  return secondsLeadClause(ir, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(ir, opts);
228
246
  }
247
+ function composeSecondsOverMinuteStep(ir, freq, opts) {
248
+ const seg = stepSegment(ir.analyses.segments.minute);
249
+ const stepPhrase = stepCycle60(seg, units.minute, opts);
250
+ if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
251
+ const bareHours = kloFromTimes(ir, freq.hours.times, opts);
252
+ return hoursFirstMinutes(bareHours, ir, opts) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
253
+ }
254
+ let hourClause = "";
255
+ if (freq.hours.kind === "during") {
256
+ hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
257
+ } else if (freq.hours.kind === "window") {
258
+ hourClause = " " + hourWindow(freq.hours, opts);
259
+ } else if (freq.hours.kind === "step") {
260
+ hourClause = " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
261
+ }
262
+ return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
263
+ }
264
+ function composeHourCadence(ir, plan, opts) {
265
+ const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
266
+ if (!clockRest || ir.shapes.minute !== "single") {
267
+ return null;
268
+ }
269
+ const minute = +ir.pattern.minute;
270
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
271
+ }
229
272
  function renderComposeSeconds(ir, plan, opts) {
273
+ const cadence = composeHourCadence(ir, plan, opts);
274
+ if (cadence !== null) {
275
+ return cadence;
276
+ }
230
277
  if (plan.rest.kind === "minuteFrequency" && ir.pattern.second !== "*") {
231
- const freq = plan.rest;
232
- const seg = stepSegment(ir.analyses.segments.minute);
233
- const stepPhrase = stepCycle60(seg, units.minute, opts);
234
- let hourClause = "";
235
- if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
236
- const bareHours = kloFromTimes(ir, freq.hours.times, opts);
237
- return hoursFirstMinutes(bareHours, ir) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
238
- } else if (freq.hours.kind === "during" && !minuteStepIsAnchored(seg)) {
239
- hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
240
- } else if (freq.hours.kind === "window") {
241
- hourClause = " " + hourWindow(freq.hours, opts);
242
- } else if (freq.hours.kind === "step") {
243
- hourClause = " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
244
- }
245
- return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
278
+ return composeSecondsOverMinuteStep(ir, plan.rest, opts);
246
279
  }
247
280
  if (plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0)) {
248
281
  return composeMinuteZero(ir, plan.rest, opts);
249
282
  }
250
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
283
+ if (isEveryOtherMinuteSeconds(ir, plan)) {
284
+ return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
285
+ }
286
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
287
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
288
+ return lead + render(ir, plan.rest, opts);
289
+ }
290
+ function isEveryOtherMinuteSeconds(ir, plan) {
291
+ if (plan.rest.kind !== "minuteFrequency" || ir.pattern.second !== "*" || ir.shapes.hour !== "wildcard") {
292
+ return false;
293
+ }
294
+ const seg = stepSegment(ir.analyses.segments.minute);
295
+ return seg.startToken === "*" && seg.interval === 2;
251
296
  }
252
297
  function composeMinuteZero(ir, rest, opts) {
253
298
  const clocks = rest.times.map(function clock(time) {
@@ -277,7 +322,7 @@ function secondsLeadClause(ir, opts) {
277
322
  if (shape === "single") {
278
323
  return atMarks(secondField, units.second, marked);
279
324
  }
280
- return atMarks(
325
+ return strideFromSegments(ir.analyses.segments.second, units.second, opts) ?? atMarks(
281
326
  joinList(segmentWords(ir.analyses.segments.second)),
282
327
  units.second,
283
328
  marked
@@ -290,20 +335,20 @@ function renderSingleMinute(ir, plan, opts) {
290
335
  return atMarks(ir.pattern.minute, units.minute, true) + trailingQualifier(ir, opts);
291
336
  }
292
337
  function renderRangeOfMinutes(ir, plan, opts) {
293
- return minutesList(ir) + trailingQualifier(ir, opts);
338
+ return minutesList(ir, opts) + trailingQualifier(ir, opts);
294
339
  }
295
340
  function renderMultipleMinutes(ir, plan, opts) {
296
- return minutesList(ir) + trailingQualifier(ir, opts);
341
+ return minutesList(ir, opts) + trailingQualifier(ir, opts);
297
342
  }
298
- function minutesList(ir) {
299
- return atMarks(
343
+ function minutesList(ir, opts) {
344
+ return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
300
345
  joinList(segmentWords(ir.analyses.segments.minute)),
301
346
  units.minute,
302
347
  true
303
348
  );
304
349
  }
305
- function bareMinutes(ir) {
306
- return atMarks(
350
+ function bareMinutes(ir, opts) {
351
+ return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
307
352
  joinList(segmentWords(ir.analyses.segments.minute)),
308
353
  units.minute,
309
354
  false
@@ -334,7 +379,11 @@ function hoursAreRangeIsolated(segments) {
334
379
  }
335
380
  return hasRange && hasSingle;
336
381
  }
337
- function hoursFirstMinutes(hoursStr, ir) {
382
+ function hoursFirstMinutes(hoursStr, ir, opts) {
383
+ const stride = strideFromSegments(ir.analyses.segments.minute, units.minute, opts);
384
+ if (stride) {
385
+ return hoursStr + " aina " + stride;
386
+ }
338
387
  return hoursStr + " aina minuuttien " + joinList(segmentWords(ir.analyses.segments.minute)) + " kohdalla";
339
388
  }
340
389
  function hourSegmentTimesWithSeka(ir, minute, second, opts) {
@@ -355,9 +404,13 @@ function hourSegmentTimesWithSeka(ir, minute, second, opts) {
355
404
  function renderMinuteFrequency(ir, plan, opts) {
356
405
  const seg = stepSegment(ir.analyses.segments.minute);
357
406
  if (plan.hours.kind === "during") {
407
+ const cadence = unevenHourCadence(ir, opts);
408
+ if (cadence !== null) {
409
+ return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(ir, opts);
410
+ }
358
411
  if (minuteStepIsAnchored(seg)) {
359
412
  const bareHours = kloFromTimes(ir, plan.hours.times, opts);
360
- return hoursFirstMinutes(bareHours, ir) + trailingQualifier(ir, opts);
413
+ return hoursFirstMinutes(bareHours, ir, opts) + trailingQualifier(ir, opts);
361
414
  }
362
415
  return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(ir, plan.hours.times, opts) + trailingQualifier(ir, opts);
363
416
  }
@@ -380,25 +433,29 @@ function renderMinuteSpanInHour(ir, plan, opts) {
380
433
  ) + trailingQualifier(ir, opts);
381
434
  }
382
435
  function renderMinutesAcrossHours(ir, plan, opts) {
436
+ const cadence = unevenHourCadence(ir, opts);
383
437
  if (plan.form === "wildcard") {
384
- return "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
438
+ return cadence ? "joka minuutti, " + cadence + trailingQualifier(ir, opts) : "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
439
+ }
440
+ if (cadence !== null) {
441
+ return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
385
442
  }
386
443
  if (hoursAreRangeIsolated(ir.analyses.segments.hour)) {
387
- return bareMinutes(ir) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
444
+ return bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
388
445
  }
389
446
  const hoursStr = kloFromTimes(ir, plan.times, opts);
390
- return hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
447
+ return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
391
448
  }
392
449
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
393
450
  const segment = stepSegment(ir.analyses.segments.hour);
451
+ const cadence = unevenHourCadence(ir, opts);
394
452
  if (plan.form === "wildcard") {
395
453
  return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
396
454
  }
397
- if (segment.startToken.indexOf("-") !== -1) {
398
- const hoursStr = kloList(segment.fires, opts);
399
- return hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
455
+ if (cadence !== null) {
456
+ return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
400
457
  }
401
- return bareMinutes(ir) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
458
+ return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
402
459
  }
403
460
  function plainHourStep(segment) {
404
461
  return segment.startToken === "*" && 24 % segment.interval === 0;
@@ -456,7 +513,7 @@ function renderHourRange(ir, plan, opts) {
456
513
  return "joka minuutti " + window + trailingQualifier(ir, opts);
457
514
  }
458
515
  if (plan.minuteForm === "range") {
459
- return hoursFirstMinutes(window, ir) + trailingQualifier(ir, opts);
516
+ return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
460
517
  }
461
518
  if (ir.pattern.minute === "0") {
462
519
  return "joka tunti " + window + trailingQualifier(ir, opts);
@@ -468,9 +525,13 @@ function renderHourRange(ir, plan, opts) {
468
525
  opts
469
526
  ) + trailingQualifier(ir, opts);
470
527
  }
471
- return hoursFirstMinutes(window, ir) + trailingQualifier(ir, opts);
528
+ return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
472
529
  }
473
530
  function renderHourStep(ir, plan, opts) {
531
+ const cadence = unevenHourCadence(ir, opts);
532
+ if (cadence !== null) {
533
+ return cadence + trailingQualifier(ir, opts);
534
+ }
474
535
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
475
536
  }
476
537
  function boundedWindow(plan) {
@@ -484,6 +545,13 @@ function hourWindow(window, opts) {
484
545
  );
485
546
  }
486
547
  function renderClockTimes(ir, plan, opts) {
548
+ if (ir.shapes.minute === "single") {
549
+ const minute = +ir.pattern.minute;
550
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
551
+ if (cadence !== null) {
552
+ return cadence;
553
+ }
554
+ }
487
555
  if (plan.times.length === 1) {
488
556
  const time = plan.times[0];
489
557
  return leadingQualifier(ir, opts) + timeWord(time.hour, time.minute, time.second, opts);
@@ -494,6 +562,12 @@ function renderClockTimes(ir, plan, opts) {
494
562
  return leadingQualifier(ir, opts) + "klo " + joinList(digits);
495
563
  }
496
564
  function renderCompactClockTimes(ir, plan, opts) {
565
+ if (plan.fold) {
566
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
567
+ if (cadence2 !== null) {
568
+ return cadence2;
569
+ }
570
+ }
497
571
  const hourSegs = ir.analyses.segments.hour;
498
572
  if (hoursAreRangeIsolated(hourSegs)) {
499
573
  if (plan.fold) {
@@ -504,14 +578,18 @@ function renderCompactClockTimes(ir, plan, opts) {
504
578
  opts
505
579
  );
506
580
  }
507
- const phrase2 = bareMinutes(ir) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
581
+ const phrase2 = bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
508
582
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase2 : phrase2;
509
583
  }
510
584
  if (plan.fold) {
511
585
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
512
586
  }
513
- const hoursStr = hourSegmentTimes(ir, 0, null, opts);
514
- const phrase = hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
587
+ const cadence = unevenHourCadence(ir, opts);
588
+ const phrase = cadence ? bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : (
589
+ // A minute list over purely enumerated hours (step fires, all singles) —
590
+ // hours-first, drop "joka tunti".
591
+ hoursFirstMinutes(hourSegmentTimes(ir, 0, null, opts), ir, opts) + trailingQualifier(ir, opts)
592
+ );
515
593
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
516
594
  }
517
595
  var renderers = {
@@ -534,20 +612,48 @@ var renderers = {
534
612
  singleMinute: renderSingleMinute,
535
613
  standaloneSeconds: renderStandaloneSeconds
536
614
  };
615
+ function renderStride(stride, opts) {
616
+ const { interval, start, last, cycle, unit } = stride;
617
+ const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
618
+ const tiles = cycle % interval === 0;
619
+ if (start === 0 && tiles) {
620
+ return cadence;
621
+ }
622
+ if (start < interval && tiles) {
623
+ return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
624
+ }
625
+ return cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last;
626
+ }
627
+ function strideFromSegments(segments, unit, opts) {
628
+ const values = singleValues(segments);
629
+ const step = values && arithmeticStep(values);
630
+ return step ? renderStride({ ...step, cycle: 60, unit }, opts) : null;
631
+ }
632
+ function singleValues(segments) {
633
+ const values = [];
634
+ for (const segment of segments) {
635
+ if (segment.kind !== "single") {
636
+ return null;
637
+ }
638
+ values.push(+segment.value);
639
+ }
640
+ return values;
641
+ }
537
642
  function stepCycle60(segment, unit, opts) {
538
643
  if (segment.startToken.indexOf("-") !== -1) {
539
644
  return atMarks(joinList(wordList(segment.fires)), unit, true);
540
645
  }
541
646
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
542
- const interval = segment.interval;
543
- const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
544
- if (start !== 0) {
545
- if (segment.fires.length <= 3) {
546
- return atMarks(joinList(wordList(segment.fires)), unit, true);
547
- }
548
- return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
647
+ if (start !== 0 && segment.fires.length <= 3) {
648
+ return atMarks(joinList(wordList(segment.fires)), unit, true);
549
649
  }
550
- return cadence;
650
+ return renderStride({
651
+ interval: segment.interval,
652
+ start,
653
+ last: segment.fires[segment.fires.length - 1],
654
+ cycle: 60,
655
+ unit
656
+ }, opts);
551
657
  }
552
658
  function stepHours(segment, opts) {
553
659
  if (segment.startToken.indexOf("-") !== -1) {
@@ -564,6 +670,121 @@ function stepHours(segment, opts) {
564
670
  }
565
671
  return cadence + " klo " + hourElatives[start] + " alkaen";
566
672
  }
673
+ function hourStrideCadence(stride, opts) {
674
+ const { start, interval, last } = stride;
675
+ const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
676
+ const tiles = 24 % interval === 0;
677
+ if (start === 0 && tiles) {
678
+ return cadence;
679
+ }
680
+ if (start < interval && tiles) {
681
+ return cadence + " klo " + hourElatives[start] + " alkaen";
682
+ }
683
+ return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
684
+ }
685
+ function hourListStride(values) {
686
+ if (values.length < 2) {
687
+ return null;
688
+ }
689
+ const interval = values[1] - values[0];
690
+ if (interval < 2) {
691
+ return null;
692
+ }
693
+ for (let i = 2; i < values.length; i += 1) {
694
+ if (values[i] - values[i - 1] !== interval) {
695
+ return null;
696
+ }
697
+ }
698
+ if (values[0] !== 0 && values.length < 5) {
699
+ return null;
700
+ }
701
+ return { interval, last: values[values.length - 1], start: values[0] };
702
+ }
703
+ function offsetCleanStride(stride) {
704
+ return stride.start < stride.interval && 24 % stride.interval === 0;
705
+ }
706
+ function hourStride(ir) {
707
+ const segments = ir.analyses.segments.hour;
708
+ if (!segments) {
709
+ return null;
710
+ }
711
+ if (segments.length === 1 && segments[0].kind === "step") {
712
+ const segment = segments[0];
713
+ if (segment.fires.length < 2) {
714
+ return null;
715
+ }
716
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
717
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
718
+ }
719
+ const values = singleValues(segments);
720
+ return values && hourListStride(values);
721
+ }
722
+ function unevenHourCadence(ir, opts) {
723
+ const stride = hourStride(ir);
724
+ if (!stride || offsetCleanStride(stride)) {
725
+ return null;
726
+ }
727
+ return hourStrideCadence(stride, opts);
728
+ }
729
+ function subMinuteSecond(ir) {
730
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
731
+ }
732
+ function hourCadenceLead(ir, minute, opts) {
733
+ if (minute === 0) {
734
+ if (subMinuteSecond(ir)) {
735
+ return secondsLeadClause(ir, opts) + " minuutin ajan";
736
+ }
737
+ return secondsLeadClause(ir, opts);
738
+ }
739
+ const minutePhrase = atMarks(String(minute), units.minute, false);
740
+ if (ir.pattern.second === "0") {
741
+ return minutePhrase;
742
+ }
743
+ return secondsLeadClause(ir, opts) + ", " + minutePhrase;
744
+ }
745
+ function hourCadence(ir, minute, opts) {
746
+ const stride = hourStride(ir);
747
+ if (!stride) {
748
+ return null;
749
+ }
750
+ const fires = (stride.last - stride.start) / stride.interval + 1;
751
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
752
+ return null;
753
+ }
754
+ const segment = ir.analyses.segments.hour[0];
755
+ const confined = minute === 0 && subMinuteSecond(ir) && ir.analyses.segments.hour.length === 1 && segment.kind === "step" && cleanHourStride(segment);
756
+ if (confined) {
757
+ return secondsLeadClause(ir, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
758
+ }
759
+ if (minute === 0 && ir.pattern.second === "0") {
760
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
761
+ }
762
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
763
+ }
764
+ function cleanHourStride(segment) {
765
+ if (segment.startToken.indexOf("-") !== -1) {
766
+ return false;
767
+ }
768
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
769
+ return 24 % segment.interval === 0 && start < segment.interval;
770
+ }
771
+ function hasHourWindow(ir) {
772
+ const segments = ir.analyses.segments.hour;
773
+ return !!segments && segments.some(function range(segment) {
774
+ return segment.kind === "range";
775
+ });
776
+ }
777
+ function hourRangeWindowTail(ir, opts) {
778
+ return ir.analyses.segments.hour.length === 1 ? hourSegmentTimes(ir, 0, null, opts) : hourSegmentTimesWithSeka(ir, 0, null, opts);
779
+ }
780
+ function hourRangeCadence(ir, minute, opts) {
781
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
782
+ return null;
783
+ }
784
+ const tail = hourRangeWindowTail(ir, opts);
785
+ const joiner = subMinuteSecond(ir) ? " " : ", ";
786
+ return hourCadenceLead(ir, minute, opts) + joiner + tail + trailingQualifier(ir, opts);
787
+ }
567
788
  function kloList(hours, opts) {
568
789
  if (hours.length === 1) {
569
790
  return timeWord(hours[0], 0, null, opts);
@@ -580,12 +801,16 @@ function kloFromTimes(ir, times, opts) {
580
801
  }
581
802
  function hourWindowsFromTimes(ir, times, opts) {
582
803
  if (times.kind === "fires") {
583
- return "klo " + joinList(times.fires.map(function window(hour) {
584
- return hourWindowDigits(hour, opts);
585
- }));
804
+ return kloList(times.fires, opts);
805
+ }
806
+ const segments = ir.analyses.segments.hour;
807
+ if (!segments.some(function ranged(segment) {
808
+ return segment.kind === "range";
809
+ })) {
810
+ return kloList(hourSegmentFires(segments), opts);
586
811
  }
587
812
  const pieces = [];
588
- ir.analyses.segments.hour.forEach(function window(segment) {
813
+ segments.forEach(function window(segment) {
589
814
  if (segment.kind === "range") {
590
815
  pieces.push(rangeDigits(
591
816
  { hour: +segment.bounds[0], minute: 0 },
@@ -602,6 +827,17 @@ function hourWindowsFromTimes(ir, times, opts) {
602
827
  });
603
828
  return "klo " + joinList(pieces);
604
829
  }
830
+ function hourSegmentFires(segments) {
831
+ const hours = [];
832
+ segments.forEach(function each(segment) {
833
+ if (segment.kind === "step") {
834
+ hours.push(...segment.fires);
835
+ } else if (segment.kind === "single") {
836
+ hours.push(+segment.value);
837
+ }
838
+ });
839
+ return hours;
840
+ }
605
841
  function hourWindowDigits(hour, opts) {
606
842
  return rangeDigits({ hour, minute: 0 }, { hour, minute: 59 }, opts);
607
843
  }