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/en.js CHANGED
@@ -175,7 +175,17 @@ function renderSecondsWithinMinute(ir, plan, opts) {
175
175
  }
176
176
  function composeHourCadence(ir, plan, opts) {
177
177
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
178
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
178
+ if (!clockRest || ir.shapes.minute !== "single") {
179
+ return null;
180
+ }
181
+ const minute = +ir.pattern.minute;
182
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
183
+ }
184
+ function clockTimesConfinement(ir, rest, opts) {
185
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
186
+ return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
187
+ }
188
+ return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
179
189
  }
180
190
  function renderComposeSeconds(ir, plan, opts) {
181
191
  const cadence = composeHourCadence(ir, plan, opts);
@@ -183,16 +193,14 @@ function renderComposeSeconds(ir, plan, opts) {
183
193
  return cadence;
184
194
  }
185
195
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
186
- const minute = plan.rest.times[0].minute;
187
- if (+minute === 0) {
188
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, plan.rest, opts);
189
- }
190
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, plan.rest, opts);
196
+ return clockTimesConfinement(ir, plan.rest, opts);
191
197
  }
192
198
  if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
193
199
  return "every second of every other minute" + trailingQualifier(ir, opts);
194
200
  }
195
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
201
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
202
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
203
+ return lead + render(ir, plan.rest, opts);
196
204
  }
