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/de.js CHANGED
@@ -14,12 +14,28 @@ var weekdayNumbers = {
14
14
  FRI: 5,
15
15
  SAT: 6
16
16
  };
17
+ var maxClockTimes = 6;
17
18
 
18
19
  // src/core/util.ts
19
20
  function isNonNegativeInteger(value) {
20
21
  const digits = /^\d+$/;
21
22
  return digits.test(value);
22
23
  }
24
+ function arithmeticStep(values) {
25
+ if (values.length < 5) {
26
+ return null;
27
+ }
28
+ const interval = values[1] - values[0];
29
+ if (interval < 2) {
30
+ return null;
31
+ }
32
+ for (let i = 2; i < values.length; i += 1) {
33
+ if (values[i] - values[i - 1] !== interval) {
34
+ return null;
35
+ }
36
+ }
37
+ return { start: values[0], interval, last: values[values.length - 1] };
38
+ }
23
39
  function toFieldNumber(token, numberMap) {
24
40
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
25
41
  }
@@ -86,6 +102,49 @@ function stepSegment(segments) {
86
102
  function cleanStep(segment, cycle) {
87
103
  return (segment.startToken === "*" || +segment.startToken === 0) && cycle % segment.interval === 0;
88
104
  }
105
+ function renderStride(stride) {
106
+ const { interval, start, last, cycle, unit, anchor } = stride;
107
+ const cadence = everyN(interval, unit);
108
+ const tiles = cycle % interval === 0;
109
+ if (start === 0 && tiles) {
110
+ return cadence;
111
+ }
112
+ const tail = anchor ? " " + anchor : "";
113
+ if (start < interval && tiles) {
114
+ return cadence + " ab " + unit.singular + " " + start + tail;
115
+ }
116
+ return cadence + " von " + unit.singular + " " + start + " bis " + last + tail;
117
+ }
118
+ function stepClause(segment, unit, anchor) {
119
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
120
+ const short = start !== 0 && segment.fires.length <= 3;
121
+ if (segment.startToken.indexOf("-") !== -1 || short) {
122
+ return "in den " + unit.plural + " " + joinList(segment.fires.map(String)) + " " + anchor;
123
+ }
124
+ return renderStride({
125
+ interval: segment.interval,
126
+ start,
127
+ last: segment.fires[segment.fires.length - 1],
128
+ cycle: 60,
129
+ unit,
130
+ anchor
131
+ });
132
+ }
133
+ function singleValues(segments) {
134
+ const values = [];
135
+ for (const segment of segments) {
136
+ if (segment.kind !== "single") {
137
+ return null;
138
+ }
139
+ values.push(+segment.value);
140
+ }
141
+ return values;
142
+ }
143
+ function strideFromSegments(segments, unit, anchor) {
144
+ const values = singleValues(segments);
145
+ const step = values && arithmeticStep(values);
146
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }) : null;
147
+ }
89
148
  var weekdayNames = [
90
149
  "sonntags",
91
150
  "montags",
@@ -152,6 +211,13 @@ function everyNthHour(segment) {
152
211
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
153
212
  return start === 0 ? base : base + " ab " + start + " Uhr";
154
213
  }
214
+ function confinedHourStride(segment) {
215
+ if (segment.startToken.indexOf("-") !== -1) {
216
+ return false;
217
+ }
218
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
219
+ return 24 % segment.interval === 0 && start < segment.interval;
220
+ }
155
221
  function weekdayNoun(token) {
156
222
  return weekdayNouns[toFieldNumber(token, weekdayNumbers)];
157
223
  }
@@ -255,14 +321,17 @@ function countedPhrase(ir, field, singular, plural) {
255
321
  return "in den " + plural + " " + joinList(fieldValues(ir, field));
256
322
  }
257
323
  function secondsLead(ir) {
324
+ return secondsClause(ir, "jeder Minute");
325
+ }
326
+ function secondsClause(ir, anchor) {
258
327
  if (ir.pattern.second === "*") {
259
328
  return "jede Sekunde";
260
329
  }
261
330
  const segments = ir.analyses.segments.second;
262
- if (ir.shapes.second === "step" && cleanStep(stepSegment(segments), 60)) {
263
- return everyN(stepSegment(segments).interval, UNITS.second);
331
+ if (ir.shapes.second === "step") {
332
+ return stepClause(stepSegment(segments), UNITS.second, anchor);
264
333
  }
265
- return countedPhrase(ir, "second", "Sekunde", "Sekunden") + " jeder Minute";
334
+ return strideFromSegments(segments, UNITS.second, anchor) ?? countedPhrase(ir, "second", "Sekunde", "Sekunden") + " " + anchor;
266
335
  }
267
336
  function spanTime(hour, minute, sep) {
268
337
  return hour + sep + pad(minute);
@@ -331,8 +400,15 @@ function renderEveryHour() {
331
400
  function renderSeconds(ir) {
332
401
  return secondsLead(ir);
333
402
  }
403
+ function minutePastClause(ir) {
404
+ return strideFromSegments(
405
+ fieldSegments(ir, "minute"),
406
+ UNITS.minute,
407
+ "jeder Stunde"
408
+ ) ?? countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
409
+ }
334
410
  function renderMinutePast(ir) {
335
- return countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
411
+ return minutePastClause(ir);
336
412
  }
337
413
  function renderSecondsWithinMinute(ir, plan) {
338
414
  if (plan.singleSecond) {
@@ -357,9 +433,21 @@ function renderMinuteSpanInHour(ir, plan, opts) {
357
433
  return "jede Minute von " + spanTime(plan.hour, plan.span[0], sep) + " bis " + spanTime(plan.hour, plan.span[1], sep) + " Uhr";
358
434
  }
359
435
  function renderComposeSeconds(ir, plan, opts) {
436
+ if ((plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes") && ir.shapes.minute === "single") {
437
+ const cadence = hourCadence(ir, +ir.pattern.minute);
438
+ if (cadence !== null) {
439
+ return cadence;
440
+ }
441
+ }
360
442
  if (composeMinuteZero(ir, plan)) {
361
443
  return secondsLead(ir) + " " + clockMinuteGenitive(plan.rest.times, opts.style.sep);
362
444
  }
445
+ if (plan.rest.kind === "minuteFrequency" && ir.shapes.second === "wildcard" && ir.shapes.hour === "wildcard") {
446
+ const minuteStep = stepSegment(ir.analyses.segments.minute);
447
+ if (minuteStep.startToken === "*" && minuteStep.interval === 2) {
448
+ return secondsLead(ir) + " jeder zweiten Minute";
449
+ }
450
+ }
363
451
  return secondsLead(ir) + ", " + render(ir, plan.rest, opts);
364
452
  }
365
453
  function composeMinuteZero(ir, plan) {
@@ -377,29 +465,35 @@ function renderMinutesAcrossHours(ir, plan, opts) {
377
465
  return "jede Minute " + duringHours(ir, plan.times, sep);
378
466
  }
379
467
  const hours = plan.times.kind === "fires" ? atHours(plan.times.fires) : joinList(hourSegmentParts(ir, 0, 0, sep));
380
- return countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hours;
468
+ return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
381
469
  }
382
470
  function renderMinuteSpanAcrossHourStep(ir, plan) {
383
471
  if (plan.form === "wildcard") {
384
472
  return "jede Minute " + everyNthHour(stepSegment(ir.analyses.segments.hour));
385
473
  }
386
- return countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hourStepPhrase(ir);
474
+ const segment = stepSegment(ir.analyses.segments.hour);
475
+ const hours = confinedHourStride(segment) ? everyNthHour(segment) : atHours(segment.fires);
476
+ return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
387
477
  }
388
478
  function renderCompactClockTimes(ir, plan, opts) {
389
479
  const sep = opts.style.sep;
390
480
  if (plan.fold) {
481
+ const cadence = hourCadence(ir, plan.minute);
482
+ if (cadence !== null) {
483
+ return cadence;
484
+ }
391
485
  const hourly = fieldSegments(ir, "hour").some((segment) => segment.kind === "range");
392
486
  return (hourly ? "st\xFCndlich " : "t\xE4glich ") + joinList(hourSegmentParts(ir, plan.minute, ir.analyses.clockSecond, sep));
393
487
  }
394
488
  const hours = fieldSegments(ir, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(ir, 0, 0, sep)) : atHours(hourFires(ir));
395
489
  const lead = ir.analyses.clockSecond ? countedPhrase(ir, "second", "Sekunde", "Sekunden") + ", " : "";
396
- return lead + countedPhrase(ir, "minute", "Minute", "Minuten") + ", " + hours;
490
+ return lead + (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
397
491
  }
398
492
  function renderMinuteFrequency(ir, plan, opts) {
399
493
  const segment = stepSegment(ir.analyses.segments.minute);
400
494
  const sep = opts.style.sep;
401
495
  const clean = cleanStep(segment, 60);
402
- const base = clean ? everyN(segment.interval, UNITS.minute) : countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
496
+ const base = stepClause(segment, UNITS.minute, "jeder Stunde");
403
497
  if (plan.hours.kind === "window") {
404
498
  const window = hourWindow(
405
499
  plan.hours.from,
@@ -421,6 +515,67 @@ function hourStepPhrase(ir) {
421
515
  const segment = stepSegment(ir.analyses.segments.hour);
422
516
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
423
517
  }
518
+ function hourStrideCadence(stride) {
519
+ const { start, interval, last } = stride;
520
+ const cadence = everyN(interval, UNITS.hour);
521
+ const tiles = 24 % interval === 0;
522
+ if (start === 0 && tiles) {
523
+ return cadence;
524
+ }
525
+ if (start < interval && tiles) {
526
+ return cadence + " ab " + start + " Uhr";
527
+ }
528
+ return cadence + " von " + start + " bis " + last + " Uhr";
529
+ }
530
+ function hourStride(ir) {
531
+ const segments = fieldSegments(ir, "hour");
532
+ if (!segments) {
533
+ return null;
534
+ }
535
+ if (segments.length === 1 && segments[0].kind === "step") {
536
+ const segment = segments[0];
537
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
538
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
539
+ }
540
+ const values = singleValues(segments);
541
+ const step = values && arithmeticStep(values);
542
+ return step || null;
543
+ }
544
+ function subMinuteSecond(ir) {
545
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
546
+ }
547
+ function hourCadenceLead(ir, minute) {
548
+ if (minute === 0) {
549
+ if (subMinuteSecond(ir)) {
550
+ return secondsClause(ir, "jeder Minute") + " f\xFCr eine Minute";
551
+ }
552
+ return secondsClause(ir, "jeder Stunde");
553
+ }
554
+ const minutePhrase = "in Minute " + minute;
555
+ if (ir.pattern.second === "0") {
556
+ return minutePhrase;
557
+ }
558
+ return secondsClause(ir, "jeder Minute") + ", " + minutePhrase;
559
+ }
560
+ function hourCadence(ir, minute) {
561
+ const stride = hourStride(ir);
562
+ if (!stride) {
563
+ return null;
564
+ }
565
+ const fires = (stride.last - stride.start) / stride.interval + 1;
566
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
567
+ return null;
568
+ }
569
+ const segment = fieldSegments(ir, "hour")[0];
570
+ const confined = minute === 0 && subMinuteSecond(ir) && fieldSegments(ir, "hour").length === 1 && segment.kind === "step" && confinedHourStride(segment);
571
+ if (confined) {
572
+ return secondsClause(ir, "jeder Minute") + " f\xFCr eine Minute " + everyNthHour(segment);
573
+ }
574
+ return hourCadenceLead(ir, minute) + ", " + hourStrideCadence(stride);
575
+ }
576
+ function hourCadenceApplies(ir) {
577
+ return ir.shapes.minute === "single" && hourCadence(ir, +ir.pattern.minute) !== null;
578
+ }
424
579
  function renderHourRange(ir, plan, opts) {
425
580
  const window = hourWindow(
426
581
  plan.from,
@@ -437,6 +592,12 @@ function renderHourRange(ir, plan, opts) {
437
592
  return countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde, " + window;
438
593
  }
439
594
  function renderClockTimes(ir, plan, opts) {
595
+ if (ir.shapes.minute === "single") {
596
+ const cadence = hourCadence(ir, +ir.pattern.minute);
597
+ if (cadence !== null) {
598
+ return cadence;
599
+ }
600
+ }
440
601
  return "um " + timesPhrase(plan.times, opts.style.sep);
441
602
  }
442
603
  var renderers = {
@@ -486,6 +647,9 @@ function isComposeMinuteZero(ir) {
486
647
  return ir.plan.kind === "composeSeconds" && composeMinuteZero(ir, ir.plan);
487
648
  }
488
649
  function needsDailyFrame(ir) {
650
+ if (hourCadenceApplies(ir)) {
651
+ return false;
652
+ }
489
653
  if (ir.plan.kind === "clockTimes" || isComposeMinuteZero(ir)) {
490
654
  return true;
491
655
  }
package/dist/lang/en.cjs CHANGED
@@ -24,6 +24,26 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
+ // src/core/util.ts
28
+ function arithmeticStep(values) {
29
+ if (values.length < 5) {
30
+ return null;
31
+ }
32
+ const interval = values[1] - values[0];
33
+ if (interval < 2) {
34
+ return null;
35
+ }
36
+ for (let i = 2; i < values.length; i += 1) {
37
+ if (values[i] - values[i - 1] !== interval) {
38
+ return null;
39
+ }
40
+ }
41
+ return { start: values[0], interval, last: values[values.length - 1] };
42
+ }
43
+
44
+ // src/core/specs.ts
45
+ var maxClockTimes = 6;
46
+
27
47
  // src/core/format.ts
28
48
  function pad(n) {
29
49
  n = "" + n;
@@ -179,7 +199,15 @@ function renderSecondsWithinMinute(ir, plan, opts) {
179
199
  }
180
200
  return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
181
201
  }
202
+ function composeHourCadence(ir, plan, opts) {
203
+ const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
204
+ return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
205
+ }
182
206
  function renderComposeSeconds(ir, plan, opts) {
207
+ const cadence = composeHourCadence(ir, plan, opts);
208
+ if (cadence !== null) {
209
+ return cadence;
210
+ }
183
211
  if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
184
212
  const minute = plan.rest.times[0].minute;
185
213
  if (+minute === 0) {
@@ -212,6 +240,9 @@ function clockTimesOf(ir, plan, opts) {
212
240
  return joinList(times, opts) + (trail && ", " + trail);
213
241
  }
214
242
  function secondsLeadClause(ir, opts) {
243
+ return secondsClause(ir, "minute", opts);
244
+ }
245
+ function secondsClause(ir, anchor, opts) {
215
246
  const secondField = ir.pattern.second;
216
247
  const shape = ir.shapes.second;
217
248
  if (secondField === "*") {
@@ -221,22 +252,27 @@ function secondsLeadClause(ir, opts) {
221
252
  return stepCycle60(
222
253
  ir.analyses.segments.second[0],
223
254
  "second",
224
- "minute",
255
+ anchor,
225
256
  opts
226
257
  );
227
258
  }
228
259
  if (shape === "range") {
229
260
  const bounds = secondField.split("-");
230
261
  const num = seriesNumber(bounds, opts);
231
- return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the minute";
262
+ return "every second from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the " + anchor;
232
263
  }
233
264
  if (shape === "single") {
234
- return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute";
265
+ return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
235
266
  }
236
- return listPastThe(
267
+ return strideFromSegments(
268
+ ir.analyses.segments.second,
269
+ "second",
270
+ anchor,
271
+ opts
272
+ ) ?? listPastThe(
237
273
  segmentWords(ir.analyses.segments.second, opts),
238
274
  "second",
239
- "minute",
275
+ anchor,
240
276
  opts
241
277
  );
242
278
  }
@@ -251,12 +287,11 @@ function renderRangeOfMinutes(ir, plan, opts) {
251
287
  return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
252
288
  }
253
289
  function renderMultipleMinutes(ir, plan, opts) {
254
- return listPastThe(
255
- segmentWords(ir.analyses.segments.minute, opts),
256
- "minute",
257
- "hour",
290
+ const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
291
+ return (stride ?? listPastThe(segmentWords(
292
+ ir.analyses.segments.minute,
258
293
  opts
259
- ) + trailingQualifier(ir, opts);
294
+ ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
260
295
  }
261
296
  function renderMinuteFrequency(ir, plan, opts) {
262
297
  let phrase = stepCycle60(
@@ -286,8 +321,9 @@ function renderMinutesAcrossHours(ir, plan, opts) {
286
321
  }
287
322
  const times = hourTimesFromPlan(ir, plan.times, true, opts);
288
323
  const lead = plan.form === "range" ? minuteRangeLead(ir.pattern.minute, opts) : (
289
- // The 'list' form is a minute list, which has segments.
290
- listPastThe(
324
+ // The 'list' form is a minute list, which has segments; an offset/uneven
325
+ // step enumerated to that list reads as a stride.
326
+ strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
291
327
  segmentWords(ir.analyses.segments.minute, opts),
292
328
  "minute",
293
329
  "hour",
@@ -314,7 +350,13 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
314
350
  if (plan.form === "wildcard") {
315
351
  return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
316
352
  }
317
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
353
+ const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
354
+ segmentWords(ir.analyses.segments.minute, opts),
355
+ "minute",
356
+ "hour",
357
+ opts
358
+ ) : minuteRangeLead(ir.pattern.minute, opts);
359
+ return lead + ", " + stepHours(segment, opts) + trailingQualifier(ir, opts);
318
360
  }
319
361
  function minuteRangeLead(minuteField, opts) {
320
362
  const bounds = minuteField.split("-");
@@ -338,7 +380,12 @@ function rangeMinuteLead(ir, opts) {
338
380
  if (ir.pattern.minute === "0") {
339
381
  return "every hour";
340
382
  }
341
- return listPastThe(
383
+ return strideFromSegments(
384
+ ir.analyses.segments.minute,
385
+ "minute",
386
+ "hour",
387
+ opts
388
+ ) ?? listPastThe(
342
389
  segmentWords(ir.analyses.segments.minute, opts),
343
390
  "minute",
344
391
  "hour",
@@ -355,6 +402,12 @@ function hourWindow(window, opts) {
355
402
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
356
403
  }
357
404
  function renderClockTimes(ir, plan, opts) {
405
+ if (ir.shapes.minute === "single") {
406
+ const cadence = hourCadence(ir, +ir.pattern.minute, opts);
407
+ if (cadence !== null) {
408
+ return cadence;
409
+ }
410
+ }
358
411
  const plain = mixedTwelve(plan.times);
359
412
  const times = plan.times.map(function clock(time) {
360
413
  return getTime({
@@ -368,6 +421,10 @@ function renderClockTimes(ir, plan, opts) {
368
421
  }
369
422
  function renderCompactClockTimes(ir, plan, opts) {
370
423
  if (plan.fold) {
424
+ const cadence = hourCadence(ir, +plan.minute, opts);
425
+ if (cadence !== null) {
426
+ return cadence;
427
+ }
371
428
  const hasRange = ir.analyses.segments.hour.some(function range(segment) {
372
429
  return segment.kind === "range";
373
430
  });
@@ -378,13 +435,14 @@ function renderCompactClockTimes(ir, plan, opts) {
378
435
  return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts);
379
436
  }
380
437
  const phrase = (
381
- // The non-fold branch is a minute list, which has segments.
382
- listPastThe(
438
+ // The non-fold branch is a minute list, which has segments. An
439
+ // offset/uneven step enumerated to that list reads as a stride.
440
+ (strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
383
441
  segmentWords(ir.analyses.segments.minute, opts),
384
442
  "minute",
385
443
  "hour",
386
444
  opts
387
- ) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
445
+ )) + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts)
388
446
  );
389
447
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
390
448
  }
@@ -432,24 +490,50 @@ var renderers = {
432
490
  singleMinute: renderSingleMinute,
433
491
  standaloneSeconds: renderStandaloneSeconds
434
492
  };
493
+ function renderStride(stride, opts) {
494
+ const { interval, start, last, cycle, unit, anchor } = stride;
495
+ const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
496
+ const tiles = cycle % interval === 0;
497
+ if (start === 0 && tiles) {
498
+ return cadence;
499
+ }
500
+ if (start < interval && tiles) {
501
+ return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
502
+ }
503
+ const num = seriesNumber([start, last], opts);
504
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
505
+ }
506
+ function singleValues(segments) {
507
+ const values = [];
508
+ for (const segment of segments) {
509
+ if (segment.kind !== "single") {
510
+ return null;
511
+ }
512
+ values.push(+segment.value);
513
+ }
514
+ return values;
515
+ }
516
+ function strideFromSegments(segments, unit, anchor, opts) {
517
+ const values = singleValues(segments);
518
+ const step = values && arithmeticStep(values);
519
+ return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
520
+ }
435
521
  function stepCycle60(segment, unit, anchor, opts) {
436
522
  if (segment.startToken.indexOf("-") !== -1) {
437
523
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
438
524
  }
439
525
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
440
- const interval = segment.interval;
441
- if (start !== 0) {
442
- if (segment.fires.length <= 3) {
443
- return listPastThe(
444
- numberWords(segment.fires, opts),
445
- unit,
446
- anchor,
447
- opts
448
- );
449
- }
450
- return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
526
+ if (start !== 0 && segment.fires.length <= 3) {
527
+ return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
451
528
  }
452
- return "every " + getNumber(interval, opts) + " " + unit + "s";
529
+ return renderStride({
530
+ interval: segment.interval,
531
+ start,
532
+ last: segment.fires[segment.fires.length - 1],
533
+ cycle: 60,
534
+ unit,
535
+ anchor
536
+ }, opts);
453
537
  }
454
538
  function stepHours(segment, opts) {
455
539
  if (segment.startToken.indexOf("-") !== -1) {
@@ -465,6 +549,68 @@ function stepHours(segment, opts) {
465
549
  }
466
550
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
467
551
  }
552
+ function hourStrideCadence(stride, opts) {
553
+ const { start, interval, last } = stride;
554
+ const cadence = "every " + getNumber(interval, opts) + " hours";
555
+ const tiles = 24 % interval === 0;
556
+ if (start === 0 && tiles) {
557
+ return cadence;
558
+ }
559
+ if (start < interval && tiles) {
560
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
561
+ }
562
+ return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
563
+ }
564
+ function hourStride(ir) {
565
+ const segments = ir.analyses.segments.hour;
566
+ if (segments.length === 1 && segments[0].kind === "step") {
567
+ const segment = segments[0];
568
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
569
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
570
+ }
571
+ const values = singleValues(segments);
572
+ const step = values && arithmeticStep(values);
573
+ return step || null;
574
+ }
575
+ function subMinuteSecond(ir) {
576
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
577
+ }
578
+ function hourCadenceLead(ir, minute, opts) {
579
+ if (minute === 0) {
580
+ if (subMinuteSecond(ir)) {
581
+ return secondsClause(ir, "minute", opts) + " for one minute";
582
+ }
583
+ return secondsClause(ir, "hour", opts);
584
+ }
585
+ const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
586
+ if (ir.pattern.second === "0") {
587
+ return minutePhrase;
588
+ }
589
+ return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
590
+ }
591
+ function hourCadence(ir, minute, opts) {
592
+ const stride = hourStride(ir);
593
+ if (!stride) {
594
+ return null;
595
+ }
596
+ const fires = (stride.last - stride.start) / stride.interval + 1;
597
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
598
+ return null;
599
+ }
600
+ const confinement = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
601
+ if (confinement) {
602
+ return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(confinement, opts) + trailingQualifier(ir, opts);
603
+ }
604
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
605
+ }
606
+ function cleanStrideSegment(ir) {
607
+ const segments = ir.analyses.segments.hour;
608
+ const segment = segments.length === 1 && segments[0];
609
+ if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
610
+ return null;
611
+ }
612
+ return segment;
613
+ }
468
614
  function seriesNumber(values, opts) {
469
615
  const anyBig = values.some(function big(v) {
470
616
  return +v > 10;