cronli5 0.1.4 → 0.1.5

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
@@ -1,3 +1,23 @@
1
+ // src/core/util.ts
2
+ function arithmeticStep(values) {
3
+ if (values.length < 5) {
4
+ return null;
5
+ }
6
+ const interval = values[1] - values[0];
7
+ if (interval < 2) {
8
+ return null;
9
+ }
10
+ for (let i = 2; i < values.length; i += 1) {
11
+ if (values[i] - values[i - 1] !== interval) {
12
+ return null;
13
+ }
14
+ }
15
+ return { start: values[0], interval, last: values[values.length - 1] };
16
+ }
17
+
18
+ // src/core/specs.ts
19
+ var maxClockTimes = 6;
20
+
1
21
  // src/core/format.ts
2
22
  function pad(n) {
3
23
  n = "" + n;
@@ -153,7 +173,15 @@ function renderSecondsWithinMinute(ir, plan, opts) {
153
173
  }
154
174
  return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
155
175
  }
176
+ function composeHourCadence(ir, plan, opts) {
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;
179
+ }
156
180
  function renderComposeSeconds(ir, plan, opts) {
181
+ const cadence = composeHourCadence(ir, plan, opts);
182
+ if (cadence !== null) {
183
+ return cadence;
184
+ }
157
185
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
158
186
  const minute = plan.rest.times[0].minute;
159
187
  if (+minute === 0) {
@@ -186,6 +214,9 @@ function clockTimesOf(ir, plan, opts) {
186
214
  return joinList(times, opts) + (trail && ", " + trail);
187
215
  }
188
216
  function secondsLeadClause(ir, opts) {
217
+ return secondsClause(ir, "minute", opts);
218
+ }
219
+ function secondsClause(ir, anchor, opts) {
189
220
  const secondField = ir.pattern.second;
190
221
  const shape = ir.shapes.second;
191
222
  if (secondField === "*") {
@@ -195,22 +226,27 @@ function secondsLeadClause(ir, opts) {
195
226
  return stepCycle60(
196
227
  ir.analyses.segments.second[0],
197
228
  "second",
198
- "minute",
229
+ anchor,
199
230
  opts
200
231
  );
201
232
  }
202
233
  if (shape === "range") {
203
234
  const bounds = secondField.split("-");
204
235
  const num = seriesNumber(bounds, opts);
205
- return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
236
+ return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
206
237
  }
207
238
  if (shape === "single") {
208
- return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
239
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
209
240
  }
210
- return listPastThe(
241
+ return strideFromSegments(
242
+ ir.analyses.segments.second,
243
+ "second",
244
+ anchor,
245
+ opts
246
+ ) ?? listPastThe(
211
247
  segmentWords(ir.analyses.segments.second, opts),
212
248
  "second",
213
- "minute",
249
+ anchor,
214
250
  opts
215
251
  );
216
252
  }
@@ -225,12 +261,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
225
261
  return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
226
262
  }
227
263
  function renderMultipleMinutes(ir, plan, opts) {
228
- return listPastThe(
229
- segmentWords(ir.analyses.segments.minute, opts),
230
- "minute",
231
- "hour",
264
+ const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
265
+ return (stride ?? listPastThe(segmentWords(
266
+ ir.analyses.segments.minute,
232
267
  opts
233
- ) + trailingQualifier(ir, opts);
268
+ ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
234
269
  }
235
270
  function renderMinuteFrequency(ir, plan, opts) {
236
271
  let phrase = stepCycle60(
@@ -260,8 +295,9 @@ function renderMinutesAcrossHours(ir, plan, opts) {
260
295
  }
261
296
  const times = hourTimesFromPlan(ir, plan.times, true, opts);
262
297
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
263
- // The 'list' form is a minute list, which has segments.
264
- listPastThe(
298
+ // The 'list' form is a minute list, which has segments; an offset/uneven
299
+ // step enumerated to that list reads as a stride.
300
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
265
301
  segmentWords(ir.analyses.segments.minute, opts),
266
302
  "minute",
267
303
  "hour",
@@ -288,7 +324,13 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
288
324
  if (plan.form === "wildcard") {
289
325
  return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
290
326
  }
291
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
327
+ const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
328
+ segmentWords(ir.analyses.segments.minute, opts),
329
+ "minute",
330
+ "hour",
331
+ opts
332
+ ) : minuteRangeLead(ir.pattern.minute, opts);
333
+ return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
292
334
  }
293
335
  function minuteRangeLead(minuteField, opts) {
294
336
  const bounds = minuteField.split("-");
@@ -312,7 +354,12 @@ function rangeMinuteLead(ir, opts) {
312
354
  if (ir.pattern.minute === "0") {
313
355
  return "every hour";
314
356
  }
315
- return listPastThe(
357
+ return strideFromSegments(
358
+ ir.analyses.segments.minute,
359
+ "minute",
360
+ "hour",
361
+ opts
362
+ ) ?? listPastThe(
316
363
  segmentWords(ir.analyses.segments.minute, opts),
317
364
  "minute",
318
365
  "hour",
@@ -329,6 +376,12 @@ function hourWindow(window, opts) {
329
376
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
330
377
  }
331
378
  function renderClockTimes(ir, plan, opts) {
379
+ if (ir.shapes.minute === "single") {
380
+ const cadence = hourCadence(ir, +ir.pattern.minute, opts);
381
+ if (cadence !== null) {
382
+ return cadence;
383
+ }
384
+ }
332
385
  const plain = mixedTwelve(plan.times);
333
386
  const times = plan.times.map(function clock(time) {
334
387
  return getTime({
@@ -342,6 +395,10 @@ function renderClockTimes(ir, plan, opts) {
342
395
  }
343
396
  function renderCompactClockTimes(ir, plan, opts) {
344
397
  if (plan.fold) {
398
+ const cadence = hourCadence(ir, +plan.minute, opts);
399
+ if (cadence !== null) {
400
+ return cadence;
401
+ }
345
402
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
346
403
  return segment.kind === "range";
347
404
  });
@@ -352,13 +409,14 @@ function renderCompactClockTimes(ir, plan, opts) {
352
409
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
353
410
  }
354
411
  const phrase = (
355
- // The non-fold branch is a minute list, which has segments.
356
- listPastThe(
412
+ // The non-fold branch is a minute list, which has segments. An
413
+ // offset/uneven step enumerated to that list reads as a stride.
414
+ (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
357
415
  segmentWords(ir.analyses.segments.minute, opts),
358
416
  "minute",
359
417
  "hour",
360
418
  opts
361
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
419
+ )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
362
420
  );
363
421
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
364
422
  }
@@ -406,24 +464,50 @@ var renderers = {
406
464
  singleMinute: renderSingleMinute,
407
465
  standaloneSeconds: renderStandaloneSeconds
408
466
  };
467
+ function renderStride(stride, opts) {
468
+ const { interval, start, last, cycle, unit, anchor } = stride;
469
+ const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
470
+ const tiles = cycle % interval === 0;
471
+ if (start === 0 && tiles) {
472
+ return cadence;
473
+ }
474
+ if (start < interval && tiles) {
475
+ return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
476
+ }
477
+ const num = seriesNumber([start, last], opts);
478
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
479
+ }
480
+ function singleValues(segments) {
481
+ const values = [];
482
+ for (const segment of segments) {
483
+ if (segment.kind !== "single") {
484
+ return null;
485
+ }
486
+ values.push(+segment.value);
487
+ }
488
+ return values;
489
+ }
490
+ function strideFromSegments(segments, unit, anchor, opts) {
491
+ const values = singleValues(segments);
492
+ const step = values && arithmeticStep(values);
493
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
494
+ }
409
495
  function stepCycle60(segment, unit, anchor, opts) {
410
496
  if (segment.startToken.indexOf("-") !== -1) {
411
497
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
412
498
  }
413
499
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
414
- const interval = segment.interval;
415
- if (start !== 0) {
416
- if (segment.fires.length <= 3) {
417
- return listPastThe(
418
- numberWords(segment.fires, opts),
419
- unit,
420
- anchor,
421
- opts
422
- );
423
- }
424
- return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
500
+ if (start !== 0 && segment.fires.length <= 3) {
501
+ return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
425
502
  }
426
- return "every " + getNumber(interval, opts) + " " + unit + "s";
503
+ return renderStride({
504
+ interval: segment.interval,
505
+ start,
506
+ last: segment.fires[segment.fires.length - 1],
507
+ cycle: 60,
508
+ unit,
509
+ anchor
510
+ }, opts);
427
511
  }
428
512
  function stepHours(segment, opts) {
429
513
  if (segment.startToken.indexOf("-") !== -1) {
@@ -439,6 +523,68 @@ function stepHours(segment, opts) {
439
523
  }
440
524
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
441
525
  }
526
+ function hourStrideCadence(stride, opts) {
527
+ const { start, interval, last } = stride;
528
+ const cadence = "every " + getNumber(interval, opts) + " hours";
529
+ const tiles = 24 % interval === 0;
530
+ if (start === 0 && tiles) {
531
+ return cadence;
532
+ }
533
+ if (start < interval && tiles) {
534
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
535
+ }
536
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
537
+ }
538
+ function hourStride(ir) {
539
+ const segments = ir.analyses.segments.hour;
540
+ if (segments.length === 1 && segments[0].kind === "step") {
541
+ const segment = segments[0];
542
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
543
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
544
+ }
545
+ const values = singleValues(segments);
546
+ const step = values && arithmeticStep(values);
547
+ return step || null;
548
+ }
549
+ function subMinuteSecond(ir) {
550
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
551
+ }
552
+ function hourCadenceLead(ir, minute, opts) {
553
+ if (minute === 0) {
554
+ if (subMinuteSecond(ir)) {
555
+ return secondsClause(ir, "minute", opts) + " for one minute";
556
+ }
557
+ return secondsClause(ir, "hour", opts);
558
+ }
559
+ const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
560
+ if (ir.pattern.second === "0") {
561
+ return minutePhrase;
562
+ }
563
+ return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
564
+ }
565
+ function hourCadence(ir, minute, opts) {
566
+ const stride = hourStride(ir);
567
+ if (!stride) {
568
+ return null;
569
+ }
570
+ const fires = (stride.last - stride.start) / stride.interval + 1;
571
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
572
+ return null;
573
+ }
574
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
575
+ if (confinement) {
576
+ return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
577
+ }
578
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
579
+ }
580
+ function cleanStrideSegment(ir) {
581
+ const segments = ir.analyses.segments.hour;
582
+ const segment = segments.length === 1 && segments[0];
583
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
584
+ return null;
585
+ }
586
+ return segment;
587
+ }
442
588
  function seriesNumber(values, opts) {
443
589
  const anyBig = values.some(function big(v) {
444
590
  return +v > 10;
package/dist/lang/es.cjs CHANGED
@@ -50,12 +50,28 @@ var weekdayNumbers = {
50
50
  FRI: 5,
51
51
  SAT: 6
52
52
  };
53
+ var maxClockTimes = 6;
53
54
 
54
55
  // src/core/util.ts
55
56
  function isNonNegativeInteger(value) {
56
57
  const digits = /^\d+$/;
57
58
  return digits.test(value);
58
59
  }
60
+ function arithmeticStep(values) {
61
+ if (values.length < 5) {
62
+ return null;
63
+ }
64
+ const interval = values[1] - values[0];
65
+ if (interval < 2) {
66
+ return null;
67
+ }
68
+ for (let i = 2; i < values.length; i += 1) {
69
+ if (values[i] - values[i - 1] !== interval) {
70
+ return null;
71
+ }
72
+ }
73
+ return { start: values[0], interval, last: values[values.length - 1] };
74
+ }
59
75
  function toFieldNumber(token, numberMap) {
60
76
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
61
77
  }
@@ -173,19 +189,31 @@ function renderSecondsWithinMinute(ir, plan, opts) {
173
189
  }
174
190
  return secondsLeadClause(ir, opts) + ", en el minuto " + minuteField + " de cada hora" + trailingQualifier(ir, opts);
175
191
  }
192
+ function secondsListAtClock(ir, rest, opts) {
193
+ const clockPhrases = rest.times.map(function clock(time) {
194
+ return atTime(timePhrase(time.hour, time.minute, null, opts));
195
+ });
196
+ const grouped = groupClockTimesByArticle(clockPhrases);
197
+ const clockList = grouped.startsWith("a ") ? grouped.slice(2) : grouped;
198
+ const stride = strideFromSegments(fieldSegments(ir, "second"), "segundo", "", opts);
199
+ const secondsPhrase = stride ?? "en los segundos " + joinList(segmentWords(fieldSegments(ir, "second")));
200
+ const dayFrame = trailingQualifier(ir, opts);
201
+ return (dayFrame ? dayFrame.trimStart() + ", " : "") + secondsPhrase + " de " + clockList;
202
+ }
203
+ function composeHourCadence(ir, plan, opts) {
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;
206
+ }
176
207
  function renderComposeSeconds(ir, plan, opts) {
208
+ const hourCad = composeHourCadence(ir, plan, opts);
209
+ if (hourCad !== null) {
210
+ return hourCad;
211
+ }
177
212
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
178
213
  return pinnedMinuteSeconds(ir, plan.rest, opts);
179
214
  }
180
215
  if (plan.rest.kind === "clockTimes" && ir.shapes.second === "list") {
181
- const clockPhrases = plan.rest.times.map(function clock(time) {
182
- return atTime(timePhrase(time.hour, time.minute, null, opts));
183
- });
184
- const grouped = groupClockTimesByArticle(clockPhrases);
185
- const clockList = grouped.startsWith("a ") ? grouped.slice(2) : grouped;
186
- const secondsPhrase = "en los segundos " + joinList(segmentWords(fieldSegments(ir, "second")));
187
- const dayFrame = trailingQualifier(ir, opts);
188
- return (dayFrame ? dayFrame.trimStart() + ", " : "") + secondsPhrase + " de " + clockList;
216
+ return secondsListAtClock(ir, plan.rest, opts);
189
217
  }
190
218
  if (plan.rest.kind === "hourRange" && ir.shapes.second === "step" && ir.pattern.weekday !== "*") {
191
219
  const restNode = plan.rest;
@@ -194,8 +222,18 @@ function renderComposeSeconds(ir, plan, opts) {
194
222
  const cadence = "cada " + numero(stepSegment(ir.analyses.segments.second).interval, opts) + " segundos del minuto " + ir.pattern.minute;
195
223
  return dayFrame + ", " + window + ", " + cadence;
196
224
  }
225
+ if (isEveryOtherMinuteSeconds(ir, plan)) {
226
+ return secondsLeadClause(ir, opts) + " de " + render(ir, plan.rest, opts);
227
+ }
197
228
  return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
198
229
  }
230
+ function isEveryOtherMinuteSeconds(ir, plan) {
231
+ if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
232
+ return false;
233
+ }
234
+ const minuteStep = stepSegment(ir.analyses.segments.minute);
235
+ return minuteStep.startToken === "*" && minuteStep.interval === 2;
236
+ }
199
237
  function pinnedMinuteSeconds(ir, rest, opts) {
200
238
  const dayTrail = leadingQualifier(ir, opts).trimEnd();
201
239
  const trail = dayTrail ? ", " + dayTrail : "";
@@ -205,6 +243,9 @@ function pinnedMinuteSeconds(ir, rest, opts) {
205
243
  return secondsLeadClause(ir, opts) + " de " + explicitClockList(rest.times, opts) + trail;
206
244
  }
207
245
  function secondsLeadClause(ir, opts) {
246
+ return secondsClause(ir, "minuto", opts);
247
+ }
248
+ function secondsClause(ir, anchor, opts) {
208
249
  const secondField = ir.pattern.second;
209
250
  const shape = ir.shapes.second;
210
251
  if (secondField === "*") {
@@ -214,18 +255,23 @@ function secondsLeadClause(ir, opts) {
214
255
  return stepCycle60(
215
256
  stepSegment(ir.analyses.segments.second),
216
257
  "segundo",
217
- "minuto",
258
+ anchor,
218
259
  opts
219
260
  );
220
261
  }
221
262
  if (shape === "range") {
222
263
  const bounds = secondField.split("-");
223
- return "cada segundo del " + bounds[0] + " al " + bounds[1] + " de cada minuto";
264
+ return "cada segundo del " + bounds[0] + " al " + bounds[1] + " de cada " + anchor;
224
265
  }
225
266
  if (shape === "single") {
226
- return "en el segundo " + secondField + " de cada minuto";
267
+ return "en el segundo " + secondField + " de cada " + anchor;
227
268
  }
228
- return "en los segundos " + joinList(segmentWords(fieldSegments(ir, "second"))) + " de cada minuto";
269
+ return strideFromSegments(
270
+ fieldSegments(ir, "second"),
271
+ "segundo",
272
+ anchor,
273
+ opts
274
+ ) ?? "en los segundos " + joinList(segmentWords(fieldSegments(ir, "second"))) + " de cada " + anchor;
229
275
  }
230
276
  function renderEveryMinute(ir, plan, opts) {
231
277
  return "cada minuto" + trailingQualifier(ir, opts);
@@ -237,10 +283,15 @@ function renderRangeOfMinutes(ir, plan, opts) {
237
283
  return minuteRangeLead(ir.pattern.minute) + " de cada hora" + trailingQualifier(ir, opts);
238
284
  }
239
285
  function renderMultipleMinutes(ir, plan, opts) {
240
- return minutesList(ir) + trailingQualifier(ir, opts);
286
+ return minutesList(ir, opts) + trailingQualifier(ir, opts);
241
287
  }
242
- function minutesList(ir) {
243
- return "en los minutos " + joinList(segmentWords(fieldSegments(ir, "minute"))) + " de cada hora";
288
+ function minutesList(ir, opts) {
289
+ return strideFromSegments(
290
+ fieldSegments(ir, "minute"),
291
+ "minuto",
292
+ "hora",
293
+ opts
294
+ ) ?? "en los minutos " + joinList(segmentWords(fieldSegments(ir, "minute"))) + " de cada hora";
244
295
  }
245
296
  function minuteRangeLead(minuteField) {
246
297
  const bounds = minuteField.split("-");
@@ -328,7 +379,7 @@ function renderMinutesAcrossHours(ir, plan, opts) {
328
379
  }
329
380
  return "cada minuto " + hourSpanFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
330
381
  }
331
- const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir);
382
+ const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute) : minutesList(ir, opts);
332
383
  return lead + ", " + atHourTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
333
384
  }
334
385
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
@@ -336,7 +387,8 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
336
387
  if (plan.form === "wildcard") {
337
388
  return "cada minuto, " + stepHourSpan(segment, opts) + trailingQualifier(ir, opts);
338
389
  }
339
- return minuteRangeLead(ir.pattern.minute) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
390
+ const lead = plan.form === "list" ? minutesList(ir, opts) : minuteRangeLead(ir.pattern.minute);
391
+ return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
340
392
  }
341
393
  function renderEveryHour(ir, plan, opts) {
342
394
  return "cada hora" + trailingQualifier(ir, opts);
@@ -352,7 +404,7 @@ function renderHourRange(ir, plan, opts) {
352
404
  if (ir.pattern.minute === "0") {
353
405
  return "cada hora " + window + trailingQualifier(ir, opts);
354
406
  }
355
- const lead = ir.shapes.minute === "single" ? "en el minuto " + ir.pattern.minute + " de cada hora" : minutesList(ir);
407
+ const lead = ir.shapes.minute === "single" ? "en el minuto " + ir.pattern.minute + " de cada hora" : minutesList(ir, opts);
356
408
  return lead + ", " + window + trailingQualifier(ir, opts);
357
409
  }
358
410
  function renderHourStep(ir, plan, opts) {
@@ -426,6 +478,12 @@ function unionYaseaSuffix(ir, opts) {
426
478
  return ", ya sea " + domArm(ir, opts) + " o " + dowArm(ir);
427
479
  }
428
480
  function renderClockTimes(ir, plan, opts) {
481
+ if (ir.shapes.minute === "single") {
482
+ const cadence = hourCadence(ir, +ir.pattern.minute, opts);
483
+ if (cadence !== null) {
484
+ return cadence;
485
+ }
486
+ }
429
487
  const phrases = plan.times.map(function clock(time) {
430
488
  return atTime(timePhrase(time.hour, time.minute, time.second, opts));
431
489
  });
@@ -607,6 +665,10 @@ function groupClockTimesByArticle(phrases) {
607
665
  }
608
666
  function renderCompactClockTimes(ir, plan, opts) {
609
667
  if (plan.fold) {
668
+ const cadence = hourCadence(ir, plan.minute, opts);
669
+ if (cadence !== null) {
670
+ return cadence;
671
+ }
610
672
  const ranged = hourSegments(ir).some(function range(segment) {
611
673
  return segment.kind === "range";
612
674
  });
@@ -615,7 +677,7 @@ function renderCompactClockTimes(ir, plan, opts) {
615
677
  }
616
678
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
617
679
  }
618
- const phrase = minutesList(ir) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
680
+ const phrase = minutesList(ir, opts) + ", " + hourSegmentTimes(ir, 0, null, opts) + trailingQualifier(ir, opts);
619
681
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
620
682
  }
621
683
  var renderers = {
@@ -638,19 +700,50 @@ var renderers = {
638
700
  singleMinute: renderSingleMinute,
639
701
  standaloneSeconds: renderStandaloneSeconds
640
702
  };
703
+ function renderStride(stride, opts) {
704
+ const { interval, start, last, cycle, unit, anchor } = stride;
705
+ const cadence = "cada " + numero(interval, opts) + " " + unit + "s";
706
+ const tiles = cycle % interval === 0;
707
+ if (start === 0 && tiles) {
708
+ return cadence;
709
+ }
710
+ const tail = anchor ? " de cada " + anchor : "";
711
+ if (start < interval && tiles) {
712
+ return cadence + " a partir del " + unit + " " + start + tail;
713
+ }
714
+ return cadence + " del " + unit + " " + start + " al " + last + tail;
715
+ }
641
716
  function stepCycle60(segment, unit, anchor, opts) {
642
717
  if (segment.startToken.indexOf("-") !== -1) {
643
718
  return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
644
719
  }
645
720
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
646
- const interval = segment.interval;
647
- if (start !== 0) {
648
- if (segment.fires.length <= 3) {
649
- return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
721
+ if (start !== 0 && segment.fires.length <= 3) {
722
+ return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
723
+ }
724
+ return renderStride({
725
+ interval: segment.interval,
726
+ start,
727
+ last: segment.fires[segment.fires.length - 1],
728
+ cycle: 60,
729
+ unit,
730
+ anchor
731
+ }, opts);
732
+ }
733
+ function strideFromSegments(segments, unit, anchor, opts) {
734
+ const values = singleValues(segments);
735
+ const step = values && arithmeticStep(values);
736
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
737
+ }
738
+ function singleValues(segments) {
739
+ const values = [];
740
+ for (const segment of segments) {
741
+ if (segment.kind !== "single") {
742
+ return null;
650
743
  }
651
- return "cada " + numero(interval, opts) + " " + unit + "s a partir del " + unit + " " + start + " de cada " + anchor;
744
+ values.push(+segment.value);
652
745
  }
653
- return "cada " + numero(interval, opts) + " " + unit + "s";
746
+ return values;
654
747
  }
655
748
  function stepHours(segment, opts) {
656
749
  if (segment.startToken.indexOf("-") !== -1) {
@@ -666,6 +759,68 @@ function stepHours(segment, opts) {
666
759
  }
667
760
  return "cada " + numero(interval, opts) + " horas a partir de " + timePhrase(start, 0, null, opts);
668
761
  }
762
+ function hourStrideCadence(stride, opts) {
763
+ const { start, interval, last } = stride;
764
+ const cadence = "cada " + numero(interval, opts) + " horas";
765
+ const tiles = 24 % interval === 0;
766
+ if (start === 0 && tiles) {
767
+ return cadence;
768
+ }
769
+ if (start < interval && tiles) {
770
+ return cadence + " a partir de " + timePhrase(start, 0, null, opts);
771
+ }
772
+ return cadence + " de " + timePhrase(start, 0, null, opts) + " a " + timePhrase(last, 0, null, opts);
773
+ }
774
+ function hourStride(ir) {
775
+ const segments = fieldSegments(ir, "hour");
776
+ if (segments.length === 1 && segments[0].kind === "step") {
777
+ const segment = segments[0];
778
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
779
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
780
+ }
781
+ const values = singleValues(segments);
782
+ const step = values && arithmeticStep(values);
783
+ return step || null;
784
+ }
785
+ function subMinuteSecond(ir) {
786
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
787
+ }
788
+ function hourCadenceLead(ir, minute, opts) {
789
+ if (minute === 0) {
790
+ if (subMinuteSecond(ir)) {
791
+ return secondsClause(ir, "minuto", opts) + " durante un minuto";
792
+ }
793
+ return secondsClause(ir, "hora", opts);
794
+ }
795
+ const minutePhrase = "en el minuto " + minute;
796
+ if (ir.pattern.second === "0") {
797
+ return minutePhrase;
798
+ }
799
+ return secondsClause(ir, "minuto", opts) + ", " + minutePhrase;
800
+ }
801
+ function hourCadence(ir, minute, opts) {
802
+ const stride = hourStride(ir);
803
+ if (!stride) {
804
+ return null;
805
+ }
806
+ const fires = (stride.last - stride.start) / stride.interval + 1;
807
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
808
+ return null;
809
+ }
810
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
811
+ if (confinement) {
812
+ return secondsClause(ir, "minuto", opts) + " durante un minuto, " + stepHourSpan(confinement, opts) + trailingQualifier(ir, opts);
813
+ }
814
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
815
+ }
816
+ function cleanStrideSegment(ir) {
817
+ const segments = fieldSegments(ir, "hour");
818
+ const segment = segments.length === 1 && segments[0];
819
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1) {
820
+ return null;
821
+ }
822
+ return segment;
823
+ }
669
824
  function atTimes(hours, opts) {
670
825
  return hours.map(function each(hour) {
671
826
  return atTime(timePhrase(hour, 0, null, opts));