197
205
  function durationHours(ir, plan, opts) {
198
206
  const hours = plan.times.map(function clock(time) {
@@ -275,7 +283,8 @@ function renderMinuteFrequency(ir, plan, opts) {
275
283
  opts
276
284
  );
277
285
  if (plan.hours.kind === "during") {
278
- phrase += " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
286
+ const cadence = unevenHourCadence(ir, opts);
287
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
279
288
  } else if (plan.hours.kind === "window") {
280
289
  phrase += " " + hourWindow(plan.hours, opts);
281
290
  } else if (plan.hours.kind === "step") {
@@ -290,10 +299,13 @@ function renderMinuteSpanInHour(ir, plan, opts) {
290
299
  return "every minute from " + getTime({ hour: plan.hour, minute: plan.span[0] }, opts) + through(opts) + getTime({ hour: plan.hour, minute: plan.span[1] }, opts) + trailingQualifier(ir, opts);
291
300
  }
292
301
  function renderMinutesAcrossHours(ir, plan, opts) {
302
+ const cadence = unevenHourCadence(ir, opts);
293
303
  if (plan.form === "wildcard") {
304
+ if (cadence !== null) {
305
+ return "every minute, " + cadence + trailingQualifier(ir, opts);
306
+ }
294
307
  return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
295
308
  }
296
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
297
309
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
298
310
  // The 'list' form is a minute list, which has segments; an offset/uneven
299
311
  // step enumerated to that list reads as a stride.
@@ -304,6 +316,10 @@ function renderMinutesAcrossHours(ir, plan, opts) {
304
316
  opts
305
317
  )
306
318
  );
319
+ if (cadence !== null) {
320
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
321
+ }
322
+ const times = hourTimesFromPlan(ir, plan.times, true, opts);
307
323
  return lead + ", at " + times + trailingQualifier(ir, opts);
308
324
  }
309
325
  var stepOrdinals = {
@@ -330,7 +346,8 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
330
346
  "hour",
331
347
  opts
332
348
  ) : minuteRangeLead(ir.pattern.minute, opts);
333
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
349
+ const cadence = unevenHourCadence(ir, opts);
350
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
334
351
  }
335
352
  function minuteRangeLead(minuteField, opts) {
336
353
  const bounds = minuteField.split("-");
@@ -367,6 +384,10 @@ function rangeMinuteLead(ir, opts) {
367
384
  );
368
385
  }
369
386
  function renderHourStep(ir, plan, opts) {
387
+ const cadence = unevenHourCadence(ir, opts);
388
+ if (cadence !== null) {
389
+ return cadence + trailingQualifier(ir, opts);
390
+ }
370
391
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
371
392
  }
372
393
  function boundedWindow(plan) {
@@ -377,7 +398,8 @@ function hourWindow(window, opts) {
377
398
  }
378
399
  function renderClockTimes(ir, plan, opts) {
379
400
  if (ir.shapes.minute === "single") {
380
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
401
+ const minute = +ir.pattern.minute;
402
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
381
403
  if (cadence !== null) {
382
404
  return cadence;
383
405
  }
@@ -395,9 +417,9 @@ function renderClockTimes(ir, plan, opts) {
395
417
  }
396
418
  function renderCompactClockTimes(ir, plan, opts) {
397
419
  if (plan.fold) {
398
- const cadence = hourCadence(ir, +plan.minute, opts);
399
- if (cadence !== null) {
400
- return cadence;
420
+ const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
421
+ if (cadence2 !== null) {
422
+ return cadence2;
401
423
  }
402
424
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
403
425
  return segment.kind === "range";
@@ -408,16 +430,18 @@ function renderCompactClockTimes(ir, plan, opts) {
408
430
  const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
409
431
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
410
432
  }
411
- const phrase = (
433
+ const minuteLead = (
412
434
  // The non-fold branch is a minute list, which has segments. An
413
435
  // offset/uneven step enumerated to that list reads as a stride.
414
- (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
436
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
415
437
  segmentWords(ir.analyses.segments.minute, opts),
416
438
  "minute",
417
439
  "hour",
418
440
  opts
419
- )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
441
+ )
420
442
  );
443
+ const cadence = unevenHourCadence(ir, opts);
444
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
421
445
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
422
446
  }
423
447
  function foldedHourWindows(ir, plan, opts) {
@@ -535,16 +559,46 @@ function hourStrideCadence(stride, opts) {
535
559
  }
536
560
  return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
537
561
  }
562
+ function offsetCleanStride(stride) {
563
+ return stride.start < stride.interval && 24 % stride.interval === 0;
564
+ }
565
+ function unevenHourCadence(ir, opts) {
566
+ const stride = hourStride(ir);
567
+ if (!stride || offsetCleanStride(stride)) {
568
+ return null;
569
+ }
570
+ return hourStrideCadence(stride, opts);
571
+ }
572
+ function hourListStride(values) {
573
+ if (values.length < 2) {
574
+ return null;
575
+ }
576
+ const interval = values[1] - values[0];
577
+ if (interval < 2) {
578
+ return null;
579
+ }
580
+ for (let i = 2; i < values.length; i += 1) {
581
+ if (values[i] - values[i - 1] !== interval) {
582
+ return null;
583
+ }
584
+ }
585
+ if (values[0] !== 0 && values.length < 5) {
586
+ return null;
587
+ }
588
+ return { interval, last: values[values.length - 1], start: values[0] };
589
+ }
538
590
  function hourStride(ir) {
539
591
  const segments = ir.analyses.segments.hour;
540
592
  if (segments.length === 1 && segments[0].kind === "step") {
541
593
  const segment = segments[0];
594
+ if (segment.fires.length < 2) {
595
+ return null;
596
+ }
542
597
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
543
598
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
544
599
  }
545
600
  const values = singleValues(segments);
546
- const step = values && arithmeticStep(values);
547
- return step || null;
601
+ return values && hourListStride(values);
548
602
  }
549
603
  function subMinuteSecond(ir) {
550
604
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -568,13 +622,16 @@ function hourCadence(ir, minute, opts) {
568
622
  return null;
569
623
  }
570
624
  const fires = (stride.last - stride.start) / stride.interval + 1;
571
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
625
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
572
626
  return null;
573
627
  }
574
628
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
575
629
  if (confinement) {
576
630
  return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
577
631
  }
632
+ if (minute === 0 && ir.pattern.second === "0") {
633
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
634
+ }
578
635
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
579
636
  }
580
637
  function cleanStrideSegment(ir) {
@@ -585,6 +642,46 @@ function cleanStrideSegment(ir) {
585
642
  }
586
643
  return segment;
587
644
  }
645
+ function hasHourWindow(ir) {
646
+ return ir.analyses.segments.hour.some(function range(segment) {
647
+ return segment.kind === "range";
648
+ });
649
+ }
650
+ function hourRangeWindowTail(ir, opts) {
651
+ const windows = [];
652
+ const singles = [];
653
+ ir.analyses.segments.hour.forEach(function classify(segment) {
654
+ if (segment.kind === "range") {
655
+ windows.push("from " + getTime(
656
+ { hour: +segment.bounds[0], minute: 0 },
657
+ opts
658
+ ) + through(opts) + getTime({ hour: +segment.bounds[1], minute: 0 }, opts));
659
+ } else if (segment.kind === "step") {
660
+ singles.push(...segment.fires);
661
+ } else {
662
+ singles.push(+segment.value);
663
+ }
664
+ });
665
+ let phrase = "every hour " + joinList(windows, opts);
666
+ if (singles.length) {
667
+ phrase += " and at " + joinList(singles.map(function time(hour) {
668
+ return getTime({ hour, minute: 0 }, opts);
669
+ }), opts);
670
+ }
671
+ return phrase;
672
+ }
673
+ function hourRangeCadence(ir, minute, opts) {
674
+ if (minute !== 0 || !hasHourWindow(ir)) {
675
+ return null;
676
+ }
677
+ if (ir.pattern.second === "0") {
678
+ return null;
679
+ }
680
+ if (subMinuteSecond(ir)) {
681
+ return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
682
+ }
683
+ return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
684
+ }
588
685
  function seriesNumber(values, opts) {
589
686
  const anyBig = values.some(function big(v) {
590
687
  return +v > 10;
package/dist/lang/es.cjs CHANGED
@@ -202,14 +202,21 @@ function secondsListAtClock(ir, rest, opts) {
202
202
  }
203
203
  function composeHourCadence(ir, plan, opts) {
204
204
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
205
- return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
205
+ if (!clockRest || ir.shapes.minute !== "single") {
206
+ return null;
207
+ }
208
+ const minute = +ir.pattern.minute;
209
+ return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
210
+ }
211
+ function isPinnedMinuteSeconds(ir, plan) {
212
+ return plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step");
206
213
  }
207
214
  function renderComposeSeconds(ir, plan, opts) {
208
215
  const hourCad = composeHourCadence(ir, plan, opts);
209
216
  if (hourCad !== null) {
210
217
  return hourCad;
211
218
  }
212
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
219
+ if (isPinnedMinuteSeconds(ir, plan)) {
213
220
  return pinnedMinuteSeconds(ir, plan.rest, opts);
214
221
  }
215
222
  if (plan.rest.kind === "clockTimes" && ir.shapes.second === "list") {
@@ -225,7 +232,9 @@ function renderComposeSeconds(ir, plan, opts) {
225
232
  if (isEveryOtherMinuteSeconds(ir, plan)) {
226
233
  return secondsLeadClause(ir, opts) + " de " + render(ir, plan.rest, opts);
227
234
  }
228
- return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
235
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
236
+ const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
237
+ return lead + render(ir, plan.rest, opts);
229
238
  }
230
239
  function isEveryOtherMinuteSeconds(ir, plan) {
231
240
  if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
@@ -237,7 +246,7 @@ function isEveryOtherMinuteSeconds(ir, plan) {
237
246
  function pinnedMinuteSeconds(ir, rest, opts) {
238
247
  const dayTrail = leadingQualifier(ir, opts).trimEnd();
239
248
  const trail = dayTrail ? ", " + dayTrail : "";
240
- if (+rest.times[0].minute === 0) {
249
+ if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
241
250
  return secondsLeadClause(ir, opts) + " durante un minuto " + durationHourList(rest.times, opts) + trail;
242
251
  }
243
252
  return secondsLeadClause(ir, opts) + " de " + explicitClockList(rest.times, opts) + trail;
@@ -354,7 +363,12 @@ function renderMinuteFrequency(ir, plan, opts) {
354
363
  opts
355
364
  );
356
365
  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);
366
+ const cadence = unevenHourCadence(ir, opts);
367
+ if (cadence) {
368
+ phrase += ", " + cadence;
369
+ } else {
370
+ phrase += singleHourStep(ir.analyses.segments.hour) ? ", " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) : " " + hourSpanFromTimes(ir, plan.hours.times, opts);
371
+ }
358
372
  } else if (plan.hours.kind === "window") {
359
373
  phrase += " " + hourWindow(plan.hours, opts);
360
374
  } else if (plan.hours.kind === "step") {
@@ -373,22 +387,30 @@ function renderMinuteSpanInHour(ir, plan, opts) {
373
387
  ) + trailingQualifier(ir, opts);
374
388
  }
375
389
  function renderMinutesAcrossHours(ir, plan, opts) {
390
+ const cadence = unevenHourCadence(ir, opts);
376
391
  if (plan.form === "wildcard") {
392
+ if (cadence !== null) {
393
+ return "cada minuto, " + cadence + trailingQualifier(ir, opts);
394
+ }
377
395
  if (singleHourStep(ir.analyses.segments.hour)) {
378
396
  return "cada minuto, " + stepHourSpan(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
379
397
  }
380
398
  return "cada minuto " + hourSpanFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
381
399
  }
382
400
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir, opts);
401
+ if (cadence !== null) {
402
+ return lead + ", " + cadence + trailingQualifier(ir, opts);
403
+ }
383
404
  return lead + ", " + atHourTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
384
405
  }
385
406
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
386
407
  const segment = stepSegment(ir.analyses.segments.hour);
408
+ const cadence = unevenHourCadence(ir, opts);
387
409
  if (plan.form === "wildcard") {
388
410
  return "cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(ir, opts);
389
411
  }
390
412
  const lead = plan.form === "list" ? minutesList(ir, opts) : minuteRangeLead(ir.pattern.minute);
391
- return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
413
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
392
414
  }
393
415
  function renderEveryHour(ir, plan, opts) {
394
416
  return "cada hora" + trailingQualifier(ir, opts);
@@ -408,6 +430,10 @@ function renderHourRange(ir, plan, opts) {
408
430
  return lead + ", " + window + trailingQualifier(ir, opts);
409
431
  }
410
432
  function renderHourStep(ir, plan, opts) {
433
+ const cadence = unevenHourCadence(ir, opts);
434
+ if (cadence !== null) {
435
+ return cadence + trailingQualifier(ir, opts);
436
+ }
411
437
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
412
438
  }
413
439
  function boundedWindow(plan) {
@@ -479,7 +505,8 @@ function unionYaseaSuffix(ir, opts) {
479
505
  }
480
506
  function renderClockTimes(ir, plan, opts) {
481
507
  if (ir.shapes.minute === "single") {
482
- const cadence = hourCadence(ir, +ir.pattern.minute, opts);
508
+ const minute = +ir.pattern.minute;
509
+ const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
483
510
  if (cadence !== null) {
484
511
  return cadence;
485
512
  }
@@ -665,9 +692,9 @@ function groupClockTimesByArticle(phrases) {
665
692
  }
666
693
  function renderCompactClockTimes(ir, plan, opts) {
667
694
  if (plan.fold) {
668
- const cadence = hourCadence(ir, plan.minute, opts);
669
- if (cadence !== null) {
670
- return cadence;
695
+ const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
696
+ if (cadence2 !== null) {
697
+ return cadence2;
671
698
  }
672
699
  const ranged = hourSegments(ir).some(function range(segment) {
673
700
  return segment.kind === "range";
@@ -677,7 +704,8 @@ function renderCompactClockTimes(ir, plan, opts) {
677
704
  }
678
705
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
679
706
  }
680
- const phrase = minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
707
+ const cadence = unevenHourCadence(ir, opts);
708
+ const phrase = cadence ? minutesList(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
681
709
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
682
710
  }
683
711
  var renderers = {
@@ -771,16 +799,46 @@ function hourStrideCadence(stride, opts) {
771
799
  }
772
800
  return cadence + " de " + timePhrase(start, 0, null, opts) + " a " + timePhrase(last, 0, null, opts);
773
801
  }
802
+ function offsetCleanStride(stride) {
803
+ return stride.start < stride.interval && 24 % stride.interval === 0;
804
+ }
805
+ function unevenHourCadence(ir, opts) {
806
+ const stride = hourStride(ir);
807
+ if (!stride || offsetCleanStride(stride)) {
808
+ return null;
809
+ }
810
+ return hourStrideCadence(stride, opts);
811
+ }
812
+ function hourListStride(values) {
813
+ if (values.length < 2) {
814
+ return null;
815
+ }
816
+ const interval = values[1] - values[0];
817
+ if (interval < 2) {
818
+ return null;
819
+ }
820
+ for (let i = 2; i < values.length; i += 1) {
821
+ if (values[i] - values[i - 1] !== interval) {
822
+ return null;
823
+ }
824
+ }
825
+ if (values[0] !== 0 && values.length < 5) {
826
+ return null;
827
+ }
828
+ return { interval, last: values[values.length - 1], start: values[0] };
829
+ }
774
830
  function hourStride(ir) {
775
831
  const segments = fieldSegments(ir, "hour");
776
832
  if (segments.length === 1 && segments[0].kind === "step") {
777
833
  const segment = segments[0];
834
+ if (segment.fires.length < 2) {
835
+ return null;
836
+ }
778
837
  const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
779
838
  return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
780
839
  }
781
840
  const values = singleValues(segments);
782
- const step = values && arithmeticStep(values);
783
- return step || null;
841
+ return values && hourListStride(values);
784
842
  }
785
843
  function subMinuteSecond(ir) {
786
844
  return ir.pattern.second === "*" || ir.shapes.second === "step";
@@ -804,13 +862,16 @@ function hourCadence(ir, minute, opts) {
804
862
  return null;
805
863
  }
806
864
  const fires = (stride.last - stride.start) / stride.interval + 1;
807
- if (ir.pattern.second === "0" && fires <= maxClockTimes) {
865
+ if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
808
866
  return null;
809
867
  }
810
868
  const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
811
869
  if (confinement) {
812
870
  return secondsClause(ir, "minuto", opts) + " durante un minuto, " + stepHourSpan(confinement, opts) + trailingQualifier(ir, opts);
813
871
  }
872
+ if (minute === 0 && ir.pattern.second === "0") {
873
+ return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
874
+ }
814
875
  return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
815
876
  }
816
877
  function cleanStrideSegment(ir) {
@@ -821,6 +882,20 @@ function cleanStrideSegment(ir) {
821
882
  }
822
883
  return segment;
823
884
  }
885
+ function hasHourWindow(ir) {
886
+ return hourSegments(ir).some(function range(segment) {
887
+ return segment.kind === "range";
888
+ });
889
+ }
890
+ function hourRangeCadence(ir, minute, opts) {
891
+ if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
892
+ return null;
893
+ }
894
+ if (subMinuteSecond(ir)) {
895
+ return secondsClause(ir, "minuto", opts) + " durante un minuto, durante las horas " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
896
+ }
897
+ return hourCadenceLead(ir, minute, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
898
+ }
824
899
  function atTimes(hours, opts) {
825
900
  return hours.map(function each(hour) {
826
901
  return atTime(timePhrase(hour, 0, null, opts));