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