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