cronli5 0.2.1 → 0.3.4

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