cronli5 0.1.0 → 0.1.2

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
@@ -168,6 +168,11 @@ function throwInvalidField(value, field) {
168
168
  }
169
169
 
170
170
  // src/core/normalize.ts
171
+ var timeFieldCycle = {
172
+ hour: 24,
173
+ minute: 60,
174
+ second: 60
175
+ };
171
176
  function applyQuartzAliases(cronPattern) {
172
177
  fieldOrder.forEach(function apply(field) {
173
178
  const aliases = fieldSpecs[field].aliases;
@@ -184,21 +189,26 @@ function normalizeCronPattern(cronPattern) {
184
189
  cronPattern[field] = value;
185
190
  return;
186
191
  }
187
- cronPattern[field] = normalizeField(value, fieldSpecs[field]);
192
+ cronPattern[field] = normalizeField(value, field, fieldSpecs[field]);
188
193
  });
189
194
  return cronPattern;
190
195
  }
191
- function normalizeField(value, spec) {
196
+ function normalizeField(value, field, spec) {
192
197
  const stringValue = "" + value;
193
198
  if (stringValue === "*") {
194
199
  return stringValue;
195
200
  }
201
+ const cycle = timeFieldCycle[field];
196
202
  const segments = stringValue.split(",").map(function canonical(segment) {
197
- return collapseDegenerateRange(
198
- collapseOnceStep(collapseUnitStep(segment, spec), spec),
199
- spec
203
+ return enumerateNonUniformStep(
204
+ collapseDegenerateRange(
205
+ collapseOnceStep(collapseUnitStep(segment, spec), spec),
206
+ spec
207
+ ),
208
+ spec,
209
+ cycle
200
210
  );
201
- });
211
+ }).join(",").split(",");
202
212
  if (segments.indexOf("*") !== -1) {
203
213
  return "*";
204
214
  }
@@ -232,6 +242,22 @@ function collapseOnceStep(segment, spec) {
232
242
  }
233
243
  return start === "*" ? "" + spec.min : start;
234
244
  }
