cronli5 0.2.1 → 0.3.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/cronli5.min.js +2 -2
  3. package/dist/cronli5.cjs +471 -383
  4. package/dist/cronli5.js +471 -383
  5. package/dist/lang/de.cjs +286 -215
  6. package/dist/lang/de.js +286 -215
  7. package/dist/lang/en.cjs +413 -327
  8. package/dist/lang/en.js +413 -327
  9. package/dist/lang/es.cjs +303 -265
  10. package/dist/lang/es.js +303 -265
  11. package/dist/lang/fi.cjs +311 -266
  12. package/dist/lang/fi.js +311 -266
  13. package/dist/lang/zh.cjs +308 -236
  14. package/dist/lang/zh.js +308 -236
  15. package/package.json +1 -1
  16. package/src/core/analyze.ts +12 -12
  17. package/src/core/cadence.ts +164 -0
  18. package/src/core/index.ts +3 -1
  19. package/src/core/normalize.ts +3 -3
  20. package/src/core/parse.ts +1 -1
  21. package/src/core/{ir.ts → schedule.ts} +17 -18
  22. package/src/core/specs.ts +1 -1
  23. package/src/core/util.ts +3 -165
  24. package/src/core/validate.ts +1 -1
  25. package/src/core/weekday.ts +54 -0
  26. package/src/cronli5.ts +5 -5
  27. package/src/lang/de/index.ts +329 -219
  28. package/src/lang/en/dialects.ts +1 -1
  29. package/src/lang/en/index.ts +521 -372
  30. package/src/lang/es/index.ts +338 -286
  31. package/src/lang/es/notes.md +1 -1
  32. package/src/lang/fi/dialects.ts +1 -1
  33. package/src/lang/fi/index.ts +365 -299
  34. package/src/lang/fi/notes.md +23 -8
  35. package/src/lang/fi/status.json +1 -1
  36. package/src/lang/zh/index.ts +344 -237
  37. package/src/types.ts +6 -6
  38. package/types/core/analyze.d.ts +3 -3
  39. package/types/core/cadence.d.ts +33 -0
  40. package/types/core/index.d.ts +3 -1
  41. package/types/core/normalize.d.ts +1 -1
  42. package/types/core/parse.d.ts +1 -1
  43. package/types/core/{ir.d.ts → schedule.d.ts} +11 -16
  44. package/types/core/specs.d.ts +1 -1
  45. package/types/core/util.d.ts +1 -30
  46. package/types/core/weekday.d.ts +10 -0
  47. package/types/lang/de/index.d.ts +1 -1
  48. package/types/lang/en/dialects.d.ts +1 -1
  49. package/types/lang/en/index.d.ts +1 -1
  50. package/types/lang/es/index.d.ts +1 -1
  51. package/types/lang/fi/dialects.d.ts +1 -1
  52. package/types/lang/fi/index.d.ts +1 -1
  53. package/types/lang/zh/index.d.ts +1 -1
  54. package/types/types.d.ts +5 -5
