cronli5 0.1.5 → 0.1.7

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.cjs CHANGED
@@ -72,6 +72,26 @@ function arithmeticStep(values) {
72
72
  }
73
73
  return { start: values[0], interval, last: values[values.length - 1] };
74
74
  }
75
+ function weekdayDisplayKey(value) {
76
+ return value === 0 ? 7 : value;
77
+ }
78
+ function orderWeekdaysForDisplay(segments) {
79
+ const flattened = segments.flatMap(function flat(segment) {
80
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
81
+ return { kind: "single", value: "" + value };
82
+ }) : [segment];
83
+ });
84
+ function key(segment) {
85
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
86
+ }
87
+ return flattened.map(function index(segment, position) {
88
+ return [segment, position];
89
+ }).sort(function byDisplayKey(a, b) {
90
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
91
+ }).map(function unwrap(pair) {
92
+ return pair[0];
93
+ });
94
+ }
75
95
  function toFieldNumber(token, numberMap) {
76
96
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
77
97
  }
@@ -202,14 +222,21 @@ function secondsListAtClock(ir, rest, opts) {
202
222
  }
203
223
  function composeHourCadence(ir, plan, opts) {
204
224
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
205
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
225
+ if (!clockRest || ir.shapes.minute !== "single") {
226
+ return null;
227
+ }
228
+ const minute = +ir.pattern.minute;
229
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
230
+ }
231
+ function isPinnedMinuteSeconds(ir, plan) {
232
+ return plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step");
206
233
  }