245
+ function enumerateNonUniformStep(segment, spec, cycle) {
246
+ const parts = segment.split("/");
247
+ if (typeof cycle !== "number" || parts.length !== 2 || includes(parts[0], "-")) {
248
+ return segment;
249
+ }
250
+ const interval = +parts[1];
251
+ const start = parts[0] === "*" ? spec.min : toFieldNumber(parts[0]);
252
+ if (cycle % interval === 0 && start < interval) {
253
+ return segment;
254
+ }
255
+ const fires = [];
256
+ for (let value = start; value <= spec.top; value += interval) {
257
+ fires.push(value);
258
+ }
259
+ return fires.join(",");
260
+ }
235
261
  function collapseDegenerateRange(segment, spec) {
236
262
  const start = segment.split("/")[0];
237
263
  if (!includes(start, "-")) {
@@ -506,7 +532,7 @@ function planSeconds(pattern, shapes, analyses) {
506
532
  }
507
533
  return {
508
534
  kind: "composeSeconds",
509
- rest: planMinutes(pattern, shapes, analyses) || planHours(pattern, shapes, analyses)
535
+ rest: planMinutes(pattern, shapes, analyses, true) || planHours(pattern, shapes, analyses, true)
510
536
  };
511
537
  }
512
538
  function planStandaloneSeconds(pattern, shapes) {
@@ -521,7 +547,7 @@ function planStandaloneSeconds(pattern, shapes) {
521
547
  }
522
548
  return { kind: "standaloneSeconds" };
523
549
  }
524
- function planMinutes(pattern, shapes, analyses) {
550
+ function planMinutes(pattern, shapes, analyses, subMinuteSecond = false) {
525
551
  if (shapes.minute === "step") {
526
552
  return {
527
553
  hours: planFrequencyHours(pattern, shapes, analyses),
@@ -544,7 +570,7 @@ function planMinutes(pattern, shapes, analyses) {
544
570
  return underStep;
545
571
  }
546
572
  if (pattern.hour === "*") {
547
- return planMinutesUnderOpenHour(pattern, shapes);
573
+ return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond);
548
574
  }
549
575
  }
550
576
  function cleanHourStride(hourField) {
@@ -614,7 +640,7 @@ function planMinutesAcrossHours(pattern, shapes) {
614
640
  }
615
641
  return null;
616
642
  }
617
- function planMinutesUnderOpenHour(pattern, shapes) {
643
+ function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond) {
618
644
  if (shapes.minute === "range") {
619
645
  return { kind: "rangeOfMinutes" };
620
646
  }
@@ -624,39 +650,45 @@ function planMinutesUnderOpenHour(pattern, shapes) {
624
650
  if (pattern.minute === "*") {
625
651
  return { kind: "everyMinute" };
626
652
  }
627
- if (pattern.minute !== "0") {
653
+ if (pattern.minute !== "0" || subMinuteSecond) {
628
654
  return { kind: "singleMinute" };
629
655
  }
630
656
  }
631
- function planHours(pattern, shapes, analyses) {
632
- if (shapes.hour === "range") {
633
- const bounds = pattern.hour.split("-");
634
- let minuteForm = "lead";
635
- if (pattern.minute === "*") {
636
- minuteForm = "wildcard";
637
- } else if (shapes.minute === "range") {
638
- minuteForm = "range";
639
- }
640
- return {
641
- from: +bounds[0],
642
- kind: "hourRange",
643
- last: analyses.lastMinuteFire,
644
- minuteForm,
645
- to: +bounds[1]
646
- };
657
+ function planHours(pattern, shapes, analyses, subMinuteSecond = false) {
658
+ const absorbsMinuteZero = subMinuteSecond && pattern.minute === "0";
659
+ if (shapes.hour === "range" && !absorbsMinuteZero) {
660
+ return planHourRange(pattern, shapes, analyses);
647
661
  }
648
- if (shapes.hour === "step" && pattern.minute === "0") {
662
+ if (shapes.hour === "step" && pattern.minute === "0" && !subMinuteSecond) {
649
663
  return { kind: "hourStep" };
650
664
  }
651
- if (pattern.hour === "*") {
665
+ if (pattern.hour === "*" && !absorbsMinuteZero) {
652
666
  return { kind: "everyHour" };
653
667
  }
654
- return planClockTimes(pattern, analyses);
668
+ return planClockTimes(pattern, analyses, absorbsMinuteZero);
669
+ }
670
+ function planHourRange(pattern, shapes, analyses) {
671
+ const bounds = pattern.hour.split("-");
672
+ let minuteForm = "lead";
673
+ if (pattern.minute === "*") {
674
+ minuteForm = "wildcard";
675
+ } else if (shapes.minute === "range") {
676
+ minuteForm = "range";
677
+ }
678
+ const multiValued = shapes.minute === "range" || shapes.minute === "list";
679
+ return {
680
+ boundMinute: multiValued ? null : analyses.lastMinuteFire,
681
+ from: +bounds[0],
682
+ kind: "hourRange",
683
+ last: analyses.lastMinuteFire,
684
+ minuteForm,
685
+ to: +bounds[1]
686
+ };
655
687
  }
656
- function planClockTimes(pattern, analyses) {
688
+ function planClockTimes(pattern, analyses, enumerate = false) {
657
689
  const hours = enumerateFires(pattern.hour, 0, 23);
658
690
  const minutes = enumerateValues(pattern.minute);
659
- if (hours.length * minutes.length > maxClockTimes) {
691
+ if (!enumerate && hours.length * minutes.length > maxClockTimes) {
660
692
  return {
661
693
  fold: minutes.length === 1,
662
694
  kind: "compactClockTimes",
@@ -970,7 +1002,7 @@ function renderEveryHour(ir, plan, opts) {
970
1002
  return "every hour" + trailingQualifier(ir, opts);
971
1003
  }
972
1004
  function renderHourRange(ir, plan, opts) {
973
- const window = hourWindow(plan, opts);
1005
+ const window = hourWindow(boundedWindow(plan), opts);
974
1006
  if (plan.minuteForm === "wildcard") {
975
1007
  return "every minute " + window + trailingQualifier(ir, opts);
976
1008
  }
@@ -993,6 +1025,9 @@ function rangeMinuteLead(ir, opts) {
993
1025
  function renderHourStep(ir, plan, opts) {
994
1026
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
995
1027
  }
1028
+ function boundedWindow(plan) {
1029
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
1030
+ }
996
1031
  function hourWindow(window, opts) {
997
1032
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
998
1033
  }
@@ -1091,13 +1126,7 @@ function stepCycle60(segment, unit, anchor, opts) {
1091
1126
  }
1092
1127
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
1093
1128
  }
1094
- if (60 % interval === 0) {
1095
- return "every " + getNumber(interval, opts) + " " + unit + "s";
1096
- }
1097
- if (segment.fires.length <= 2) {
1098
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1099
- }
1100
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
1129
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
1101
1130
  }
1102
1131
  function stepHours(segment, opts) {
1103
1132
  if (segment.startToken.indexOf("-") !== -1) {
@@ -1105,15 +1134,12 @@ function stepHours(segment, opts) {
1105
1134
  }
1106
1135
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
1107
1136
  const interval = segment.interval;
1108
- if (start === 0 && 24 % interval === 0) {
1137
+ if (start === 0) {
1109
1138
  return "every " + getNumber(interval, opts) + " hours";
1110
1139
  }
1111
1140
  if (segment.fires.length <= 3) {
1112
1141
  return "at " + hourTimes(segment.fires, opts);
1113
1142
  }
1114
- if (start === 0) {
1115
- return "every " + getNumber(interval, opts) + " hours from midnight";
1116
- }
1117
1143
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
1118
1144
  }
1119
1145
  function seriesNumber(values, opts) {
package/dist/lang/de.cjs CHANGED
@@ -352,7 +352,7 @@ function duringHours(ir, times, sep) {
352
352
  if (windows.length <= 3 || times.kind !== "fires") {
353
353
  return joinList(windows);
354
354
  }
355
- return "in den Stunden von " + joinList(times.fires.map(String)) + " Uhr";
355
+ return "in den Stunden " + joinList(times.fires.map(String)) + " Uhr";
356
356
  }
357
357
  function renderEverySecond() {
358
358
  return everyUnit(UNITS.second);
@@ -433,7 +433,12 @@ function hourStepPhrase(ir) {
433
433
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
434
434
  }
435
435
  function renderHourRange(ir, plan, opts) {
436
- const window = hourWindow(plan.from, plan.to, plan.last, opts.style.sep);
436
+ const window = hourWindow(
437
+ plan.from,
438
+ plan.to,
439
+ plan.boundMinute ?? 0,
440
+ opts.style.sep
441
+ );
437
442
  if (plan.minuteForm === "wildcard") {
438
443
  return "jede Minute " + window;
439
444
  }
@@ -540,7 +545,9 @@ var de2 = {
540
545
  fallback: "ein unlesbares Cron-Muster",
541
546
  options: normalizeOptions,
542
547
  reboot: "beim Systemstart",
543
- sentence: (description) => "L\xE4uft " + description + "."
548
+ // A description ending in a German ordinal already carries its period
549
+ // ("…am 8."), so closing the sentence must not double it.
550
+ sentence: (description) => "L\xE4uft " + description + (description.endsWith(".") ? "" : ".")
544
551
  };
545
552
  var index_default = de2;
546
553
  module.exports = module.exports.default;
package/dist/lang/de.js CHANGED
@@ -326,7 +326,7 @@ function duringHours(ir, times, sep) {
326
326
  if (windows.length <= 3 || times.kind !== "fires") {
327
327
  return joinList(windows);
328
328
  }
329
- return "in den Stunden von " + joinList(times.fires.map(String)) + " Uhr";
329
+ return "in den Stunden " + joinList(times.fires.map(String)) + " Uhr";
330
330
  }
331
331
  function renderEverySecond() {
332
332
  return everyUnit(UNITS.second);
@@ -407,7 +407,12 @@ function hourStepPhrase(ir) {
407
407
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
408
408
  }
409
409
  function renderHourRange(ir, plan, opts) {
410
- const window = hourWindow(plan.from, plan.to, plan.last, opts.style.sep);
410
+ const window = hourWindow(
411
+ plan.from,
412
+ plan.to,
413
+ plan.boundMinute ?? 0,
414
+ opts.style.sep
415
+ );
411
416
  if (plan.minuteForm === "wildcard") {
412
417
  return "jede Minute " + window;
413
418
  }
@@ -514,7 +519,9 @@ var de2 = {
514
519
  fallback: "ein unlesbares Cron-Muster",
515
520
  options: normalizeOptions,
516
521
  reboot: "beim Systemstart",
517
- sentence: (description) => "L\xE4uft " + description + "."
522
+ // A description ending in a German ordinal already carries its period
523
+ // ("…am 8."), so closing the sentence must not double it.
524
+ sentence: (description) => "L\xE4uft " + description + (description.endsWith(".") ? "" : ".")
518
525
  };
519
526
  var index_default = de2;
520
527
  export {
package/dist/lang/en.cjs CHANGED
@@ -307,7 +307,7 @@ function renderEveryHour(ir, plan, opts) {
307
307
  return "every hour" + trailingQualifier(ir, opts);
308
308
  }
309
309
  function renderHourRange(ir, plan, opts) {
310
- const window = hourWindow(plan, opts);
310
+ const window = hourWindow(boundedWindow(plan), opts);
311
311
  if (plan.minuteForm === "wildcard") {
312
312
  return "every minute " + window + trailingQualifier(ir, opts);
313
313
  }
@@ -330,6 +330,9 @@ function rangeMinuteLead(ir, opts) {
330
330
  function renderHourStep(ir, plan, opts) {
331
331
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
332
332
  }
333
+ function boundedWindow(plan) {
334
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
335
+ }
333
336
  function hourWindow(window, opts) {
334
337
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
335
338
  }
@@ -428,13 +431,7 @@ function stepCycle60(segment, unit, anchor, opts) {
428
431
  }
429
432
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
430
433
  }
431
- if (60 % interval === 0) {
432
- return "every " + getNumber(interval, opts) + " " + unit + "s";
433
- }
434
- if (segment.fires.length <= 2) {
435
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
436
- }
437
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
434
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
438
435
  }
439
436
  function stepHours(segment, opts) {
440
437
  if (segment.startToken.indexOf("-") !== -1) {
@@ -442,15 +439,12 @@ function stepHours(segment, opts) {
442
439
  }
443
440
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
444
441
  const interval = segment.interval;
445
- if (start === 0 && 24 % interval === 0) {
442
+ if (start === 0) {
446
443
  return "every " + getNumber(interval, opts) + " hours";
447
444
  }
448
445
  if (segment.fires.length <= 3) {
449
446
  return "at " + hourTimes(segment.fires, opts);
450
447
  }
451
- if (start === 0) {
452
- return "every " + getNumber(interval, opts) + " hours from midnight";
453
- }
454
448
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
455
449
  }
456
450
  function seriesNumber(values, opts) {
package/dist/lang/en.js CHANGED
@@ -281,7 +281,7 @@ function renderEveryHour(ir, plan, opts) {
281
281
  return "every hour" + trailingQualifier(ir, opts);
282
282
  }
283
283
  function renderHourRange(ir, plan, opts) {
284
- const window = hourWindow(plan, opts);
284
+ const window = hourWindow(boundedWindow(plan), opts);
285
285
  if (plan.minuteForm === "wildcard") {
286
286
  return "every minute " + window + trailingQualifier(ir, opts);
287
287
  }
@@ -304,6 +304,9 @@ function rangeMinuteLead(ir, opts) {
304
304
  function renderHourStep(ir, plan, opts) {
305
305
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
306
306
  }
307
+ function boundedWindow(plan) {
308
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
309
+ }
307
310
  function hourWindow(window, opts) {
308
311
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
309
312
  }
@@ -402,13 +405,7 @@ function stepCycle60(segment, unit, anchor, opts) {
402
405
  }
403
406
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
404
407
  }
405
- if (60 % interval === 0) {
406
- return "every " + getNumber(interval, opts) + " " + unit + "s";
407
- }
408
- if (segment.fires.length <= 2) {
409
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
410
- }
411
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
408
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
412
409
  }
413
410
  function stepHours(segment, opts) {
414
411
  if (segment.startToken.indexOf("-") !== -1) {
@@ -416,15 +413,12 @@ function stepHours(segment, opts) {
416
413
  }
417
414
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
418
415
  const interval = segment.interval;
419
- if (start === 0 && 24 % interval === 0) {
416
+ if (start === 0) {
420
417
  return "every " + getNumber(interval, opts) + " hours";
421
418
  }
422
419
  if (segment.fires.length <= 3) {
423
420
  return "at " + hourTimes(segment.fires, opts);
424
421
  }
425
- if (start === 0) {
426
- return "every " + getNumber(interval, opts) + " hours from midnight";
427
- }
428
422
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
429
423
  }
430
424
  function seriesNumber(values, opts) {
package/dist/lang/es.cjs CHANGED
@@ -331,7 +331,7 @@ function renderEveryHour(ir, plan, opts) {
331
331
  return "cada hora" + trailingQualifier(ir, opts);
332
332
  }
333
333
  function renderHourRange(ir, plan, opts) {
334
- const window = hourWindow(plan, opts);
334
+ const window = hourWindow(boundedWindow(plan), opts);
335
335
  if (plan.minuteForm === "wildcard") {
336
336
  return "cada minuto " + window + trailingQualifier(ir, opts);
337
337
  }
@@ -347,6 +347,9 @@ function renderHourRange(ir, plan, opts) {
347
347
  function renderHourStep(ir, plan, opts) {
348
348
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
349
349
  }
350
+ function boundedWindow(plan) {
351
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
352
+ }
350
353
  function hourWindow(window, opts) {
351
354
  return timeRange(
352
355
  { hour: window.from, minute: 0 },
@@ -597,13 +600,7 @@ function stepCycle60(segment, unit, anchor, opts) {
597
600
  }
598
601
  return "cada " + numero(interval, opts) + " " + unit + "s a partir del " + unit + " " + start + " de cada " + anchor;
599
602
  }
600
- if (60 % interval === 0) {
601
- return "cada " + numero(interval, opts) + " " + unit + "s";
602
- }
603
- if (segment.fires.length <= 2) {
604
- return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
605
- }
606
- return "cada " + numero(interval, opts) + " " + unit + "s de cada " + anchor;
603
+ return "cada " + numero(interval, opts) + " " + unit + "s";
607
604
  }
608
605
  function stepHours(segment, opts) {
609
606
  if (segment.startToken.indexOf("-") !== -1) {
@@ -611,15 +608,12 @@ function stepHours(segment, opts) {
611
608
  }
612
609
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
613
610
  const interval = segment.interval;
614
- if (start === 0 && 24 % interval === 0) {
611
+ if (start === 0) {
615
612
  return "cada " + numero(interval, opts) + " horas";
616
613
  }
617
614
  if (segment.fires.length <= 3) {
618
615
  return groupClockTimesByArticle(atTimes(segment.fires, opts));
619
616
  }
620
- if (start === 0) {
621
- return "cada " + numero(interval, opts) + " horas desde medianoche";
622
- }
623
617
  return "cada " + numero(interval, opts) + " horas a partir de " + timePhrase(start, 0, null, opts);
624
618
  }
625
619
  function atTimes(hours, opts) {
package/dist/lang/es.js CHANGED
@@ -305,7 +305,7 @@ function renderEveryHour(ir, plan, opts) {
305
305
  return "cada hora" + trailingQualifier(ir, opts);
306
306
  }
307
307
  function renderHourRange(ir, plan, opts) {
308
- const window = hourWindow(plan, opts);
308
+ const window = hourWindow(boundedWindow(plan), opts);
309
309
  if (plan.minuteForm === "wildcard") {
310
310
  return "cada minuto " + window + trailingQualifier(ir, opts);
311
311
  }
@@ -321,6 +321,9 @@ function renderHourRange(ir, plan, opts) {
321
321
  function renderHourStep(ir, plan, opts) {
322
322
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
323
323
  }
324
+ function boundedWindow(plan) {
325
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
326
+ }
324
327
  function hourWindow(window, opts) {
325
328
  return timeRange(
326
329
  { hour: window.from, minute: 0 },
@@ -571,13 +574,7 @@ function stepCycle60(segment, unit, anchor, opts) {
571
574
  }
572
575
  return "cada " + numero(interval, opts) + " " + unit + "s a partir del " + unit + " " + start + " de cada " + anchor;
573
576
  }
574
- if (60 % interval === 0) {
575
- return "cada " + numero(interval, opts) + " " + unit + "s";
576
- }
577
- if (segment.fires.length <= 2) {
578
- return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
579
- }
580
- return "cada " + numero(interval, opts) + " " + unit + "s de cada " + anchor;
577
+ return "cada " + numero(interval, opts) + " " + unit + "s";
581
578
  }
582
579
  function stepHours(segment, opts) {
583
580
  if (segment.startToken.indexOf("-") !== -1) {
@@ -585,15 +582,12 @@ function stepHours(segment, opts) {
585
582
  }
586
583
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
587
584
  const interval = segment.interval;
588
- if (start === 0 && 24 % interval === 0) {
585
+ if (start === 0) {
589
586
  return "cada " + numero(interval, opts) + " horas";
590
587
  }
591
588
  if (segment.fires.length <= 3) {
592
589
  return groupClockTimesByArticle(atTimes(segment.fires, opts));
593
590
  }
594
- if (start === 0) {
595
- return "cada " + numero(interval, opts) + " horas desde medianoche";
596
- }
597
591
  return "cada " + numero(interval, opts) + " horas a partir de " + timePhrase(start, 0, null, opts);
598
592
  }
599
593
  function atTimes(hours, opts) {
package/dist/lang/fi.cjs CHANGED
@@ -194,15 +194,13 @@ var units = {
194
194
  mark: "joka tunti",
195
195
  anchor: "jokaisen tunnin",
196
196
  ela: "minuutista",
197
- gen: "minuutin",
198
- restart: "tasatunnista alkaen"
197
+ gen: "minuutin"
199
198
  },
200
199
  second: {
201
200
  mark: "joka minuutti",
202
201
  anchor: "jokaisen minuutin",
203
202
  ela: "sekunnista",
204
- gen: "sekunnin",
205
- restart: "joka minuutti"
203
+ gen: "sekunnin"
206
204
  }
207
205
  };
208
206
  function atMarks(values, unit, withMark) {
@@ -465,7 +463,7 @@ function renderEveryHour(ir, plan, opts) {
465
463
  return "joka tunti" + trailingQualifier(ir, opts);
466
464
  }
467
465
  function renderHourRange(ir, plan, opts) {
468
- const window = hourWindow(plan, opts);
466
+ const window = hourWindow(boundedWindow(plan), opts);
469
467
  if (plan.minuteForm === "wildcard") {
470
468
  return "joka minuutti " + window + trailingQualifier(ir, opts);
471
469
  }
@@ -487,6 +485,9 @@ function renderHourRange(ir, plan, opts) {
487
485
  function renderHourStep(ir, plan, opts) {
488
486
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
489
487
  }
488
+ function boundedWindow(plan) {
489
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
490
+ }
490
491
  function hourWindow(window, opts) {
491
492
  return kloRange(
492
493
  { hour: window.from, minute: 0 },
@@ -558,13 +559,7 @@ function stepCycle60(segment, unit, opts) {
558
559
  }
559
560
  return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
560
561
  }
561
- if (60 % interval === 0) {
562
- return cadence;
563
- }
564
- if (segment.fires.length <= 2) {
565
- return atMarks(joinList(wordList(segment.fires)), unit, true);
566
- }
567
- return cadence + " " + unit.restart;
562
+ return cadence;
568
563
  }
569
564
  function stepHours(segment, opts) {
570
565
  if (segment.startToken.indexOf("-") !== -1) {
@@ -573,15 +568,12 @@ function stepHours(segment, opts) {
573
568
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
574
569
  const interval = segment.interval;
575
570
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
576
- if (start === 0 && 24 % interval === 0) {
571
+ if (start === 0) {
577
572
  return cadence;
578
573
  }
579
574
  if (segment.fires.length <= 3) {
580
575
  return kloList(segment.fires, opts);
581
576
  }
582
- if (start === 0) {
583
- return cadence + " keskiy\xF6st\xE4 alkaen";
584
- }
585
577
  return cadence + " klo " + hourElatives[start] + " alkaen";
586
578
  }
587
579
  function kloList(hours, opts) {
package/dist/lang/fi.js CHANGED
@@ -168,15 +168,13 @@ var units = {
168
168
  mark: "joka tunti",
169
169
  anchor: "jokaisen tunnin",
170
170
  ela: "minuutista",
171
- gen: "minuutin",
172
- restart: "tasatunnista alkaen"
171
+ gen: "minuutin"
173
172
  },
174
173
  second: {
175
174
  mark: "joka minuutti",
176
175
  anchor: "jokaisen minuutin",
177
176
  ela: "sekunnista",
178
- gen: "sekunnin",
179
- restart: "joka minuutti"
177
+ gen: "sekunnin"
180
178
  }
181
179
  };
182
180
  function atMarks(values, unit, withMark) {
@@ -439,7 +437,7 @@ function renderEveryHour(ir, plan, opts) {
439
437
  return "joka tunti" + trailingQualifier(ir, opts);
440
438
  }
441
439
  function renderHourRange(ir, plan, opts) {
442
- const window = hourWindow(plan, opts);
440
+ const window = hourWindow(boundedWindow(plan), opts);
443
441
  if (plan.minuteForm === "wildcard") {
444
442
  return "joka minuutti " + window + trailingQualifier(ir, opts);
445
443
  }
@@ -461,6 +459,9 @@ function renderHourRange(ir, plan, opts) {
461
459
  function renderHourStep(ir, plan, opts) {
462
460
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
463
461
  }
462
+ function boundedWindow(plan) {
463
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
464
+ }
464
465
  function hourWindow(window, opts) {
465
466
  return kloRange(
466
467
  { hour: window.from, minute: 0 },
@@ -532,13 +533,7 @@ function stepCycle60(segment, unit, opts) {
532
533
  }
533
534
  return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
534
535
  }
535
- if (60 % interval === 0) {
536
- return cadence;
537
- }
538
- if (segment.fires.length <= 2) {
539
- return atMarks(joinList(wordList(segment.fires)), unit, true);
540
- }
541
- return cadence + " " + unit.restart;
536
+ return cadence;
542
537
  }
543
538
  function stepHours(segment, opts) {
544
539
  if (segment.startToken.indexOf("-") !== -1) {
@@ -547,15 +542,12 @@ function stepHours(segment, opts) {
547
542
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
548
543
  const interval = segment.interval;
549
544
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
550
- if (start === 0 && 24 % interval === 0) {
545
+ if (start === 0) {
551
546
  return cadence;
552
547
  }
553
548
  if (segment.fires.length <= 3) {
554
549
  return kloList(segment.fires, opts);
555
550
  }
556
- if (start === 0) {
557
- return cadence + " keskiy\xF6st\xE4 alkaen";
558
- }
559
551
  return cadence + " klo " + hourElatives[start] + " alkaen";
560
552
  }
561
553
  function kloList(hours, opts) {