package/dist/lang/en.cjs CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
- // src/core/util.ts
27
+ // src/core/cadence.ts
28
28
  function arithmeticStep(values) {
29
29
  if (values.length < 5) {
30
30
  return null;
@@ -40,31 +40,11 @@ function arithmeticStep(values) {
40
40
  }
41
41
  return { start: values[0], interval, last: values[values.length - 1] };
42
42
  }
43
- function weekdayDisplayKey(value) {
44
- return value === 0 ? 7 : value;
45
- }
46
- function orderWeekdaysForDisplay(segments) {
47
- const flattened = segments.flatMap(function flat(segment) {
48
- return segment.kind === "step" ? segment.fires.map(function single(value) {
49
- return { kind: "single", value: "" + value };
50
- }) : [segment];
51
- });
52
- function key(segment) {
53
- return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
54
- }
55
- return flattened.map(function index(segment, position) {
56
- return [segment, position];
57
- }).sort(function byDisplayKey(a, b) {
58
- return key(a[0]) - key(b[0]) || a[1] - b[1];
59
- }).map(function unwrap(pair) {
60
- return pair[0];
61
- });
62
- }
63
- function segmentsOf(ir, field) {
64
- return ir.analyses.segments[field] ?? [];
43
+ function segmentsOf(schedule, field) {
44
+ return schedule.analyses.segments[field] ?? [];
65
45
  }
66
- function stepSegment(ir, field) {
67
- return segmentsOf(ir, field)[0];
46
+ function stepSegment(schedule, field) {
47
+ return segmentsOf(schedule, field)[0];
68
48
  }
69
49
  function singleValues(segments) {
70
50
  const values = [];
@@ -79,6 +59,17 @@ function singleValues(segments) {
79
59
  function offsetCleanStride(stride) {
80
60
  return stride.start < stride.interval && 24 % stride.interval === 0;
81
61
  }
62
+ function renderStride(spec, parts) {
63
+ const { start, interval, cycle } = spec;
64
+ const tiles = cycle % interval === 0;
65
+ if (start === 0 && tiles) {
66
+ return parts.bare();
67
+ }
68
+ if (start < interval && tiles) {
69
+ return parts.offset();
70
+ }
71
+ return parts.bounded();
72
+ }
82
73
  function hourListStride(values) {
83
74
  if (values.length < 2) {
84
75
  return null;
@@ -98,6 +89,28 @@ function hourListStride(values) {
98
89
  return { interval, last: values[values.length - 1], start: values[0] };
99
90
  }
100
91
 
92
+ // src/core/weekday.ts
93
+ function weekdayDisplayKey(value) {
94
+ return value === 0 ? 7 : value;
95
+ }
96
+ function orderWeekdaysForDisplay(segments) {
97
+ const flattened = segments.flatMap(function flat(segment) {
98
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
99
+ return { kind: "single", value: "" + value };
100
+ }) : [segment];
101
+ });
102
+ function key(segment) {
103
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
104
+ }
105
+ return flattened.map(function index(segment, position) {
106
+ return [segment, position];
107
+ }).sort(function byDisplayKey(a, b) {
108
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
109
+ }).map(function unwrap(pair) {
110
+ return pair[0];
111
+ });
112
+ }
113
+
101
114
  // src/core/shapes.ts
102
115
  function isOpenStep(field) {
103
116
  return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
@@ -235,72 +248,134 @@ function normalizeOptions(options) {
235
248
  years: !!options.years
236
249
  };
237
250
  }
238
- function describe(ir, opts) {
239
- const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
240
- const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
241
- return applyYear(lead + body, ir, opts);
251
+ function describe(schedule, opts) {
252
+ const dense = denseCadence(schedule, opts);
253
+ if (dense !== null) {
254
+ return applyYear(dense, schedule, opts);
255
+ }
256
+ const body = confinement(schedule, opts) ?? render(schedule, schedule.plan, opts);
257
+ const lead = isDayUnion(schedule, opts) ? dayUnionMonthLead(schedule, opts) : "";
258
+ return applyYear(lead + body, schedule, opts);
242
259
  }
243
- function render(ir, plan, opts) {
260
+ function render(schedule, plan, opts) {
244
261
  const renderer = renderers[plan.kind];
245
- return renderer(ir, plan, opts);
262
+ return renderer(schedule, plan, opts);
246
263
  }
247
- function renderEverySecond(ir, plan, opts) {
248
- return "every second" + trailingQualifier(ir, opts);
264
+ function isCadenceShape(shape) {
265
+ return shape === "step" || shape === "range" || shape === "list";
249
266
  }
250
- function renderStandaloneSeconds(ir, plan, opts) {
251
- return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
267
+ function isDenseCadence(schedule, opts) {
268
+ if (!opts.style.untilWindow || opts.short || schedule.plan.kind !== "composeSeconds" || schedule.plan.rest.kind === "clockTimes" || isDayUnion(schedule, opts)) {
269
+ return false;
270
+ }
271
+ const { shapes } = schedule;
272
+ return isCadenceShape(shapes.second) && isCadenceShape(shapes.minute) && isCadenceShape(shapes.hour);
252
273
  }
253
- function renderSecondPastMinute(ir, plan, opts) {
254
- const secondField = ir.pattern.second;
255
- return getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute, every minute" + trailingQualifier(ir, opts);
274
+ function denseHourFragment(schedule, opts) {
275
+ const stride = hourStride(schedule);
276
+ if (stride) {
277
+ return hourStrideCadence(stride, opts);
278
+ }
279
+ if (schedule.shapes.hour === "range") {
280
+ const segment = segmentsOf(schedule, "hour").find(function range(part) {
281
+ return part.kind === "range";
282
+ });
283
+ return rangeWindow({
284
+ continuous: false,
285
+ from: +segment.bounds[0],
286
+ throughMinute: 0,
287
+ to: +segment.bounds[1]
288
+ }, opts);
289
+ }
290
+ return "during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours";
256
291
  }
257
- function renderSecondsWithinMinute(ir, plan, opts) {
258
- const minuteField = ir.pattern.minute;
292
+ function denseMinuteFragment(schedule, opts) {
293
+ if (schedule.shapes.minute === "step") {
294
+ return stepCycle60(stepSegment(schedule, "minute"), "minute", "hour", opts);
295
+ }
296
+ if (schedule.shapes.minute === "range") {
297
+ return minuteRangeLead(schedule.pattern.minute, opts);
298
+ }
299
+ return strideFromSegments(
300
+ segmentsOf(schedule, "minute"),
301
+ "minute",
302
+ "hour",
303
+ opts
304
+ ) ?? listPastThe(
305
+ segmentWords(segmentsOf(schedule, "minute"), opts),
306
+ "minute",
307
+ "hour",
308
+ opts
309
+ );
310
+ }
311
+ function denseCadence(schedule, opts) {
312
+ if (!isDenseCadence(schedule, opts)) {
313
+ return null;
314
+ }
315
+ const hour = denseHourFragment(schedule, opts);
316
+ const minute = denseMinuteFragment(schedule, opts);
317
+ const second = secondsClause(schedule, "minute", opts);
318
+ const nested = hour + ", " + minute + ", and within each of those minutes, " + second;
319
+ const anchor = trailingQualifier(schedule, opts).trim();
320
+ return anchor ? anchor + ", " + nested : nested;
321
+ }
322
+ function renderEverySecond(schedule, plan, opts) {
323
+ return "every second" + trailingQualifier(schedule, opts);
324
+ }
325
+ function renderStandaloneSeconds(schedule, plan, opts) {
326
+ return secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
327
+ }
328
+ function renderSecondPastMinute(schedule, plan, opts) {
329
+ const secondField = schedule.pattern.second;
330
+ return getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute, every minute" + trailingQualifier(schedule, opts);
331
+ }
332
+ function renderSecondsWithinMinute(schedule, plan, opts) {
333
+ const minuteField = schedule.pattern.minute;
259
334
  const minuteWord = getNumber(minuteField, opts);
260
335
  const minuteUnit = pluralize(minuteField, "minute");
261
336
  if (plan.singleSecond) {
262
- const secondField = ir.pattern.second;
263
- return minuteWord + " " + minuteUnit + " and " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the hour, every hour" + trailingQualifier(ir, opts);
337
+ const secondField = schedule.pattern.second;
338
+ return minuteWord + " " + minuteUnit + " and " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the hour, every hour" + trailingQualifier(schedule, opts);
264
339
  }
265
- return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
340
+ return secondsLeadClause(schedule, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(schedule, opts);
266
341
  }
267
- function composeHourCadence(ir, plan, opts) {
342
+ function composeHourCadence(schedule, plan, opts) {
268
343
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
269
- if (!clockRest || ir.shapes.minute !== "single") {
344
+ if (!clockRest || schedule.shapes.minute !== "single") {
270
345
  return null;
271
346
  }
272
- const minute = +ir.pattern.minute;
273
- return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
347
+ const minute = +schedule.pattern.minute;
348
+ return hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
274
349
  }
275
- function clockTimesConfinement(ir, rest, opts) {
276
- if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
277
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
350
+ function clockTimesConfinement(schedule, rest, opts) {
351
+ if (+rest.times[0].minute === 0 && schedule.shapes.minute === "single") {
352
+ return secondsLeadClause(schedule, opts) + " for one minute at " + durationHours(schedule, rest, opts);
278
353
  }
279
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
354
+ return secondsLeadClause(schedule, opts) + " of " + clockTimesOf(schedule, rest, opts);
280
355
  }
281
- function renderComposeSeconds(ir, plan, opts) {
282
- const cadence = composeHourCadence(ir, plan, opts);
356
+ function renderComposeSeconds(schedule, plan, opts) {
357
+ const cadence = composeHourCadence(schedule, plan, opts);
283
358
  if (cadence !== null) {
284
359
  return cadence;
285
360
  }
286
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
287
- return clockTimesConfinement(ir, plan.rest, opts);
361
+ if (plan.rest.kind === "clockTimes" && (schedule.shapes.second === "wildcard" || schedule.shapes.second === "step")) {
362
+ return clockTimesConfinement(schedule, plan.rest, opts);
288
363
  }
289
- if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
290
- return "every second of every other minute" + trailingQualifier(ir, opts);
364
+ if (schedule.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && schedule.pattern.minute === "*/2") {
365
+ return "every second of every other minute" + trailingQualifier(schedule, opts);
291
366
  }
292
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
293
- const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
294
- return lead + render(ir, plan.rest, opts);
367
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && schedule.analyses.clockSecond;
368
+ const lead = restOwnsLead ? "" : secondsLeadClause(schedule, opts) + ", ";
369
+ return lead + render(schedule, plan.rest, opts);
295
370
  }
296
- function durationHours(ir, plan, opts) {
371
+ function durationHours(schedule, plan, opts) {
297
372
  const hours = plan.times.map(function clock(time) {
298
373
  return getTime({ hour: time.hour, minute: 0 }, opts);
299
374
  });
300
- const trail = dayQualifier(ir, leadingWords, opts);
375
+ const trail = dayQualifier(schedule, leadingWords, opts);
301
376
  return joinList(hours, opts) + (trail && ", " + trail);
302
377
  }
303
- function clockTimesOf(ir, plan, opts) {
378
+ function clockTimesOf(schedule, plan, opts) {
304
379
  const times = plan.times.map(function clock(time) {
305
380
  return getTime({
306
381
  hour: time.hour,
@@ -309,21 +384,21 @@ function clockTimesOf(ir, plan, opts) {
309
384
  explicit: true
310
385
  }, opts);
311
386
  });
312
- const trail = dayQualifier(ir, leadingWords, opts);
387
+ const trail = dayQualifier(schedule, leadingWords, opts);
313
388
  return joinList(times, opts) + (trail && ", " + trail);
314
389
  }
315
- function secondsLeadClause(ir, opts) {
316
- return secondsClause(ir, "minute", opts);
390
+ function secondsLeadClause(schedule, opts) {
391
+ return secondsClause(schedule, "minute", opts);
317
392
  }
318
- function secondsClause(ir, anchor, opts) {
319
- const secondField = ir.pattern.second;
320
- const shape = ir.shapes.second;
393
+ function secondsClause(schedule, anchor, opts) {
394
+ const secondField = schedule.pattern.second;
395
+ const shape = schedule.shapes.second;
321
396
  if (secondField === "*") {
322
397
  return "every second";
323
398
  }
324
399
  if (shape === "step") {
325
400
  return stepCycle60(
326
- stepSegment(ir, "second"),
401
+ stepSegment(schedule, "second"),
327
402
  "second",
328
403
  anchor,
329
404
  opts
@@ -338,44 +413,44 @@ function secondsClause(ir, anchor, opts) {
338
413
  return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
339
414
  }
340
415
  return strideFromSegments(
341
- segmentsOf(ir, "second"),
416
+ segmentsOf(schedule, "second"),
342
417
  "second",
343
418
  anchor,
344
419
  opts
345
420
  ) ?? listPastThe(
346
- segmentWords(segmentsOf(ir, "second"), opts),
421
+ segmentWords(segmentsOf(schedule, "second"), opts),
347
422
  "second",
348
423
  anchor,
349
424
  opts
350
425
  );
351
426
  }
352
- function renderEveryMinute(ir, plan, opts) {
353
- return "every minute" + trailingQualifier(ir, opts);
427
+ function renderEveryMinute(schedule, plan, opts) {
428
+ return "every minute" + trailingQualifier(schedule, opts);
354
429
  }
355
- function renderSingleMinute(ir, plan, opts) {
356
- const minuteField = ir.pattern.minute;
357
- return getNumber(minuteField, opts) + " " + pluralize(minuteField, "minute") + " past the hour, every hour" + trailingQualifier(ir, opts);
430
+ function renderSingleMinute(schedule, plan, opts) {
431
+ const minuteField = schedule.pattern.minute;
432
+ return getNumber(minuteField, opts) + " " + pluralize(minuteField, "minute") + " past the hour, every hour" + trailingQualifier(schedule, opts);
358
433
  }
359
- function renderRangeOfMinutes(ir, plan, opts) {
360
- return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
434
+ function renderRangeOfMinutes(schedule, plan, opts) {
435
+ return minuteRangeLead(schedule.pattern.minute, opts) + trailingQualifier(schedule, opts);
361
436
  }
362
- function renderMultipleMinutes(ir, plan, opts) {
363
- const stride = strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts);
437
+ function renderMultipleMinutes(schedule, plan, opts) {
438
+ const stride = strideFromSegments(segmentsOf(schedule, "minute"), "minute", "hour", opts);
364
439
  return (stride ?? listPastThe(segmentWords(
365
- segmentsOf(ir, "minute"),
440
+ segmentsOf(schedule, "minute"),
366
441
  opts
367
- ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
442
+ ), "minute", "hour", opts)) + trailingQualifier(schedule, opts);
368
443
  }
369
- function renderMinuteFrequency(ir, plan, opts) {
444
+ function renderMinuteFrequency(schedule, plan, opts) {
370
445
  let phrase = stepCycle60(
371
- stepSegment(ir, "minute"),
446
+ stepSegment(schedule, "minute"),
372
447
  "minute",
373
448
  "hour",
374
449
  opts
375
450
  );
376
451
  if (plan.hours.kind === "during") {
377
- const cadence = unevenHourCadence(ir, opts);
378
- phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
452
+ const cadence = unevenHourCadence(schedule, opts);
453
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(schedule, plan.hours.times, false, opts) + " hours";
379
454
  } else if (plan.hours.kind === "window") {
380
455
  phrase += " " + rangeWindow({
381
456
  continuous: false,
@@ -384,45 +459,50 @@ function renderMinuteFrequency(ir, plan, opts) {
384
459
  to: plan.hours.to
385
460
  }, opts);
386
461
  } else if (plan.hours.kind === "step") {
387
- phrase += " " + everyNthHour(stepSegment(ir, "hour"), opts);
462
+ phrase += " " + everyNthHour(stepSegment(schedule, "hour"), opts);
388
463
  }
389
- return phrase + trailingQualifier(ir, opts);
464
+ return phrase + trailingQualifier(schedule, opts);
390
465
  }
391
- function renderMinuteSpanInHour(ir, plan, opts) {
392
- if (ir.pattern.minute === "*") {
393
- return "every minute of the " + getTime({ hour: plan.hour, minute: 0 }, opts) + " hour" + trailingQualifier(ir, opts);
466
+ function renderMinuteSpanInHour(schedule, plan, opts) {
467
+ if (schedule.pattern.minute === "*") {
468
+ return "every minute of the " + getTime({ hour: plan.hour, minute: 0 }, opts) + " hour" + trailingQualifier(schedule, opts);
394
469
  }
395
- 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);
470
+ 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(schedule, opts);
396
471
  }
397
- function renderMinutesAcrossHours(ir, plan, opts) {
398
- const cadence = unevenHourCadence(ir, opts);
472
+ function renderMinutesAcrossHours(schedule, plan, opts) {
473
+ const cadence = unevenHourCadence(schedule, opts);
399
474
  if (plan.form === "wildcard") {
400
475
  if (cadence !== null) {
401
- return "every minute, " + cadence + trailingQualifier(ir, opts);
476
+ return "every minute, " + cadence + trailingQualifier(schedule, opts);
402
477
  }
403
- return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
478
+ return "every minute during the " + hourTimesFromPlan(schedule, plan.times, false, opts) + " hours" + trailingQualifier(schedule, opts);
404
479
  }
405
480
  if (plan.form === "range") {
406
- const lead2 = minuteRangeLead(ir.pattern.minute, opts);
481
+ const lead2 = minuteRangeLead(schedule.pattern.minute, opts);
407
482
  if (cadence !== null) {
408
- return lead2 + ", " + cadence + trailingQualifier(ir, opts);
483
+ return lead2 + ", " + cadence + trailingQualifier(schedule, opts);
409
484
  }
410
485
  if (singleHourFire(plan.times)) {
411
- return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
486
+ return lead2 + ", at " + hourTimesFromPlan(schedule, plan.times, true, opts) + trailingQualifier(schedule, opts);
412
487
  }
413
- return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
488
+ return lead2 + " during the " + hourTimesFromPlan(schedule, plan.times, false, opts) + " hours" + trailingQualifier(schedule, opts);
414
489
  }
415
- const lead = strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
416
- segmentWords(segmentsOf(ir, "minute"), opts),
490
+ const lead = strideFromSegments(
491
+ segmentsOf(schedule, "minute"),
492
+ "minute",
493
+ "hour",
494
+ opts
495
+ ) ?? listPastThe(
496
+ segmentWords(segmentsOf(schedule, "minute"), opts),
417
497
  "minute",
418
498
  "hour",
419
499
  opts
420
500
  );
421
501
  if (cadence !== null) {
422
- return lead + ", " + cadence + trailingQualifier(ir, opts);
502
+ return lead + ", " + cadence + trailingQualifier(schedule, opts);
423
503
  }
424
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
425
- return lead + ", at " + times + trailingQualifier(ir, opts);
504
+ const times = hourTimesFromPlan(schedule, plan.times, true, opts);
505
+ return lead + ", at " + times + trailingQualifier(schedule, opts);
426
506
  }
427
507
  var stepOrdinals = {
428
508
  2: "other",
@@ -437,60 +517,65 @@ function everyNthHour(segment, opts) {
437
517
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
438
518
  return start === 0 ? base : base + " starting at " + getTime({ hour: start, minute: 0 }, opts);
439
519
  }
440
- function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
441
- const segment = stepSegment(ir, "hour");
520
+ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
521
+ const segment = stepSegment(schedule, "hour");
442
522
  if (plan.form === "wildcard") {
443
- return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
523
+ return "every minute " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
444
524
  }
445
- const lead = plan.form === "list" ? strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
446
- segmentWords(segmentsOf(ir, "minute"), opts),
525
+ const lead = plan.form === "list" ? strideFromSegments(
526
+ segmentsOf(schedule, "minute"),
447
527
  "minute",
448
528
  "hour",
449
529
  opts
450
- ) : minuteRangeLead(ir.pattern.minute, opts);
451
- const cadence = unevenHourCadence(ir, opts);
452
- return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
530
+ ) ?? listPastThe(
531
+ segmentWords(segmentsOf(schedule, "minute"), opts),
532
+ "minute",
533
+ "hour",
534
+ opts
535
+ ) : minuteRangeLead(schedule.pattern.minute, opts);
536
+ const cadence = unevenHourCadence(schedule, opts);
537
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(schedule, opts);
453
538
  }
454
539
  function minuteRangeLead(minuteField, opts) {
455
540
  const bounds = minuteField.split("-");
456
541
  const num = seriesNumber();
457
542
  return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
458
543
  }
459
- function renderEveryHour(ir, plan, opts) {
460
- return "every hour" + trailingQualifier(ir, opts);
544
+ function renderEveryHour(schedule, plan, opts) {
545
+ return "every hour" + trailingQualifier(schedule, opts);
461
546
  }
462
- function renderHourRange(ir, plan, opts) {
547
+ function renderHourRange(schedule, plan, opts) {
463
548
  const window = hourWindow(boundedWindow(plan), opts);
464
549
  if (plan.minuteForm === "wildcard") {
465
- return "every minute " + window + trailingQualifier(ir, opts);
550
+ return "every minute " + window + trailingQualifier(schedule, opts);
466
551
  }
467
552
  if (plan.minuteForm === "range") {
468
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + window + trailingQualifier(ir, opts);
553
+ return minuteRangeLead(schedule.pattern.minute, opts) + ", " + window + trailingQualifier(schedule, opts);
469
554
  }
470
- return rangeMinuteLead(ir, opts) + " " + window + trailingQualifier(ir, opts);
555
+ return rangeMinuteLead(schedule, opts) + " " + window + trailingQualifier(schedule, opts);
471
556
  }
472
- function rangeMinuteLead(ir, opts) {
473
- if (ir.pattern.minute === "0") {
557
+ function rangeMinuteLead(schedule, opts) {
558
+ if (schedule.pattern.minute === "0") {
474
559
  return "every hour";
475
560
  }
476
561
  return strideFromSegments(
477
- segmentsOf(ir, "minute"),
562
+ segmentsOf(schedule, "minute"),
478
563
  "minute",
479
564
  "hour",
480
565
  opts
481
566
  ) ?? listPastThe(
482
- segmentWords(segmentsOf(ir, "minute"), opts),
567
+ segmentWords(segmentsOf(schedule, "minute"), opts),
483
568
  "minute",
484
569
  "hour",
485
570
  opts
486
571
  );
487
572
  }
488
- function renderHourStep(ir, plan, opts) {
489
- const cadence = unevenHourCadence(ir, opts);
573
+ function renderHourStep(schedule, plan, opts) {
574
+ const cadence = unevenHourCadence(schedule, opts);
490
575
  if (cadence !== null) {
491
- return cadence + trailingQualifier(ir, opts);
576
+ return cadence + trailingQualifier(schedule, opts);
492
577
  }
493
- return stepHours(stepSegment(ir, "hour"), opts) + trailingQualifier(ir, opts);
578
+ return stepHours(stepSegment(schedule, "hour"), opts) + trailingQualifier(schedule, opts);
494
579
  }
495
580
  function boundedWindow(plan) {
496
581
  const continuous = plan.minuteForm === "wildcard";
@@ -513,10 +598,10 @@ function hourWindow(window, opts) {
513
598
  to: window.to
514
599
  }, opts);
515
600
  }
516
- function renderClockTimes(ir, plan, opts) {
517
- if (ir.shapes.minute === "single") {
518
- const minute = +ir.pattern.minute;
519
- const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
601
+ function renderClockTimes(schedule, plan, opts) {
602
+ if (schedule.shapes.minute === "single") {
603
+ const minute = +schedule.pattern.minute;
604
+ const cadence = hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
520
605
  if (cadence !== null) {
521
606
  return cadence;
522
607
  }
@@ -530,47 +615,52 @@ function renderClockTimes(ir, plan, opts) {
530
615
  plain
531
616
  }, opts);
532
617
  });
533
- return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
618
+ return interpretDayQualifier(schedule, opts) + "at " + joinList(times, opts) + dayUnionTrail(schedule, opts);
534
619
  }
535
- function dayUnionTrail(ir, opts) {
536
- return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
620
+ function dayUnionTrail(schedule, opts) {
621
+ return isDayUnion(schedule, opts) ? dayUnionCondition(schedule, opts) : "";
537
622
  }
538
- function renderCompactClockTimes(ir, plan, opts) {
623
+ function renderCompactClockTimes(schedule, plan, opts) {
539
624
  if (plan.fold) {
540
- const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
625
+ const cadence2 = hourCadence(schedule, +plan.minute, opts) ?? hourRangeCadence(schedule, +plan.minute, opts);
541
626
  if (cadence2 !== null) {
542
627
  return cadence2;
543
628
  }
544
- const hasRange = segmentsOf(ir, "hour").some(function range(segment) {
629
+ const hasRange = segmentsOf(schedule, "hour").some(function range(segment) {
545
630
  return segment.kind === "range";
546
631
  });
547
- if (hasRange && !ir.analyses.clockSecond) {
548
- return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
632
+ if (hasRange && !schedule.analyses.clockSecond) {
633
+ return foldedHourWindows(schedule, plan, opts) + trailingQualifier(schedule, opts);
549
634
  }
550
- const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
551
- return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
635
+ const fold = { minute: plan.minute, second: schedule.analyses.clockSecond };
636
+ return interpretDayQualifier(schedule, opts) + "at " + hourSegmentTimes(schedule, fold, true, opts) + dayUnionTrail(schedule, opts);
552
637
  }
553
638
  const minuteLead = (
554
639
  // The non-fold branch is a minute list, which has segments. An
555
640
  // offset/uneven step enumerated to that list reads as a stride.
556
- strideFromSegments(segmentsOf(ir, "minute"), "minute", "hour", opts) ?? listPastThe(
557
- segmentWords(segmentsOf(ir, "minute"), opts),
641
+ strideFromSegments(
642
+ segmentsOf(schedule, "minute"),
643
+ "minute",
644
+ "hour",
645
+ opts
646
+ ) ?? listPastThe(
647
+ segmentWords(segmentsOf(schedule, "minute"), opts),
558
648
  "minute",
559
649
  "hour",
560
650
  opts
561
651
  )
562
652
  );
563
- const cadence = unevenHourCadence(ir, opts);
564
- const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
565
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
653
+ const cadence = unevenHourCadence(schedule, opts);
654
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(schedule, opts) : minuteLead + ", at " + hourSegmentTimes(schedule, { minute: 0, second: null }, true, opts) + trailingQualifier(schedule, opts);
655
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
566
656
  }
567
- function foldedHourWindows(ir, plan, opts) {
657
+ function foldedHourWindows(schedule, plan, opts) {
568
658
  const minute = plan.minute;
569
659
  const windows = [];
570
- const times = collectHourOutliers(ir).map(function time(hour) {
660
+ const times = collectHourOutliers(schedule).map(function time(hour) {
571
661
  return getTime({ hour, minute }, opts);
572
662
  });
573
- segmentsOf(ir, "hour").forEach(function classify(segment) {
663
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
574
664
  if (segment.kind === "range") {
575
665
  windows.push(rangeWindow({
576
666
  continuous: false,
@@ -580,12 +670,12 @@ function foldedHourWindows(ir, plan, opts) {
580
670
  }, opts));
581
671
  }
582
672
  });
583
- const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
673
+ const phrase = rangeMinuteLead(schedule, opts) + " " + joinList(windows, opts);
584
674
  return phrase + outlierTail(times, opts);
585
675
  }
586
- function collectHourOutliers(ir) {
676
+ function collectHourOutliers(schedule) {
587
677
  const hours = [];
588
- segmentsOf(ir, "hour").forEach(function classify(segment) {
678
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
589
679
  if (segment.kind === "step") {
590
680
  hours.push(...segment.fires);
591
681
  } else if (segment.kind !== "range") {
@@ -603,16 +693,16 @@ function outlierTail(times, opts) {
603
693
  function isCadenceField(token) {
604
694
  return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
605
695
  }
606
- function leadingCadence(ir, opts) {
607
- const { second, minute } = ir.pattern;
696
+ function leadingCadence(schedule, opts) {
697
+ const { second, minute } = schedule.pattern;
608
698
  if (isCadenceField(second)) {
609
- return { secondLead: true, text: secondsClause(ir, "minute", opts) };
699
+ return { secondLead: true, text: secondsClause(schedule, "minute", opts) };
610
700
  }
611
701
  if (second === "0" && isCadenceField(minute)) {
612
702
  const text = minute === "*" ? "every minute" : (
613
703
  // A clean minute step's first segment is a step segment.
614
704
  stepCycle60(
615
- stepSegment(ir, "minute"),
705
+ stepSegment(schedule, "minute"),
616
706
  "minute",
617
707
  "hour",
618
708
  opts
@@ -622,19 +712,19 @@ function leadingCadence(ir, opts) {
622
712
  }
623
713
  return null;
624
714
  }
625
- function minuteConfinement(ir, opts) {
626
- const minute = ir.pattern.minute;
715
+ function minuteConfinement(schedule, opts) {
716
+ const minute = schedule.pattern.minute;
627
717
  if (minute === "*") {
628
718
  return "";
629
719
  }
630
720
  if (isCadenceField(minute)) {
631
721
  return " of every other minute";
632
722
  }
633
- const segments = segmentsOf(ir, "minute");
634
- if (ir.shapes.minute === "single") {
723
+ const segments = segmentsOf(schedule, "minute");
724
+ if (schedule.shapes.minute === "single") {
635
725
  return " during minute :" + pad(minute);
636
726
  }
637
- if (ir.shapes.minute === "range") {
727
+ if (schedule.shapes.minute === "range") {
638
728
  const bounds = minute.split("-");
639
729
  return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
640
730
  }
@@ -643,64 +733,61 @@ function minuteConfinement(ir, opts) {
643
733
  });
644
734
  return " during minutes " + joinList(values, opts);
645
735
  }
646
- function hourConfinement(ir, opts) {
647
- const hour = ir.pattern.hour;
736
+ function hourConfinement(schedule, opts) {
737
+ const hour = schedule.pattern.hour;
648
738
  if (hour === "*") {
649
- const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
739
+ const minutePinned = schedule.pattern.minute !== "*" && !isCadenceField(schedule.pattern.minute);
650
740
  return minutePinned ? " of every hour" : "";
651
741
  }
652
742
  if (isCadenceField(hour)) {
653
743
  return hour === "*/2" ? " of every other hour" : "";
654
744
  }
655
- if (ir.shapes.hour === "single") {
745
+ if (schedule.shapes.hour === "single") {
656
746
  const h = +hour;
657
- if (ir.shapes.minute === "step") {
747
+ if (schedule.shapes.minute === "step") {
658
748
  return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
659
749
  }
660
- if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
750
+ if (schedule.pattern.minute !== "*" && !isCadenceField(schedule.pattern.minute)) {
661
751
  return " at " + getTime({ hour: h, minute: 0 }, opts);
662
752
  }
663
753
  return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
664
754
  }
665
- if (ir.shapes.hour === "range") {
755
+ if (schedule.shapes.hour === "range") {
666
756
  const bounds = hour.split("-");
667
757
  return " " + rangeWindow({
668
- continuous: ir.pattern.minute === "*",
758
+ continuous: schedule.pattern.minute === "*",
669
759
  from: +bounds[0],
670
760
  throughMinute: 0,
671
761
  to: +bounds[1]
672
762
  }, opts);
673
763
  }
674
- return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
675
- }
676
- function isContiguousHourRange(ir) {
677
- return ir.shapes.hour === "range";
764
+ return " during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours";
678
765
  }
679
- function confinableHour(ir) {
680
- if (ir.shapes.hour !== "step") {
766
+ function confinableHour(schedule) {
767
+ if (schedule.shapes.hour !== "step") {
681
768
  return true;
682
769
  }
683
- const segment = stepSegment(ir, "hour");
684
- return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
770
+ const segment = stepSegment(schedule, "hour");
771
+ return schedule.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
685
772
  }
686
- function isMinuteStride(ir) {
687
- if (ir.shapes.minute !== "list") {
773
+ function isMinuteStride(schedule) {
774
+ if (schedule.shapes.minute !== "list") {
688
775
  return false;
689
776
  }
690
- const values = singleValues(segmentsOf(ir, "minute"));
777
+ const values = singleValues(segmentsOf(schedule, "minute"));
691
778
  return values !== null && arithmeticStep(values) !== null;
692
779
  }
693
- function confinementEligible(ir, lead) {
694
- const { minute, hour } = ir.pattern;
780
+ function confinementEligible(schedule, lead) {
781
+ const { minute, hour } = schedule.pattern;
695
782
  const minuteStep = isCadenceField(minute) && minute !== "*";
696
- if (!confinableHour(ir)) {
783
+ if (!confinableHour(schedule)) {
697
784
  return false;
698
785
  }
699
786
  if (lead.secondLead) {
700
787
  if (minuteStep) {
701
- return minute === "*/2" && !isContiguousHourRange(ir);
788
+ return minute === "*/2" && schedule.shapes.hour !== "range";
702
789
  }
703
- if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
790
+ if (isMinuteStride(schedule) || schedule.shapes.minute === "list" && schedule.shapes.hour === "list") {
704
791
  return false;
705
792
  }
706
793
  return true;
@@ -708,21 +795,21 @@ function confinementEligible(ir, lead) {
708
795
  if (hour === "*/2") {
709
796
  return true;
710
797
  }
711
- return ir.shapes.hour === "single" && minute === "*/2";
798
+ return schedule.shapes.hour === "single" && minute === "*/2";
712
799
  }
713
- function confinement(ir, opts) {
800
+ function confinement(schedule, opts) {
714
801
  if (!opts.style.untilWindow || opts.short) {
715
802
  return null;
716
803
  }
717
- if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
804
+ if (schedule.pattern.minute === "*" && schedule.pattern.hour === "*") {
718
805
  return null;
719
806
  }
720
- const lead = leadingCadence(ir, opts);
721
- if (!lead || !confinementEligible(ir, lead)) {
807
+ const lead = leadingCadence(schedule, opts);
808
+ if (!lead || !confinementEligible(schedule, lead)) {
722
809
  return null;
723
810
  }
724
- const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
725
- return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
811
+ const minutePart = lead.secondLead ? minuteConfinement(schedule, opts) : "";
812
+ return lead.text + minutePart + hourConfinement(schedule, opts) + trailingQualifier(schedule, opts);
726
813
  }
727
814
  var renderers = {
728
815
  clockTimes: renderClockTimes,
@@ -744,23 +831,25 @@ var renderers = {
744
831
  singleMinute: renderSingleMinute,
745
832
  standaloneSeconds: renderStandaloneSeconds
746
833
  };
747
- function renderStride(stride, opts) {
834
+ function renderStride2(stride, opts) {
748
835
  const { interval, start, last, cycle, unit, anchor } = stride;
749
836
  const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
750
- const tiles = cycle % interval === 0;
751
- if (start === 0 && tiles) {
752
- return cadence;
753
- }
754
- if (start < interval && tiles) {
755
- return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
756
- }
757
- const num = seriesNumber();
758
- return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
837
+ return renderStride({ start, interval, cycle }, {
838
+ bare: () => cadence,
839
+ // A clean wrap from a non-zero offset: name the start, no endpoint.
840
+ offset: () => cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor,
841
+ // A bounded, non-wrapping set: pin both endpoints. Each bound is a value,
842
+ // so it reads as a digit, matching the range idiom ("from 0 through 30").
843
+ bounded: () => {
844
+ const num = seriesNumber();
845
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
846
+ }
847
+ });
759
848
  }
760
849
  function strideFromSegments(segments, unit, anchor, opts) {
761
850
  const values = singleValues(segments);
762
851
  const step = values && arithmeticStep(values);
763
- return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
852
+ return step ? renderStride2({ ...step, cycle: 60, unit, anchor }, opts) : null;
764
853
  }
765
854
  function stepCycle60(segment, unit, anchor, opts) {
766
855
  if (segment.startToken.indexOf("-") !== -1) {
@@ -770,7 +859,7 @@ function stepCycle60(segment, unit, anchor, opts) {
770
859
  if (start !== 0 && segment.fires.length <= 3) {
771
860
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
772
861
  }
773
- return renderStride({
862
+ return renderStride2({
774
863
  interval: segment.interval,
775
864
  start,
776
865
  last: segment.fires[segment.fires.length - 1],
@@ -796,24 +885,21 @@ function stepHours(segment, opts) {
796
885
  function hourStrideCadence(stride, opts) {
797
886
  const { start, interval, last } = stride;
798
887
  const cadence = "every " + getNumber(interval, opts) + " hours";
799
- const tiles = 24 % interval === 0;
800
- if (start === 0 && tiles) {
801
- return cadence;
802
- }
803
- if (start < interval && tiles) {
804
- return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
805
- }
806
- return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
888
+ return renderStride({ start, interval, cycle: 24 }, {
889
+ bare: () => cadence,
890
+ offset: () => cadence + " from " + getTime({ hour: start, minute: 0 }, opts),
891
+ bounded: () => cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts)
892
+ });
807
893
  }
808
- function unevenHourCadence(ir, opts) {
809
- const stride = hourStride(ir);
894
+ function unevenHourCadence(schedule, opts) {
895
+ const stride = hourStride(schedule);
810
896
  if (!stride || offsetCleanStride(stride)) {
811
897
  return null;
812
898
  }
813
899
  return hourStrideCadence(stride, opts);
814
900
  }
815
- function hourStride(ir) {
816
- const segments = segmentsOf(ir, "hour");
901
+ function hourStride(schedule) {
902
+ const segments = segmentsOf(schedule, "hour");
817
903
  if (segments.length === 1 && segments[0].kind === "step") {
818
904
  const segment = segments[0];
819
905
  if (segment.fires.length < 2) {
@@ -825,57 +911,57 @@ function hourStride(ir) {
825
911
  const values = singleValues(segments);
826
912
  return values && hourListStride(values);
827
913
  }
828
- function subMinuteSecond(ir) {
829
- return ir.pattern.second === "*" || ir.shapes.second === "step";
914
+ function subMinuteSecond(schedule) {
915
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
830
916
  }
831
- function hourCadenceLead(ir, minute, opts) {
917
+ function hourCadenceLead(schedule, minute, opts) {
832
918
  if (minute === 0) {
833
- if (subMinuteSecond(ir)) {
834
- return secondsClause(ir, "minute", opts) + " for one minute";
919
+ if (subMinuteSecond(schedule)) {
920
+ return secondsClause(schedule, "minute", opts) + " for one minute";
835
921
  }
836
- return secondsClause(ir, "hour", opts);
922
+ return secondsClause(schedule, "hour", opts);
837
923
  }
838
924
  const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
839
- if (ir.pattern.second === "0") {
925
+ if (schedule.pattern.second === "0") {
840
926
  return minutePhrase;
841
927
  }
842
- return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
928
+ return secondsClause(schedule, "minute", opts) + ", " + minutePhrase;
843
929
  }
844
- function hourCadence(ir, minute, opts) {
845
- const stride = hourStride(ir);
930
+ function hourCadence(schedule, minute, opts) {
931
+ const stride = hourStride(schedule);
846
932
  if (!stride) {
847
933
  return null;
848
934
  }
849
935
  const fires = (stride.last - stride.start) / stride.interval + 1;
850
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
936
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
851
937
  return null;
852
938
  }
853
- const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
939
+ const minuteZeroStride = minute === 0 && subMinuteSecond(schedule) && cleanStrideSegment(schedule);
854
940
  if (minuteZeroStride) {
855
- return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
941
+ return secondsClause(schedule, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(schedule, opts);
856
942
  }
857
- if (minute === 0 && ir.pattern.second === "0") {
858
- return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
943
+ if (minute === 0 && schedule.pattern.second === "0") {
944
+ return hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
859
945
  }
860
- return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
946
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
861
947
  }
862
- function cleanStrideSegment(ir) {
863
- const segments = segmentsOf(ir, "hour");
948
+ function cleanStrideSegment(schedule) {
949
+ const segments = segmentsOf(schedule, "hour");
864
950
  const segment = segments.length === 1 && segments[0];
865
951
  if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
866
952
  return null;
867
953
  }
868
954
  return segment;
869
955
  }
870
- function hasHourWindow(ir) {
871
- return segmentsOf(ir, "hour").some(function range(segment) {
956
+ function hasHourWindow(schedule) {
957
+ return segmentsOf(schedule, "hour").some(function range(segment) {
872
958
  return segment.kind === "range";
873
959
  });
874
960
  }
875
- function hourRangeWindowTail(ir, opts) {
961
+ function hourRangeWindowTail(schedule, opts) {
876
962
  const windows = [];
877
- const outlierHours = collectHourOutliers(ir);
878
- segmentsOf(ir, "hour").forEach(function classify(segment) {
963
+ const outlierHours = collectHourOutliers(schedule);
964
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
879
965
  if (segment.kind === "range") {
880
966
  windows.push(rangeWindow({
881
967
  continuous: false,
@@ -891,17 +977,17 @@ function hourRangeWindowTail(ir, opts) {
891
977
  });
892
978
  return phrase + outlierTail(times, opts);
893
979
  }
894
- function hourRangeCadence(ir, minute, opts) {
895
- if (minute !== 0 || !hasHourWindow(ir)) {
980
+ function hourRangeCadence(schedule, minute, opts) {
981
+ if (minute !== 0 || !hasHourWindow(schedule)) {
896
982
  return null;
897
983
  }
898
- if (ir.pattern.second === "0") {
984
+ if (schedule.pattern.second === "0") {
899
985
  return null;
900
986
  }
901
- if (subMinuteSecond(ir)) {
902
- return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
987
+ if (subMinuteSecond(schedule)) {
988
+ return secondsClause(schedule, "minute", opts) + " for one minute during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(schedule, opts);
903
989
  }
904
- return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
990
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourRangeWindowTail(schedule, opts) + trailingQualifier(schedule, opts);
905
991
  }
906
992
  function seriesNumber() {
907
993
  return function format(n) {
@@ -960,11 +1046,11 @@ function hourTimes(hours, opts) {
960
1046
  function singleHourFire(times) {
961
1047
  return times.kind === "fires" && times.fires.length === 1;
962
1048
  }
963
- function hourTimesFromPlan(ir, times, atContext, opts) {
1049
+ function hourTimesFromPlan(schedule, times, atContext, opts) {
964
1050
  if (times.kind === "fires") {
965
1051
  return hourTimes(times.fires, opts);
966
1052
  }
967
- return hourSegmentTimes(ir, { minute: 0, second: null }, atContext, opts);
1053
+ return hourSegmentTimes(schedule, { minute: 0, second: null }, atContext, opts);
968
1054
  }
969
1055
  function segmentHours(segment) {
970
1056
  if (segment.kind === "range") {
@@ -972,9 +1058,9 @@ function segmentHours(segment) {
972
1058
  }
973
1059
  return segment.kind === "step" ? segment.fires : [segment.value];
974
1060
  }
975
- function hourSegmentTimes(ir, fold, atContext, opts) {
1061
+ function hourSegmentTimes(schedule, fold, atContext, opts) {
976
1062
  const { minute, second } = fold;
977
- const segments = segmentsOf(ir, "hour");
1063
+ const segments = segmentsOf(schedule, "hour");
978
1064
  const plain = mixedTwelve(segments.flatMap(function entries(segment) {
979
1065
  return segmentHours(segment).map(function entry(hour) {
980
1066
  return { hour: +hour, minute, second };
@@ -1037,85 +1123,85 @@ var leadingWords = {
1037
1123
  stepDate: "",
1038
1124
  weekday: "every "
1039
1125
  };
1040
- function trailingQualifier(ir, opts) {
1041
- if (isDayUnion(ir, opts)) {
1042
- return dayUnionCondition(ir, opts);
1126
+ function trailingQualifier(schedule, opts) {
1127
+ if (isDayUnion(schedule, opts)) {
1128
+ return dayUnionCondition(schedule, opts);
1043
1129
  }
1044
- const phrase = dayQualifier(ir, trailingWords, opts);
1130
+ const phrase = dayQualifier(schedule, trailingWords, opts);
1045
1131
  return phrase && " " + phrase;
1046
1132
  }
1047
- function interpretDayQualifier(ir, opts) {
1048
- if (isDayUnion(ir, opts)) {
1133
+ function interpretDayQualifier(schedule, opts) {
1134
+ if (isDayUnion(schedule, opts)) {
1049
1135
  return "";
1050
1136
  }
1051
- return dayQualifier(ir, leadingWords, opts) + " ";
1137
+ return dayQualifier(schedule, leadingWords, opts) + " ";
1052
1138
  }
1053
- function dayQualifier(ir, words, opts) {
1054
- const pattern = ir.pattern;
1139
+ function dayQualifier(schedule, words, opts) {
1140
+ const pattern = schedule.pattern;
1055
1141
  if (pattern.date !== "*" && pattern.weekday !== "*") {
1056
- return dateOrWeekday(ir, opts);
1142
+ return dateOrWeekday(schedule, opts);
1057
1143
  }
1058
1144
  if (pattern.date !== "*") {
1059
- return datePhrase(ir, words, opts);
1145
+ return datePhrase(schedule, words, opts);
1060
1146
  }
1061
1147
  if (pattern.weekday !== "*") {
1062
1148
  const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
1063
1149
  if (quartzWeekday) {
1064
- return monthScopeForRecurrence(quartzWeekday, ir, opts);
1150
+ return monthScopeForRecurrence(quartzWeekday, schedule, opts);
1065
1151
  }
1066
- const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
1067
- return weekdays + monthScope(ir, opts);
1152
+ const weekdays = words.weekday + weekdayPhrase(schedule, words.recurringWeekday, opts);
1153
+ return weekdays + monthScope(schedule, opts);
1068
1154
  }
1069
1155
  if (pattern.month !== "*") {
1070
- return words.month + monthName(ir, opts);
1156
+ return words.month + monthName(schedule, opts);
1071
1157
  }
1072
1158
  return words.all;
1073
1159
  }
1074
- function datePhrase(ir, words, opts) {
1075
- const pattern = ir.pattern;
1160
+ function datePhrase(schedule, words, opts) {
1161
+ const pattern = schedule.pattern;
1076
1162
  const quartzDate = quartzDatePhrase(pattern.date, opts);
1077
1163
  if (quartzDate) {
1078
- return monthScopeForRecurrence(quartzDate, ir, opts);
1164
+ return monthScopeForRecurrence(quartzDate, schedule, opts);
1079
1165
  }
1080
1166
  if (isOpenStep(pattern.date)) {
1081
1167
  return monthScopeForRecurrence(
1082
1168
  words.stepDate + stepDates(pattern.date),
1083
- ir,
1169
+ schedule,
1084
1170
  opts
1085
1171
  );
1086
1172
  }
1087
- if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
1088
- return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
1173
+ if (pattern.month !== "*" && !monthFoldsIntoDate(schedule)) {
1174
+ return "on the " + dateOrdinals(schedule, opts) + monthScope(schedule, opts);
1089
1175
  }
1090
1176
  if (pattern.month !== "*") {
1091
- return "on " + monthDatePhrase(ir, opts);
1177
+ return "on " + monthDatePhrase(schedule, opts);
1092
1178
  }
1093
- return "on the " + dateOrdinals(ir, opts);
1179
+ return "on the " + dateOrdinals(schedule, opts);
1094
1180
  }
1095
- function monthFoldsIntoDate(ir) {
1096
- return !oddEvenMonth(ir.pattern.month) && // Reached only with a restricted month, which has segments.
1097
- segmentsOf(ir, "month").every(function flat(segment) {
1181
+ function monthFoldsIntoDate(schedule) {
1182
+ return !oddEvenMonth(schedule.pattern.month) && // Reached only with a restricted month, which has segments.
1183
+ segmentsOf(schedule, "month").every(function flat(segment) {
1098
1184
  return segment.kind !== "range";
1099
1185
  });
1100
1186
  }
1101
- function isDayUnion(ir, opts) {
1102
- return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
1187
+ function isDayUnion(schedule, opts) {
1188
+ return schedule.pattern.date !== "*" && schedule.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
1103
1189
  }
1104
- function dayUnionCondition(ir, opts) {
1190
+ function dayUnionCondition(schedule, opts) {
1105
1191
  const pieces = [
1106
- ...dayUnionDatePieces(ir, opts),
1107
- ...dayUnionWeekdayPieces(ir, opts)
1192
+ ...dayUnionDatePieces(schedule, opts),
1193
+ ...dayUnionWeekdayPieces(schedule, opts)
1108
1194
  ];
1109
1195
  return " whenever the day is " + joinOr(pieces, opts);
1110
1196
  }
1111
- function dayUnionMonthLead(ir, opts) {
1112
- if (ir.pattern.month === "*") {
1197
+ function dayUnionMonthLead(schedule, opts) {
1198
+ if (schedule.pattern.month === "*") {
1113
1199
  return "";
1114
1200
  }
1115
- return "in " + monthName(ir, opts) + " ";
1201
+ return "in " + monthName(schedule, opts) + " ";
1116
1202
  }
1117
- function dayUnionDatePieces(ir, opts) {
1118
- const dateField = ir.pattern.date;
1203
+ function dayUnionDatePieces(schedule, opts) {
1204
+ const dateField = schedule.pattern.date;
1119
1205
  const quartz = quartzDatePhrase(dateField, opts);
1120
1206
  if (quartz) {
1121
1207
  return [quartz.replace(/^on /, "")];
@@ -1125,7 +1211,7 @@ function dayUnionDatePieces(ir, opts) {
1125
1211
  return [oddEven];
1126
1212
  }
1127
1213
  const pieces = [];
1128
- segmentsOf(ir, "date").forEach(function expand(segment) {
1214
+ segmentsOf(schedule, "date").forEach(function expand(segment) {
1129
1215
  if (segment.kind === "range") {
1130
1216
  pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
1131
1217
  } else if (segment.kind === "step") {
@@ -1138,14 +1224,14 @@ function dayUnionDatePieces(ir, opts) {
1138
1224
  });
1139
1225
  return pieces;
1140
1226
  }
1141
- function dayUnionWeekdayPieces(ir, opts) {
1142
- const weekdayField = ir.pattern.weekday;
1227
+ function dayUnionWeekdayPieces(schedule, opts) {
1228
+ const weekdayField = schedule.pattern.weekday;
1143
1229
  const quartz = quartzWeekdayPhrase(weekdayField, opts);
1144
1230
  if (quartz) {
1145
1231
  return [quartz.replace(/^on /, "")];
1146
1232
  }
1147
1233
  const pieces = [];
1148
- segmentsOf(ir, "weekday").forEach(function expand(segment) {
1234
+ segmentsOf(schedule, "weekday").forEach(function expand(segment) {
1149
1235
  if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
1150
1236
  pieces.push("a weekday");
1151
1237
  } else if (segment.kind === "range") {
@@ -1173,16 +1259,16 @@ function oddEvenDay(dateField) {
1173
1259
  }
1174
1260
  return start === "2" ? "an even-numbered day" : null;
1175
1261
  }
1176
- function dateOrWeekday(ir, opts) {
1177
- const pattern = ir.pattern;
1178
- const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
1179
- if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
1180
- return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
1262
+ function dateOrWeekday(schedule, opts) {
1263
+ const pattern = schedule.pattern;
1264
+ const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(schedule, false, opts);
1265
+ if (pattern.month !== "*" && monthFoldsIntoDate(schedule) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
1266
+ return "on " + monthDatePhrase(schedule, opts) + " or " + weekdayPart + " in " + monthName(schedule, opts);
1181
1267
  }
1182
- return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
1268
+ return datePart(schedule, opts) + " or " + weekdayPart + orMonthScope(schedule, opts);
1183
1269
  }
1184
- function datePart(ir, opts) {
1185
- const pattern = ir.pattern;
1270
+ function datePart(schedule, opts) {
1271
+ const pattern = schedule.pattern;
1186
1272
  const quartzDate = quartzDatePhrase(pattern.date, opts);
1187
1273
  if (quartzDate) {
1188
1274
  return quartzDate;
@@ -1190,13 +1276,13 @@ function datePart(ir, opts) {
1190
1276
  if (isOpenStep(pattern.date)) {
1191
1277
  return stepDates(pattern.date);
1192
1278
  }
1193
- return "on the " + dateOrdinals(ir, opts);
1279
+ return "on the " + dateOrdinals(schedule, opts);
1194
1280
  }
1195
- function orMonthScope(ir, opts) {
1196
- if (ir.pattern.month === "*") {
1281
+ function orMonthScope(schedule, opts) {
1282
+ if (schedule.pattern.month === "*") {
1197
1283
  return "";
1198
1284
  }
1199
- return ", in " + monthName(ir, opts);
1285
+ return ", in " + monthName(schedule, opts);
1200
1286
  }
1201
1287
  function quartzDatePhrase(dateField, opts) {
1202
1288
  if (dateField === "L") {
@@ -1223,39 +1309,39 @@ function quartzWeekdayPhrase(weekdayField, opts) {
1223
1309
  return "on the last " + getWeekday(weekdayField.slice(0, -1), opts) + " of the month";
1224
1310
  }
1225
1311
  }
1226
- function monthDatePhrase(ir, opts) {
1227
- const month = monthName(ir, opts);
1312
+ function monthDatePhrase(schedule, opts) {
1313
+ const month = monthName(schedule, opts);
1228
1314
  const days = renderSegments(
1229
- segmentsOf(ir, "date"),
1315
+ segmentsOf(schedule, "date"),
1230
1316
  opts.style.ordinals ? getOrdinal : cardinalDay,
1231
1317
  opts
1232
1318
  );
1233
- if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
1234
- return "the " + getOrdinal(ir.pattern.date) + " of " + month;
1319
+ if (opts.style.dayFirst && schedule.shapes.date === "single" && schedule.shapes.month !== "single") {
1320
+ return "the " + getOrdinal(schedule.pattern.date) + " of " + month;
1235
1321
  }
1236
1322
  return opts.style.dayFirst ? days + " " + month : month + " " + days;
1237
1323
  }
1238
1324
  function cardinalDay(value) {
1239
1325
  return "" + value;
1240
1326
  }
1241
- function monthScope(ir, opts) {
1242
- if (ir.pattern.month === "*") {
1327
+ function monthScope(schedule, opts) {
1328
+ if (schedule.pattern.month === "*") {
1243
1329
  return "";
1244
1330
  }
1245
- return " in " + monthName(ir, opts);
1331
+ return " in " + monthName(schedule, opts);
1246
1332
  }
1247
- function monthScopeForRecurrence(phrase, ir, opts) {
1248
- if (ir.pattern.month === "*") {
1333
+ function monthScopeForRecurrence(phrase, schedule, opts) {
1334
+ if (schedule.pattern.month === "*") {
1249
1335
  return phrase;
1250
1336
  }
1251
1337
  const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
1252
- if (carriesRecurrence && ir.shapes.month === "range") {
1253
- return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
1338
+ if (carriesRecurrence && schedule.shapes.month === "range") {
1339
+ return phrase.replace(" of the month", " of each month") + " from " + monthName(schedule, opts);
1254
1340
  }
1255
- if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
1256
- return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
1341
+ if (carriesRecurrence && (schedule.shapes.month === "single" || schedule.shapes.month === "step")) {
1342
+ return phrase.replace(" of the month", "") + " in " + monthName(schedule, opts);
1257
1343
  }
1258
- return phrase + " in " + monthName(ir, opts);
1344
+ return phrase + " in " + monthName(schedule, opts);
1259
1345
  }
1260
1346
  function stepDates(dateField) {
1261
1347
  const parts = dateField.split("/");
@@ -1268,15 +1354,15 @@ function stepDates(dateField) {
1268
1354
  }
1269
1355
  return phrase;
1270
1356
  }
1271
- function dateOrdinals(ir, opts) {
1272
- return renderSegments(segmentsOf(ir, "date"), getOrdinal, opts);
1357
+ function dateOrdinals(schedule, opts) {
1358
+ return renderSegments(segmentsOf(schedule, "date"), getOrdinal, opts);
1273
1359
  }
1274
- function monthName(ir, opts) {
1275
- const oddEven = oddEvenMonth(ir.pattern.month);
1360
+ function monthName(schedule, opts) {
1361
+ const oddEven = oddEvenMonth(schedule.pattern.month);
1276
1362
  if (oddEven) {
1277
1363
  return oddEven;
1278
1364
  }
1279
- return renderSegments(segmentsOf(ir, "month"), function name(value) {
1365
+ return renderSegments(segmentsOf(schedule, "month"), function name(value) {
1280
1366
  return getMonth(value, opts);
1281
1367
  }, opts);
1282
1368
  }
@@ -1293,8 +1379,8 @@ function oddEvenMonth(monthField) {
1293
1379
  }
1294
1380
  return start === "2" ? "every even-numbered month" : null;
1295
1381
  }
1296
- function weekdayPhrase(ir, recurring, opts) {
1297
- const segments = orderWeekdaysForDisplay(segmentsOf(ir, "weekday"));
1382
+ function weekdayPhrase(schedule, recurring, opts) {
1383
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
1298
1384
  const hasRange = segments.some(function range(segment) {
1299
1385
  return segment.kind === "range";
1300
1386
  });
@@ -1322,8 +1408,8 @@ function renderSegments(segments, word, opts) {
1322
1408
  });
1323
1409
  return joinList(pieces, opts);
1324
1410
  }
1325
- function applyYear(description, ir, opts) {
1326
- const yearField = ir.pattern.year;
1411
+ function applyYear(description, schedule, opts) {
1412
+ const yearField = schedule.pattern.year;
1327
1413
  if (yearField === "*") {
1328
1414
  return description;
1329
1415
  }
@@ -1331,7 +1417,7 @@ function applyYear(description, ir, opts) {
1331
1417
  return description + ", " + stepYears(yearField, opts);
1332
1418
  }
1333
1419
  const label = yearLabel(yearField, opts);
1334
- if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
1420
+ if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && schedule.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
1335
1421
  const yearGlue = opts.style.dayFirst ? " " : ", ";
1336
1422
  return description.replace(" at ", yearGlue + label + " at ");
1337
1423
  }