207
234
  function renderComposeSeconds(ir, plan, opts) {
208
235
  const hourCad = composeHourCadence(ir, plan, opts);
209
236
  if (hourCad !== null) {
210
237
  return hourCad;
211
238
  }
212
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
239
+ if (isPinnedMinuteSeconds(ir, plan)) {
213
240
  return pinnedMinuteSeconds(ir, plan.rest, opts);
214
241
  }
215
242
  if (plan.rest.kind === "clockTimes" && ir.shapes.second === "list") {
@@ -217,7 +244,7 @@ function renderComposeSeconds(ir, plan, opts) {
217
244
  }
218
245
  if (plan.rest.kind === "hourRange" && ir.shapes.second === "step" && ir.pattern.weekday !== "*") {
219
246
  const restNode = plan.rest;
220
- const window = hourWindow(restNode, opts);
247
+ const window = hourWindow(boundedWindow(restNode), opts);
221
248
  const dayFrame = weekdayQualifier(ir) + monthScope(ir);
222
249
  const cadence = "cada " + numero(stepSegment(ir.analyses.segments.second).interval, opts) + " segundos del minuto " + ir.pattern.minute;
223
250
  return dayFrame + ", " + window + ", " + cadence;
@@ -225,7 +252,9 @@ function renderComposeSeconds(ir, plan, opts) {
225
252
  if (isEveryOtherMinuteSeconds(ir, plan)) {
226
253
  return secondsLeadClause(ir, opts) + " de " + render(ir, plan.rest, opts);
227
254
  }
228
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
255
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
256
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
257
+ return lead + render(ir, plan.rest, opts);
229
258
  }
230
259
  function isEveryOtherMinuteSeconds(ir, plan) {
231
260
  if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
@@ -237,7 +266,7 @@ function isEveryOtherMinuteSeconds(ir, plan) {
237
266
  function pinnedMinuteSeconds(ir, rest, opts) {
238
267
  const dayTrail = leadingQualifier(ir, opts).trimEnd();
239
268
  const trail = dayTrail ? ", " + dayTrail : "";
240
- if (+rest.times[0].minute === 0) {
269
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
241
270
  return secondsLeadClause(ir, opts) + " durante un minuto " + durationHourList(rest.times, opts) + trail;
242
271
  }
243
272
  return secondsLeadClause(ir, opts) + " de " + explicitClockList(rest.times, opts) + trail;
@@ -354,7 +383,12 @@ function renderMinuteFrequency(ir, plan, opts) {
354
383
  opts
355
384
  );
356
385
  if (plan.hours.kind === "during") {
357
- phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
386
+ const cadence = unevenHourCadence(ir, opts);
387
+ if (cadence) {
388
+ phrase += ", " + cadence;
389
+ } else {
390
+ phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
391
+ }
358
392
  } else if (plan.hours.kind === "window") {
359
393
  phrase += " " + hourWindow(plan.hours, opts);
360
394
  } else if (plan.hours.kind === "step") {
@@ -373,22 +407,30 @@ function renderMinuteSpanInHour(ir, plan, opts) {
373
407
  ) + trailingQualifier(ir, opts);
374
408
  }
375
409
  function renderMinutesAcrossHours(ir, plan, opts) {
410
+ const cadence = unevenHourCadence(ir, opts);
376
411
  if (plan.form === "wildcard") {
412
+ if (cadence !== null) {
413
+ return "cada minuto, " + cadence + trailingQualifier(ir, opts);
414
+ }
377
415
  if (singleHourStep(ir.analyses.segments.hour)) {
378
416
  return "cada minuto, " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
379
417
  }
380
418
  return "cada minuto " + hourSpanFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
381
419
  }
382
420
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir, opts);
421
+ if (cadence !== null) {
422
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
423
+ }
383
424
  return lead + ", " + atHourTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
384
425
  }
385
426
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
386
427
  const segment = stepSegment(ir.analyses.segments.hour);
428
+ const cadence = unevenHourCadence(ir, opts);
387
429
  if (plan.form === "wildcard") {
388
430
  return "cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(ir, opts);
389
431
  }
390
432
  const lead = plan.form === "list" ? minutesList(ir, opts) : minuteRangeLead(ir.pattern.minute);
391
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
433
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
392
434
  }
393
435
  function renderEveryHour(ir, plan, opts) {
394
436
  return "cada hora" + trailingQualifier(ir, opts);
@@ -408,10 +450,15 @@ function renderHourRange(ir, plan, opts) {
408
450
  return lead + ", " + window + trailingQualifier(ir, opts);
409
451
  }
410
452
  function renderHourStep(ir, plan, opts) {
453
+ const cadence = unevenHourCadence(ir, opts);
454
+ if (cadence !== null) {
455
+ return cadence + trailingQualifier(ir, opts);
456
+ }
411
457
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
412
458
  }
413
459
  function boundedWindow(plan) {
414
- return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
460
+ const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
461
+ return { from: plan.from, last, to: plan.to };
415
462
  }
416
463
  function hourWindow(window, opts) {
417
464
  return timeRange(
@@ -455,7 +502,7 @@ function dowArm(ir) {
455
502
  if (quartz) {
456
503
  return quartz;
457
504
  }
458
- const segments = flattenSteps(fieldSegments(ir, "weekday"));
505
+ const segments = orderWeekdaysForDisplay(fieldSegments(ir, "weekday"));
459
506
  const allSingles = segments.every(function single(segment) {
460
507
  return segment.kind === "single";
461
508
  });
@@ -479,7 +526,8 @@ function unionYaseaSuffix(ir, opts) {
479
526
  }
480
527
  function renderClockTimes(ir, plan, opts) {
481
528
  if (ir.shapes.minute === "single") {
482
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
529
+ const minute = +ir.pattern.minute;
530
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
483
531
  if (cadence !== null) {
484
532
  return cadence;
485
533
  }
@@ -665,9 +713,9 @@ function groupClockTimesByArticle(phrases) {
665
713
  }
666
714
  function renderCompactClockTimes(ir, plan, opts) {
667
715
  if (plan.fold) {
668
- const cadence = hourCadence(ir, plan.minute, opts);
669
- if (cadence !== null) {
670
- return cadence;
716
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
717
+ if (cadence2 !== null) {
718
+ return cadence2;
671
719
  }
672
720
  const ranged = hourSegments(ir).some(function range(segment) {
673
721
  return segment.kind === "range";
@@ -677,7 +725,8 @@ function renderCompactClockTimes(ir, plan, opts) {
677
725
  }
678
726
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
679
727
  }
680
- const phrase = minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
728
+ const cadence = unevenHourCadence(ir, opts);
729
+ const phrase = cadence ? minutesList(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : minutesList(ir, opts) + ", " + hourContextTimes(ir, opts) + trailingQualifier(ir, opts);
681
730
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
682
731
  }
683
732
  var renderers = {
@@ -771,16 +820,46 @@ function hourStrideCadence(stride, opts) {
771
820
  }
772
821
  return cadence + " de " + timePhrase(start, 0, null, opts) + " a " + timePhrase(last, 0, null, opts);
773
822
  }
823
+ function offsetCleanStride(stride) {
824
+ return stride.start < stride.interval && 24 % stride.interval === 0;
825
+ }
826
+ function unevenHourCadence(ir, opts) {
827
+ const stride = hourStride(ir);
828
+ if (!stride || offsetCleanStride(stride)) {
829
+ return null;
830
+ }
831
+ return hourStrideCadence(stride, opts);
832
+ }
833
+ function hourListStride(values) {
834
+ if (values.length < 2) {
835
+ return null;
836
+ }
837
+ const interval = values[1] - values[0];
838
+ if (interval < 2) {
839
+ return null;
840
+ }
841
+ for (let i = 2; i < values.length; i += 1) {
842
+ if (values[i] - values[i - 1] !== interval) {
843
+ return null;
844
+ }
845
+ }
846
+ if (values[0] !== 0 && values.length < 5) {
847
+ return null;
848
+ }
849
+ return { interval, last: values[values.length - 1], start: values[0] };
850
+ }
774
851
  function hourStride(ir) {
775
852
  const segments = fieldSegments(ir, "hour");
776
853
  if (segments.length === 1 && segments[0].kind === "step") {
777
854
  const segment = segments[0];
855
+ if (segment.fires.length < 2) {
856
+ return null;
857
+ }
778
858
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
779
859
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
780
860
  }
781
861
  const values = singleValues(segments);
782
- const step = values && arithmeticStep(values);
783
- return step || null;
862
+ return values && hourListStride(values);
784
863
  }
785
864
  function subMinuteSecond(ir) {
786
865
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -804,13 +883,16 @@ function hourCadence(ir, minute, opts) {
804
883
  return null;
805
884
  }
806
885
  const fires = (stride.last - stride.start) / stride.interval + 1;
807
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
886
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
808
887
  return null;
809
888
  }
810
889
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
811
890
  if (confinement) {
812
891
  return secondsClause(ir, "minuto", opts) + " durante un minuto, " + stepHourSpan(confinement, opts) + trailingQualifier(ir, opts);
813
892
  }
893
+ if (minute === 0 && ir.pattern.second === "0") {
894
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
895
+ }
814
896
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
815
897
  }
816
898
  function cleanStrideSegment(ir) {
@@ -821,6 +903,62 @@ function cleanStrideSegment(ir) {
821
903
  }
822
904
  return segment;
823
905
  }
906
+ function hasHourWindow(ir) {
907
+ return hourSegments(ir).some(function range(segment) {
908
+ return segment.kind === "range";
909
+ });
910
+ }
911
+ function hourRangeCadence(ir, minute, opts) {
912
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
913
+ return null;
914
+ }
915
+ if (subMinuteSecond(ir)) {
916
+ return secondsClause(ir, "minuto", opts) + " durante un minuto, durante las horas " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
917
+ }
918
+ return hourCadenceLead(ir, minute, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
919
+ }
920
+ function hourContextTimes(ir, opts) {
921
+ const segments = hourSegments(ir);
922
+ const points = [];
923
+ const hasRange = segments.some(function range(segment) {
924
+ return segment.kind === "range";
925
+ });
926
+ segments.forEach(function collect(segment) {
927
+ if (segment.kind === "step") {
928
+ points.push(...segment.fires);
929
+ } else if (segment.kind === "single") {
930
+ points.push(+segment.value);
931
+ }
932
+ });
933
+ function isWord(hour) {
934
+ return !opts.ampm && (hour === 0 || hour === 12);
935
+ }
936
+ if (!hasRange && points.every(isWord)) {
937
+ return joinList(points.map(function each(hour) {
938
+ return atTime(bareHourPhrase(hour, opts));
939
+ }));
940
+ }
941
+ function wholeHour(hour) {
942
+ return "de la hora " + fromTime(explicitTimePhrase(hour, 0, opts));
943
+ }
944
+ const pieces = [];
945
+ segments.forEach(function place(segment) {
946
+ if (segment.kind === "range") {
947
+ pieces.push(timeRange(
948
+ { hour: +segment.bounds[0], minute: 0 },
949
+ { hour: +segment.bounds[1], minute: 0 },
950
+ opts
951
+ ));
952
+ } else if (segment.kind === "step") {
953
+ segment.fires.forEach(function each(hour) {
954
+ pieces.push(wholeHour(hour));
955
+ });
956
+ } else {
957
+ pieces.push(wholeHour(+segment.value));
958
+ }
959
+ });
960
+ return joinList(pieces);
961
+ }
824
962
  function atTimes(hours, opts) {
825
963
  return hours.map(function each(hour) {
826
964
  return atTime(timePhrase(hour, 0, null, opts));
@@ -1083,7 +1221,7 @@ function weekdayQualifier(ir) {
1083
1221
  if (quartz) {
1084
1222
  return quartz;
1085
1223
  }
1086
- const segments = flattenSteps(fieldSegments(ir, "weekday"));
1224
+ const segments = orderWeekdaysForDisplay(fieldSegments(ir, "weekday"));
1087
1225
  const allSingles = segments.every(function single(segment) {
1088
1226
  return segment.kind === "single";
1089
1227
  });
package/dist/lang/es.js CHANGED
@@ -46,6 +46,26 @@ function arithmeticStep(values) {
46
46
  }
47
47
  return { start: values[0], interval, last: values[values.length - 1] };
48
48
  }
49
+ function weekdayDisplayKey(value) {
50
+ return value === 0 ? 7 : value;
51
+ }
52
+ function orderWeekdaysForDisplay(segments) {
53
+ const flattened = segments.flatMap(function flat(segment) {
54
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
55
+ return { kind: "single", value: "" + value };
56
+ }) : [segment];
57
+ });
58
+ function key(segment) {
59
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
60
+ }
61
+ return flattened.map(function index(segment, position) {
62
+ return [segment, position];
63
+ }).sort(function byDisplayKey(a, b) {
64
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
65
+ }).map(function unwrap(pair) {
66
+ return pair[0];
67
+ });
68
+ }
49
69
  function toFieldNumber(token, numberMap) {
50
70
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
51
71
  }
@@ -176,14 +196,21 @@ function secondsListAtClock(ir, rest, opts) {
176
196
  }
177
197
  function composeHourCadence(ir, plan, opts) {
178
198
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
179
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
199
+ if (!clockRest || ir.shapes.minute !== "single") {
200
+ return null;
201
+ }
202
+ const minute = +ir.pattern.minute;
203
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
204
+ }
205
+ function isPinnedMinuteSeconds(ir, plan) {
206
+ return plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step");
180
207
  }
181
208
  function renderComposeSeconds(ir, plan, opts) {
182
209
  const hourCad = composeHourCadence(ir, plan, opts);
183
210
  if (hourCad !== null) {
184
211
  return hourCad;
185
212
  }
186
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
213
+ if (isPinnedMinuteSeconds(ir, plan)) {
187
214
  return pinnedMinuteSeconds(ir, plan.rest, opts);
188
215
  }
189
216
  if (plan.rest.kind === "clockTimes" && ir.shapes.second === "list") {
@@ -191,7 +218,7 @@ function renderComposeSeconds(ir, plan, opts) {
191
218
  }
192
219
  if (plan.rest.kind === "hourRange" && ir.shapes.second === "step" && ir.pattern.weekday !== "*") {
193
220
  const restNode = plan.rest;
194
- const window = hourWindow(restNode, opts);
221
+ const window = hourWindow(boundedWindow(restNode), opts);
195
222
  const dayFrame = weekdayQualifier(ir) + monthScope(ir);
196
223
  const cadence = "cada " + numero(stepSegment(ir.analyses.segments.second).interval, opts) + " segundos del minuto " + ir.pattern.minute;
197
224
  return dayFrame + ", " + window + ", " + cadence;
@@ -199,7 +226,9 @@ function renderComposeSeconds(ir, plan, opts) {
199
226
  if (isEveryOtherMinuteSeconds(ir, plan)) {
200
227
  return secondsLeadClause(ir, opts) + " de " + render(ir, plan.rest, opts);
201
228
  }
202
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
229
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
230
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
231
+ return lead + render(ir, plan.rest, opts);
203
232
  }
204
233
  function isEveryOtherMinuteSeconds(ir, plan) {
205
234
  if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
@@ -211,7 +240,7 @@ function isEveryOtherMinuteSeconds(ir, plan) {
211
240
  function pinnedMinuteSeconds(ir, rest, opts) {
212
241
  const dayTrail = leadingQualifier(ir, opts).trimEnd();
213
242
  const trail = dayTrail ? ", " + dayTrail : "";
214
- if (+rest.times[0].minute === 0) {
243
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
215
244
  return secondsLeadClause(ir, opts) + " durante un minuto " + durationHourList(rest.times, opts) + trail;
216
245
  }
217
246
  return secondsLeadClause(ir, opts) + " de " + explicitClockList(rest.times, opts) + trail;
@@ -328,7 +357,12 @@ function renderMinuteFrequency(ir, plan, opts) {
328
357
  opts
329
358
  );
330
359
  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);
360
+ const cadence = unevenHourCadence(ir, opts);
361
+ if (cadence) {
362
+ phrase += ", " + cadence;
363
+ } else {
364
+ phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
365
+ }
332
366
  } else if (plan.hours.kind === "window") {
333
367
  phrase += " " + hourWindow(plan.hours, opts);
334
368
  } else if (plan.hours.kind === "step") {
@@ -347,22 +381,30 @@ function renderMinuteSpanInHour(ir, plan, opts) {
347
381
  ) + trailingQualifier(ir, opts);
348
382
  }
349
383
  function renderMinutesAcrossHours(ir, plan, opts) {
384
+ const cadence = unevenHourCadence(ir, opts);
350
385
  if (plan.form === "wildcard") {
386
+ if (cadence !== null) {
387
+ return "cada minuto, " + cadence + trailingQualifier(ir, opts);
388
+ }
351
389
  if (singleHourStep(ir.analyses.segments.hour)) {
352
390
  return "cada minuto, " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
353
391
  }
354
392
  return "cada minuto " + hourSpanFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
355
393
  }
356
394
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir, opts);
395
+ if (cadence !== null) {
396
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
397
+ }
357
398
  return lead + ", " + atHourTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
358
399
  }
359
400
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
360
401
  const segment = stepSegment(ir.analyses.segments.hour);
402
+ const cadence = unevenHourCadence(ir, opts);
361
403
  if (plan.form === "wildcard") {
362
404
  return "cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(ir, opts);
363
405
  }
364
406
  const lead = plan.form === "list" ? minutesList(ir, opts) : minuteRangeLead(ir.pattern.minute);
365
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
407
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
366
408
  }
367
409
  function renderEveryHour(ir, plan, opts) {
368
410
  return "cada hora" + trailingQualifier(ir, opts);
@@ -382,10 +424,15 @@ function renderHourRange(ir, plan, opts) {
382
424
  return lead + ", " + window + trailingQualifier(ir, opts);
383
425
  }
384
426
  function renderHourStep(ir, plan, opts) {
427
+ const cadence = unevenHourCadence(ir, opts);
428
+ if (cadence !== null) {
429
+ return cadence + trailingQualifier(ir, opts);
430
+ }
385
431
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
386
432
  }
387
433
  function boundedWindow(plan) {
388
- return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
434
+ const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
435
+ return { from: plan.from, last, to: plan.to };
389
436
  }
390
437
  function hourWindow(window, opts) {
391
438
  return timeRange(
@@ -429,7 +476,7 @@ function dowArm(ir) {
429
476
  if (quartz) {
430
477
  return quartz;
431
478
  }
432
- const segments = flattenSteps(fieldSegments(ir, "weekday"));
479
+ const segments = orderWeekdaysForDisplay(fieldSegments(ir, "weekday"));
433
480
  const allSingles = segments.every(function single(segment) {
434
481
  return segment.kind === "single";
435
482
  });
@@ -453,7 +500,8 @@ function unionYaseaSuffix(ir, opts) {
453
500
  }
454
501
  function renderClockTimes(ir, plan, opts) {
455
502
  if (ir.shapes.minute === "single") {
456
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
503
+ const minute = +ir.pattern.minute;
504
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
457
505
  if (cadence !== null) {
458
506
  return cadence;
459
507
  }
@@ -639,9 +687,9 @@ function groupClockTimesByArticle(phrases) {
639
687
  }
640
688
  function renderCompactClockTimes(ir, plan, opts) {
641
689
  if (plan.fold) {
642
- const cadence = hourCadence(ir, plan.minute, opts);
643
- if (cadence !== null) {
644
- return cadence;
690
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
691
+ if (cadence2 !== null) {
692
+ return cadence2;
645
693
  }
646
694
  const ranged = hourSegments(ir).some(function range(segment) {
647
695
  return segment.kind === "range";
@@ -651,7 +699,8 @@ function renderCompactClockTimes(ir, plan, opts) {
651
699
  }
652
700
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
653
701
  }
654
- const phrase = minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
702
+ const cadence = unevenHourCadence(ir, opts);
703
+ const phrase = cadence ? minutesList(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : minutesList(ir, opts) + ", " + hourContextTimes(ir, opts) + trailingQualifier(ir, opts);
655
704
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
656
705
  }
657
706
  var renderers = {
@@ -745,16 +794,46 @@ function hourStrideCadence(stride, opts) {
745
794
  }
746
795
  return cadence + " de " + timePhrase(start, 0, null, opts) + " a " + timePhrase(last, 0, null, opts);
747
796
  }
797
+ function offsetCleanStride(stride) {
798
+ return stride.start < stride.interval && 24 % stride.interval === 0;
799
+ }
800
+ function unevenHourCadence(ir, opts) {
801
+ const stride = hourStride(ir);
802
+ if (!stride || offsetCleanStride(stride)) {
803
+ return null;
804
+ }
805
+ return hourStrideCadence(stride, opts);
806
+ }
807
+ function hourListStride(values) {
808
+ if (values.length < 2) {
809
+ return null;
810
+ }
811
+ const interval = values[1] - values[0];
812
+ if (interval < 2) {
813
+ return null;
814
+ }
815
+ for (let i = 2; i < values.length; i += 1) {
816
+ if (values[i] - values[i - 1] !== interval) {
817
+ return null;
818
+ }
819
+ }
820
+ if (values[0] !== 0 && values.length < 5) {
821
+ return null;
822
+ }
823
+ return { interval, last: values[values.length - 1], start: values[0] };
824
+ }
748
825
  function hourStride(ir) {
749
826
  const segments = fieldSegments(ir, "hour");
750
827
  if (segments.length === 1 && segments[0].kind === "step") {
751
828
  const segment = segments[0];
829
+ if (segment.fires.length < 2) {
830
+ return null;
831
+ }
752
832
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
753
833
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
754
834
  }
755
835
  const values = singleValues(segments);
756
- const step = values && arithmeticStep(values);
757
- return step || null;
836
+ return values && hourListStride(values);
758
837
  }
759
838
  function subMinuteSecond(ir) {
760
839
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -778,13 +857,16 @@ function hourCadence(ir, minute, opts) {
778
857
  return null;
779
858
  }
780
859
  const fires = (stride.last - stride.start) / stride.interval + 1;
781
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
860
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
782
861
  return null;
783
862
  }
784
863
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
785
864
  if (confinement) {
786
865
  return secondsClause(ir, "minuto", opts) + " durante un minuto, " + stepHourSpan(confinement, opts) + trailingQualifier(ir, opts);
787
866
  }
867
+ if (minute === 0 && ir.pattern.second === "0") {
868
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
869
+ }
788
870
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
789
871
  }
790
872
  function cleanStrideSegment(ir) {
@@ -795,6 +877,62 @@ function cleanStrideSegment(ir) {
795
877
  }
796
878
  return segment;
797
879
  }
880
+ function hasHourWindow(ir) {
881
+ return hourSegments(ir).some(function range(segment) {
882
+ return segment.kind === "range";
883
+ });
884
+ }
885
+ function hourRangeCadence(ir, minute, opts) {
886
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
887
+ return null;
888
+ }
889
+ if (subMinuteSecond(ir)) {
890
+ return secondsClause(ir, "minuto", opts) + " durante un minuto, durante las horas " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
891
+ }
892
+ return hourCadenceLead(ir, minute, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
893
+ }
894
+ function hourContextTimes(ir, opts) {
895
+ const segments = hourSegments(ir);
896
+ const points = [];
897
+ const hasRange = segments.some(function range(segment) {
898
+ return segment.kind === "range";
899
+ });
900
+ segments.forEach(function collect(segment) {
901
+ if (segment.kind === "step") {
902
+ points.push(...segment.fires);
903
+ } else if (segment.kind === "single") {
904
+ points.push(+segment.value);
905
+ }
906
+ });
907
+ function isWord(hour) {
908
+ return !opts.ampm && (hour === 0 || hour === 12);
909
+ }
910
+ if (!hasRange && points.every(isWord)) {
911
+ return joinList(points.map(function each(hour) {
912
+ return atTime(bareHourPhrase(hour, opts));
913
+ }));
914
+ }
915
+ function wholeHour(hour) {
916
+ return "de la hora " + fromTime(explicitTimePhrase(hour, 0, opts));
917
+ }
918
+ const pieces = [];
919
+ segments.forEach(function place(segment) {
920
+ if (segment.kind === "range") {
921
+ pieces.push(timeRange(
922
+ { hour: +segment.bounds[0], minute: 0 },
923
+ { hour: +segment.bounds[1], minute: 0 },
924
+ opts
925
+ ));
926
+ } else if (segment.kind === "step") {
927
+ segment.fires.forEach(function each(hour) {
928
+ pieces.push(wholeHour(hour));
929
+ });
930
+ } else {
931
+ pieces.push(wholeHour(+segment.value));
932
+ }
933
+ });
934
+ return joinList(pieces);
935
+ }
798
936
  function atTimes(hours, opts) {
799
937
  return hours.map(function each(hour) {
800
938
  return atTime(timePhrase(hour, 0, null, opts));
@@ -1057,7 +1195,7 @@ function weekdayQualifier(ir) {
1057
1195
  if (quartz) {
1058
1196
  return quartz;
1059
1197
  }
1060
- const segments = flattenSteps(fieldSegments(ir, "weekday"));
1198
+ const segments = orderWeekdaysForDisplay(fieldSegments(ir, "weekday"));
1061
1199
  const allSingles = segments.every(function single(segment) {
1062
1200
  return segment.kind === "single";
1063
1201
  });