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/cronli5.js CHANGED
@@ -69,41 +69,6 @@ function isNonNegativeInteger(value) {
69
69
  const digits = /^\d+$/;
70
70
  return digits.test(value);
71
71
  }
72
- function arithmeticStep(values) {
73
- if (values.length < 5) {
74
- return null;
75
- }
76
- const interval = values[1] - values[0];
77
- if (interval < 2) {
78
- return null;
79
- }
80
- for (let i = 2; i < values.length; i += 1) {
81
- if (values[i] - values[i - 1] !== interval) {
82
- return null;
83
- }
84
- }
85
- return { start: values[0], interval, last: values[values.length - 1] };
86
- }
87
- function weekdayDisplayKey(value) {
88
- return value === 0 ? 7 : value;
89
- }
90
- function orderWeekdaysForDisplay(segments) {
91
- const flattened = segments.flatMap(function flat(segment) {
92
- return segment.kind === "step" ? segment.fires.map(function single(value) {
93
- return { kind: "single", value: "" + value };
94
- }) : [segment];
95
- });
96
- function key(segment) {
97
- return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
98
- }
99
- return flattened.map(function index(segment, position) {
100
- return [segment, position];
101
- }).sort(function byDisplayKey(a, b) {
102
- return key(a[0]) - key(b[0]) || a[1] - b[1];
103
- }).map(function unwrap(pair) {
104
- return pair[0];
105
- });
106
- }
107
72
  function toFieldNumber(token, numberMap) {
108
73
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
109
74
  }
@@ -236,7 +201,7 @@ function normalizeField(value, field, spec) {
236
201
  const cycle = timeFieldCycle[field];
237
202
  const segments = stringValue.split(",").map(function canonical(segment) {
238
203
  return canonicalizeTokens(collapseFullSpanRange(
239
- enumerateNonUniformStep(
204
+ enumerateIfNonUniform(
240
205
  collapseFullSpanStep(
241
206
  collapseDegenerateRange(
242
207
  collapseOnceStep(collapseUnitStep(segment, spec), spec),
@@ -300,7 +265,7 @@ function collapseOnceStep(segment, spec) {
300
265
  }
301
266
  return start === "*" ? "" + spec.min : start;
302
267
  }
303
- function enumerateNonUniformStep(segment, spec, cycle) {
268
+ function enumerateIfNonUniform(segment, spec, cycle) {
304
269
  const parts = segment.split("/");
305
270
  if (typeof cycle !== "number" || parts.length !== 2 || includes(parts[0], "-")) {
306
271
  return segment;
@@ -439,6 +404,93 @@ function expandMacro(cronString) {
439
404
  throw new Error("`cronli5` does not recognize the macro `" + trimmed + "`.");
440
405
  }
441
406
 
407
+ // src/core/weekday.ts
408
+ function weekdayDisplayKey(value) {
409
+ return value === 0 ? 7 : value;
410
+ }
411
+ function orderWeekdaysForDisplay(segments) {
412
+ const flattened = segments.flatMap(function flat(segment) {
413
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
414
+ return { kind: "single", value: "" + value };
415
+ }) : [segment];
416
+ });
417
+ function key(segment) {
418
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
419
+ }
420
+ return flattened.map(function index(segment, position) {
421
+ return [segment, position];
422
+ }).sort(function byDisplayKey(a, b) {
423
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
424
+ }).map(function unwrap(pair) {
425
+ return pair[0];
426
+ });
427
+ }
428
+
429
+ // src/core/cadence.ts
430
+ function arithmeticStep(values) {
431
+ if (values.length < 5) {
432
+ return null;
433
+ }
434
+ const interval = values[1] - values[0];
435
+ if (interval < 2) {
436
+ return null;
437
+ }
438
+ for (let i = 2; i < values.length; i += 1) {
439
+ if (values[i] - values[i - 1] !== interval) {
440
+ return null;
441
+ }
442
+ }
443
+ return { start: values[0], interval, last: values[values.length - 1] };
444
+ }
445
+ function segmentsOf(schedule, field) {
446
+ return schedule.analyses.segments[field] ?? [];
447
+ }
448
+ function stepSegment(schedule, field) {
449
+ return segmentsOf(schedule, field)[0];
450
+ }
451
+ function singleValues(segments) {
452
+ const values = [];
453
+ for (const segment of segments) {
454
+ if (segment.kind !== "single") {
455
+ return null;
456
+ }
457
+ values.push(+segment.value);
458
+ }
459
+ return values;
460
+ }
461
+ function offsetCleanStride(stride) {
462
+ return stride.start < stride.interval && 24 % stride.interval === 0;
463
+ }
464
+ function renderStride(spec, parts) {
465
+ const { start, interval, cycle } = spec;
466
+ const tiles = cycle % interval === 0;
467
+ if (start === 0 && tiles) {
468
+ return parts.bare();
469
+ }
470
+ if (start < interval && tiles) {
471
+ return parts.offset();
472
+ }
473
+ return parts.bounded();
474
+ }
475
+ function hourListStride(values) {
476
+ if (values.length < 2) {
477
+ return null;
478
+ }
479
+ const interval = values[1] - values[0];
480
+ if (interval < 2) {
481
+ return null;
482
+ }
483
+ for (let i = 2; i < values.length; i += 1) {
484
+ if (values[i] - values[i - 1] !== interval) {
485
+ return null;
486
+ }
487
+ }
488
+ if (values[0] !== 0 && values.length < 5) {
489
+ return null;
490
+ }
491
+ return { interval, last: values[values.length - 1], start: values[0] };
492
+ }
493
+
442
494
  // src/core/shapes.ts
443
495
  function isSingleValue(field) {
444
496
  return field !== "*" && !includes(field, ",") && !includes(field, "-") && !includes(field, "/");
@@ -455,6 +507,9 @@ function isDiscreteList(field) {
455
507
  function isDiscreteHours(hourField) {
456
508
  return hourField !== "*" && !isPlainRange(hourField) && !isPlainStep(hourField);
457
509
  }
510
+ function isOpenStep(field) {
511
+ return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
512
+ }
458
513
 
459
514
  // src/core/analyze.ts
460
515
  function getOccurrences(start, interval, max) {
@@ -590,11 +645,11 @@ function analyze(pattern) {
590
645
  minuteSpan: minuteSpan(pattern.minute),
591
646
  segments
592
647
  };
593
- const content = { analyses, pattern, shapes };
594
- return { ...content, plan: selectStrategy(content) };
648
+ const facts = { analyses, pattern, shapes };
649
+ return { ...facts, plan: selectPlan(facts) };
595
650
  }
596
- function selectStrategy(content) {
597
- const { analyses, pattern, shapes } = content;
651
+ function selectPlan(facts) {
652
+ const { analyses, pattern, shapes } = facts;
598
653
  if (pattern.second !== "0") {
599
654
  const seconds = planSeconds(pattern, shapes, analyses);
600
655
  if (seconds) {
@@ -938,72 +993,134 @@ function normalizeOptions(options) {
938
993
  years: !!options.years
939
994
  };
940
995
  }
941
- function describe(ir, opts) {
942
- const body = confinement(ir, opts) ?? render(ir, ir.plan, opts);
943
- const lead = isDayUnion(ir, opts) ? dayUnionMonthLead(ir, opts) : "";
944
- return applyYear(lead + body, ir, opts);
996
+ function describe(schedule, opts) {
997
+ const dense = denseCadence(schedule, opts);
998
+ if (dense !== null) {
999
+ return applyYear(dense, schedule, opts);
1000
+ }
1001
+ const body = confinement(schedule, opts) ?? render(schedule, schedule.plan, opts);
1002
+ const lead = isDayUnion(schedule, opts) ? dayUnionMonthLead(schedule, opts) : "";
1003
+ return applyYear(lead + body, schedule, opts);
945
1004
  }
946
- function render(ir, plan, opts) {
1005
+ function render(schedule, plan, opts) {
947
1006
  const renderer = renderers[plan.kind];
948
- return renderer(ir, plan, opts);
1007
+ return renderer(schedule, plan, opts);
949
1008
  }
950
- function renderEverySecond(ir, plan, opts) {
951
- return "every second" + trailingQualifier(ir, opts);
1009
+ function isCadenceShape(shape) {
1010
+ return shape === "step" || shape === "range" || shape === "list";
952
1011
  }
953
- function renderStandaloneSeconds(ir, plan, opts) {
954
- return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
1012
+ function isDenseCadence(schedule, opts) {
1013
+ if (!opts.style.untilWindow || opts.short || schedule.plan.kind !== "composeSeconds" || schedule.plan.rest.kind === "clockTimes" || isDayUnion(schedule, opts)) {
1014
+ return false;
1015
+ }
1016
+ const { shapes } = schedule;
1017
+ return isCadenceShape(shapes.second) && isCadenceShape(shapes.minute) && isCadenceShape(shapes.hour);
955
1018
  }
956
- function renderSecondPastMinute(ir, plan, opts) {
957
- const secondField = ir.pattern.second;
958
- return getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute, every minute" + trailingQualifier(ir, opts);
1019
+ function denseHourFragment(schedule, opts) {
1020
+ const stride = hourStride(schedule);
1021
+ if (stride) {
1022
+ return hourStrideCadence(stride, opts);
1023
+ }
1024
+ if (schedule.shapes.hour === "range") {
1025
+ const segment = segmentsOf(schedule, "hour").find(function range(part) {
1026
+ return part.kind === "range";
1027
+ });
1028
+ return rangeWindow({
1029
+ continuous: false,
1030
+ from: +segment.bounds[0],
1031
+ throughMinute: 0,
1032
+ to: +segment.bounds[1]
1033
+ }, opts);
1034
+ }
1035
+ return "during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours";
959
1036
  }
960
- function renderSecondsWithinMinute(ir, plan, opts) {
961
- const minuteField = ir.pattern.minute;
1037
+ function denseMinuteFragment(schedule, opts) {
1038
+ if (schedule.shapes.minute === "step") {
1039
+ return stepCycle60(stepSegment(schedule, "minute"), "minute", "hour", opts);
1040
+ }
1041
+ if (schedule.shapes.minute === "range") {
1042
+ return minuteRangeLead(schedule.pattern.minute, opts);
1043
+ }
1044
+ return strideFromSegments(
1045
+ segmentsOf(schedule, "minute"),
1046
+ "minute",
1047
+ "hour",
1048
+ opts
1049
+ ) ?? listPastThe(
1050
+ segmentWords(segmentsOf(schedule, "minute"), opts),
1051
+ "minute",
1052
+ "hour",
1053
+ opts
1054
+ );
1055
+ }
1056
+ function denseCadence(schedule, opts) {
1057
+ if (!isDenseCadence(schedule, opts)) {
1058
+ return null;
1059
+ }
1060
+ const hour = denseHourFragment(schedule, opts);
1061
+ const minute = denseMinuteFragment(schedule, opts);
1062
+ const second = secondsClause(schedule, "minute", opts);
1063
+ const nested = hour + ", " + minute + ", and within each of those minutes, " + second;
1064
+ const anchor = trailingQualifier(schedule, opts).trim();
1065
+ return anchor ? anchor + ", " + nested : nested;
1066
+ }
1067
+ function renderEverySecond(schedule, plan, opts) {
1068
+ return "every second" + trailingQualifier(schedule, opts);
1069
+ }
1070
+ function renderStandaloneSeconds(schedule, plan, opts) {
1071
+ return secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
1072
+ }
1073
+ function renderSecondPastMinute(schedule, plan, opts) {
1074
+ const secondField = schedule.pattern.second;
1075
+ return getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the minute, every minute" + trailingQualifier(schedule, opts);
1076
+ }
1077
+ function renderSecondsWithinMinute(schedule, plan, opts) {
1078
+ const minuteField = schedule.pattern.minute;
962
1079
  const minuteWord = getNumber(minuteField, opts);
963
1080
  const minuteUnit = pluralize(minuteField, "minute");
964
1081
  if (plan.singleSecond) {
965
- const secondField = ir.pattern.second;
966
- return minuteWord + " " + minuteUnit + " and " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the hour, every hour" + trailingQualifier(ir, opts);
1082
+ const secondField = schedule.pattern.second;
1083
+ return minuteWord + " " + minuteUnit + " and " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the hour, every hour" + trailingQualifier(schedule, opts);
967
1084
  }
968
- return secondsLeadClause(ir, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(ir, opts);
1085
+ return secondsLeadClause(schedule, opts) + ", " + minuteWord + " " + minuteUnit + " past the hour, every hour" + trailingQualifier(schedule, opts);
969
1086
  }
970
- function composeHourCadence(ir, plan, opts) {
1087
+ function composeHourCadence(schedule, plan, opts) {
971
1088
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
972
- if (!clockRest || ir.shapes.minute !== "single") {
1089
+ if (!clockRest || schedule.shapes.minute !== "single") {
973
1090
  return null;
974
1091
  }
975
- const minute = +ir.pattern.minute;
976
- return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
1092
+ const minute = +schedule.pattern.minute;
1093
+ return hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
977
1094
  }
978
- function clockTimesConfinement(ir, rest, opts) {
979
- if (+rest.times[0].minute === 0 && ir.shapes.minute === "single") {
980
- return secondsLeadClause(ir, opts) + " for one minute at " + durationHours(ir, rest, opts);
1095
+ function clockTimesConfinement(schedule, rest, opts) {
1096
+ if (+rest.times[0].minute === 0 && schedule.shapes.minute === "single") {
1097
+ return secondsLeadClause(schedule, opts) + " for one minute at " + durationHours(schedule, rest, opts);
981
1098
  }
982
- return secondsLeadClause(ir, opts) + " of " + clockTimesOf(ir, rest, opts);
1099
+ return secondsLeadClause(schedule, opts) + " of " + clockTimesOf(schedule, rest, opts);
983
1100
  }
984
- function renderComposeSeconds(ir, plan, opts) {
985
- const cadence = composeHourCadence(ir, plan, opts);
1101
+ function renderComposeSeconds(schedule, plan, opts) {
1102
+ const cadence = composeHourCadence(schedule, plan, opts);
986
1103
  if (cadence !== null) {
987
1104
  return cadence;
988
1105
  }
989
- if (plan.rest.kind === "clockTimes" && (ir.shapes.second === "wildcard" || ir.shapes.second === "step")) {
990
- return clockTimesConfinement(ir, plan.rest, opts);
1106
+ if (plan.rest.kind === "clockTimes" && (schedule.shapes.second === "wildcard" || schedule.shapes.second === "step")) {
1107
+ return clockTimesConfinement(schedule, plan.rest, opts);
991
1108
  }
992
- if (ir.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && ir.pattern.minute === "*/2") {
993
- return "every second of every other minute" + trailingQualifier(ir, opts);
1109
+ if (schedule.shapes.second === "wildcard" && plan.rest.kind === "minuteFrequency" && plan.rest.hours.kind === "none" && schedule.pattern.minute === "*/2") {
1110
+ return "every second of every other minute" + trailingQualifier(schedule, opts);
994
1111
  }
995
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
996
- const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
997
- return lead + render(ir, plan.rest, opts);
1112
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && schedule.analyses.clockSecond;
1113
+ const lead = restOwnsLead ? "" : secondsLeadClause(schedule, opts) + ", ";
1114
+ return lead + render(schedule, plan.rest, opts);
998
1115
  }
999
- function durationHours(ir, plan, opts) {
1116
+ function durationHours(schedule, plan, opts) {
1000
1117
  const hours = plan.times.map(function clock(time) {
1001
1118
  return getTime({ hour: time.hour, minute: 0 }, opts);
1002
1119
  });
1003
- const trail = dayQualifier(ir, leadingWords, opts);
1120
+ const trail = dayQualifier(schedule, leadingWords, opts);
1004
1121
  return joinList(hours, opts) + (trail && ", " + trail);
1005
1122
  }
1006
- function clockTimesOf(ir, plan, opts) {
1123
+ function clockTimesOf(schedule, plan, opts) {
1007
1124
  const times = plan.times.map(function clock(time) {
1008
1125
  return getTime({
1009
1126
  hour: time.hour,
@@ -1012,21 +1129,21 @@ function clockTimesOf(ir, plan, opts) {
1012
1129
  explicit: true
1013
1130
  }, opts);
1014
1131
  });
1015
- const trail = dayQualifier(ir, leadingWords, opts);
1132
+ const trail = dayQualifier(schedule, leadingWords, opts);
1016
1133
  return joinList(times, opts) + (trail && ", " + trail);
1017
1134
  }
1018
- function secondsLeadClause(ir, opts) {
1019
- return secondsClause(ir, "minute", opts);
1135
+ function secondsLeadClause(schedule, opts) {
1136
+ return secondsClause(schedule, "minute", opts);
1020
1137
  }
1021
- function secondsClause(ir, anchor, opts) {
1022
- const secondField = ir.pattern.second;
1023
- const shape = ir.shapes.second;
1138
+ function secondsClause(schedule, anchor, opts) {
1139
+ const secondField = schedule.pattern.second;
1140
+ const shape = schedule.shapes.second;
1024
1141
  if (secondField === "*") {
1025
1142
  return "every second";
1026
1143
  }
1027
1144
  if (shape === "step") {
1028
1145
  return stepCycle60(
1029
- ir.analyses.segments.second[0],
1146
+ stepSegment(schedule, "second"),
1030
1147
  "second",
1031
1148
  anchor,
1032
1149
  opts
@@ -1041,86 +1158,96 @@ function secondsClause(ir, anchor, opts) {
1041
1158
  return "at " + getNumber(secondField, opts) + " " + pluralize(secondField, "second") + " past the " + anchor;
1042
1159
  }
1043
1160
  return strideFromSegments(
1044
- ir.analyses.segments.second,
1161
+ segmentsOf(schedule, "second"),
1045
1162
  "second",
1046
1163
  anchor,
1047
1164
  opts
1048
1165
  ) ?? listPastThe(
1049
- segmentWords(ir.analyses.segments.second, opts),
1166
+ segmentWords(segmentsOf(schedule, "second"), opts),
1050
1167
  "second",
1051
1168
  anchor,
1052
1169
  opts
1053
1170
  );
1054
1171
  }
1055
- function renderEveryMinute(ir, plan, opts) {
1056
- return "every minute" + trailingQualifier(ir, opts);
1172
+ function renderEveryMinute(schedule, plan, opts) {
1173
+ return "every minute" + trailingQualifier(schedule, opts);
1057
1174
  }
1058
- function renderSingleMinute(ir, plan, opts) {
1059
- const minuteField = ir.pattern.minute;
1060
- return getNumber(minuteField, opts) + " " + pluralize(minuteField, "minute") + " past the hour, every hour" + trailingQualifier(ir, opts);
1175
+ function renderSingleMinute(schedule, plan, opts) {
1176
+ const minuteField = schedule.pattern.minute;
1177
+ return getNumber(minuteField, opts) + " " + pluralize(minuteField, "minute") + " past the hour, every hour" + trailingQualifier(schedule, opts);
1061
1178
  }
1062
- function renderRangeOfMinutes(ir, plan, opts) {
1063
- return minuteRangeLead(ir.pattern.minute, opts) + trailingQualifier(ir, opts);
1179
+ function renderRangeOfMinutes(schedule, plan, opts) {
1180
+ return minuteRangeLead(schedule.pattern.minute, opts) + trailingQualifier(schedule, opts);
1064
1181
  }
1065
- function renderMultipleMinutes(ir, plan, opts) {
1066
- const stride = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts);
1182
+ function renderMultipleMinutes(schedule, plan, opts) {
1183
+ const stride = strideFromSegments(segmentsOf(schedule, "minute"), "minute", "hour", opts);
1067
1184
  return (stride ?? listPastThe(segmentWords(
1068
- ir.analyses.segments.minute,
1185
+ segmentsOf(schedule, "minute"),
1069
1186
  opts
1070
- ), "minute", "hour", opts)) + trailingQualifier(ir, opts);
1187
+ ), "minute", "hour", opts)) + trailingQualifier(schedule, opts);
1071
1188
  }
1072
- function renderMinuteFrequency(ir, plan, opts) {
1189
+ function renderMinuteFrequency(schedule, plan, opts) {
1073
1190
  let phrase = stepCycle60(
1074
- ir.analyses.segments.minute[0],
1191
+ stepSegment(schedule, "minute"),
1075
1192
  "minute",
1076
1193
  "hour",
1077
1194
  opts
1078
1195
  );
1079
1196
  if (plan.hours.kind === "during") {
1080
- const cadence = unevenHourCadence(ir, opts);
1081
- phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(ir, plan.hours.times, false, opts) + " hours";
1197
+ const cadence = unevenHourCadence(schedule, opts);
1198
+ phrase += cadence ? ", " + cadence : " during the " + hourTimesFromPlan(schedule, plan.hours.times, false, opts) + " hours";
1082
1199
  } else if (plan.hours.kind === "window") {
1083
- phrase += " " + hourWindow(plan.hours, opts);
1200
+ phrase += " " + rangeWindow({
1201
+ continuous: false,
1202
+ from: plan.hours.from,
1203
+ throughMinute: plan.hours.last,
1204
+ to: plan.hours.to
1205
+ }, opts);
1084
1206
  } else if (plan.hours.kind === "step") {
1085
- phrase += " " + everyNthHour(ir.analyses.segments.hour[0], opts);
1207
+ phrase += " " + everyNthHour(stepSegment(schedule, "hour"), opts);
1086
1208
  }
1087
- return phrase + trailingQualifier(ir, opts);
1209
+ return phrase + trailingQualifier(schedule, opts);
1088
1210
  }
1089
- function renderMinuteSpanInHour(ir, plan, opts) {
1090
- if (ir.pattern.minute === "*") {
1091
- return "every minute of the " + getTime({ hour: plan.hour, minute: 0 }, opts) + " hour" + trailingQualifier(ir, opts);
1211
+ function renderMinuteSpanInHour(schedule, plan, opts) {
1212
+ if (schedule.pattern.minute === "*") {
1213
+ return "every minute of the " + getTime({ hour: plan.hour, minute: 0 }, opts) + " hour" + trailingQualifier(schedule, opts);
1092
1214
  }
1093
- 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);
1215
+ 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);
1094
1216
  }
1095
- function renderMinutesAcrossHours(ir, plan, opts) {
1096
- const cadence = unevenHourCadence(ir, opts);
1217
+ function renderMinutesAcrossHours(schedule, plan, opts) {
1218
+ const cadence = unevenHourCadence(schedule, opts);
1097
1219
  if (plan.form === "wildcard") {
1098
1220
  if (cadence !== null) {
1099
- return "every minute, " + cadence + trailingQualifier(ir, opts);
1221
+ return "every minute, " + cadence + trailingQualifier(schedule, opts);
1100
1222
  }
1101
- return "every minute during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
1223
+ return "every minute during the " + hourTimesFromPlan(schedule, plan.times, false, opts) + " hours" + trailingQualifier(schedule, opts);
1102
1224
  }
1103
1225
  if (plan.form === "range") {
1104
- const lead2 = minuteRangeLead(ir.pattern.minute, opts);
1226
+ const lead2 = minuteRangeLead(schedule.pattern.minute, opts);
1105
1227
  if (cadence !== null) {
1106
- return lead2 + ", " + cadence + trailingQualifier(ir, opts);
1228
+ return lead2 + ", " + cadence + trailingQualifier(schedule, opts);
1107
1229
  }
1108
1230
  if (singleHourFire(plan.times)) {
1109
- return lead2 + ", at " + hourTimesFromPlan(ir, plan.times, true, opts) + trailingQualifier(ir, opts);
1231
+ return lead2 + ", at " + hourTimesFromPlan(schedule, plan.times, true, opts) + trailingQualifier(schedule, opts);
1110
1232
  }
1111
- return lead2 + " during the " + hourTimesFromPlan(ir, plan.times, false, opts) + " hours" + trailingQualifier(ir, opts);
1233
+ return lead2 + " during the " + hourTimesFromPlan(schedule, plan.times, false, opts) + " hours" + trailingQualifier(schedule, opts);
1112
1234
  }
1113
- const lead = strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1114
- segmentWords(ir.analyses.segments.minute, opts),
1235
+ const lead = strideFromSegments(
1236
+ segmentsOf(schedule, "minute"),
1237
+ "minute",
1238
+ "hour",
1239
+ opts
1240
+ ) ?? listPastThe(
1241
+ segmentWords(segmentsOf(schedule, "minute"), opts),
1115
1242
  "minute",
1116
1243
  "hour",
1117
1244
  opts
1118
1245
  );
1119
1246
  if (cadence !== null) {
1120
- return lead + ", " + cadence + trailingQualifier(ir, opts);
1247
+ return lead + ", " + cadence + trailingQualifier(schedule, opts);
1121
1248
  }
1122
- const times = hourTimesFromPlan(ir, plan.times, true, opts);
1123
- return lead + ", at " + times + trailingQualifier(ir, opts);
1249
+ const times = hourTimesFromPlan(schedule, plan.times, true, opts);
1250
+ return lead + ", at " + times + trailingQualifier(schedule, opts);
1124
1251
  }
1125
1252
  var stepOrdinals = {
1126
1253
  2: "other",
@@ -1135,79 +1262,91 @@ function everyNthHour(segment, opts) {
1135
1262
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
1136
1263
  return start === 0 ? base : base + " starting at " + getTime({ hour: start, minute: 0 }, opts);
1137
1264
  }
1138
- function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
1139
- const segment = ir.analyses.segments.hour[0];
1265
+ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
1266
+ const segment = stepSegment(schedule, "hour");
1140
1267
  if (plan.form === "wildcard") {
1141
- return "every minute " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
1268
+ return "every minute " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
1142
1269
  }
1143
- const lead = plan.form === "list" ? strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1144
- segmentWords(ir.analyses.segments.minute, opts),
1270
+ const lead = plan.form === "list" ? strideFromSegments(
1271
+ segmentsOf(schedule, "minute"),
1272
+ "minute",
1273
+ "hour",
1274
+ opts
1275
+ ) ?? listPastThe(
1276
+ segmentWords(segmentsOf(schedule, "minute"), opts),
1145
1277
  "minute",
1146
1278
  "hour",
1147
1279
  opts
1148
- ) : minuteRangeLead(ir.pattern.minute, opts);
1149
- const cadence = unevenHourCadence(ir, opts);
1150
- return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(ir, opts);
1280
+ ) : minuteRangeLead(schedule.pattern.minute, opts);
1281
+ const cadence = unevenHourCadence(schedule, opts);
1282
+ return lead + ", " + (cadence ?? stepHours(segment, opts)) + trailingQualifier(schedule, opts);
1151
1283
  }
1152
1284
  function minuteRangeLead(minuteField, opts) {
1153
1285
  const bounds = minuteField.split("-");
1154
1286
  const num = seriesNumber();
1155
1287
  return "every minute from " + num(bounds[0]) + through(opts) + num(bounds[1]) + " past the hour";
1156
1288
  }
1157
- function renderEveryHour(ir, plan, opts) {
1158
- return "every hour" + trailingQualifier(ir, opts);
1289
+ function renderEveryHour(schedule, plan, opts) {
1290
+ return "every hour" + trailingQualifier(schedule, opts);
1159
1291
  }
1160
- function renderHourRange(ir, plan, opts) {
1292
+ function renderHourRange(schedule, plan, opts) {
1161
1293
  const window = hourWindow(boundedWindow(plan), opts);
1162
1294
  if (plan.minuteForm === "wildcard") {
1163
- return "every minute " + window + trailingQualifier(ir, opts);
1295
+ return "every minute " + window + trailingQualifier(schedule, opts);
1164
1296
  }
1165
1297
  if (plan.minuteForm === "range") {
1166
- return minuteRangeLead(ir.pattern.minute, opts) + ", " + window + trailingQualifier(ir, opts);
1298
+ return minuteRangeLead(schedule.pattern.minute, opts) + ", " + window + trailingQualifier(schedule, opts);
1167
1299
  }
1168
- return rangeMinuteLead(ir, opts) + " " + window + trailingQualifier(ir, opts);
1300
+ return rangeMinuteLead(schedule, opts) + " " + window + trailingQualifier(schedule, opts);
1169
1301
  }
1170
- function rangeMinuteLead(ir, opts) {
1171
- if (ir.pattern.minute === "0") {
1302
+ function rangeMinuteLead(schedule, opts) {
1303
+ if (schedule.pattern.minute === "0") {
1172
1304
  return "every hour";
1173
1305
  }
1174
1306
  return strideFromSegments(
1175
- ir.analyses.segments.minute,
1307
+ segmentsOf(schedule, "minute"),
1176
1308
  "minute",
1177
1309
  "hour",
1178
1310
  opts
1179
1311
  ) ?? listPastThe(
1180
- segmentWords(ir.analyses.segments.minute, opts),
1312
+ segmentWords(segmentsOf(schedule, "minute"), opts),
1181
1313
  "minute",
1182
1314
  "hour",
1183
1315
  opts
1184
1316
  );
1185
1317
  }
1186
- function renderHourStep(ir, plan, opts) {
1187
- const cadence = unevenHourCadence(ir, opts);
1318
+ function renderHourStep(schedule, plan, opts) {
1319
+ const cadence = unevenHourCadence(schedule, opts);
1188
1320
  if (cadence !== null) {
1189
- return cadence + trailingQualifier(ir, opts);
1321
+ return cadence + trailingQualifier(schedule, opts);
1190
1322
  }
1191
- return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
1323
+ return stepHours(stepSegment(schedule, "hour"), opts) + trailingQualifier(schedule, opts);
1192
1324
  }
1193
1325
  function boundedWindow(plan) {
1194
- const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
1195
- return { from: plan.from, last, to: plan.to };
1326
+ const continuous = plan.minuteForm === "wildcard";
1327
+ const closeMinute = continuous ? plan.boundMinute ?? 0 : 0;
1328
+ return { from: plan.from, closeMinute, to: plan.to, continuous };
1196
1329
  }
1197
- function rangeWindow(from, to, throughMinute, opts) {
1330
+ function rangeWindow(window, opts) {
1331
+ const { from, to, throughMinute, continuous } = window;
1198
1332
  const open = "from " + getTime({ hour: from, minute: 0 }, opts);
1199
1333
  if (opts.style.untilWindow && !opts.short && from !== to) {
1200
- return open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts);
1334
+ return continuous ? open + " until " + getTime({ hour: (to + 1) % 24, minute: 0 }, opts) : open + through(opts) + getTime({ hour: to, minute: 0 }, opts);
1201
1335
  }
1202
1336
  return open + through(opts) + getTime({ hour: to, minute: throughMinute }, opts);
1203
1337
  }
1204
1338
  function hourWindow(window, opts) {
1205
- return rangeWindow(window.from, window.to, window.last, opts);
1339
+ return rangeWindow({
1340
+ continuous: window.continuous,
1341
+ from: window.from,
1342
+ throughMinute: window.closeMinute,
1343
+ to: window.to
1344
+ }, opts);
1206
1345
  }
1207
- function renderClockTimes(ir, plan, opts) {
1208
- if (ir.shapes.minute === "single") {
1209
- const minute = +ir.pattern.minute;
1210
- const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
1346
+ function renderClockTimes(schedule, plan, opts) {
1347
+ if (schedule.shapes.minute === "single") {
1348
+ const minute = +schedule.pattern.minute;
1349
+ const cadence = hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
1211
1350
  if (cadence !== null) {
1212
1351
  return cadence;
1213
1352
  }
@@ -1221,93 +1360,94 @@ function renderClockTimes(ir, plan, opts) {
1221
1360
  plain
1222
1361
  }, opts);
1223
1362
  });
1224
- return interpretDayQualifier(ir, opts) + "at " + joinList(times, opts) + dayUnionTrail(ir, opts);
1363
+ return interpretDayQualifier(schedule, opts) + "at " + joinList(times, opts) + dayUnionTrail(schedule, opts);
1225
1364
  }
1226
- function dayUnionTrail(ir, opts) {
1227
- return isDayUnion(ir, opts) ? dayUnionCondition(ir, opts) : "";
1365
+ function dayUnionTrail(schedule, opts) {
1366
+ return isDayUnion(schedule, opts) ? dayUnionCondition(schedule, opts) : "";
1228
1367
  }
1229
- function renderCompactClockTimes(ir, plan, opts) {
1368
+ function renderCompactClockTimes(schedule, plan, opts) {
1230
1369
  if (plan.fold) {
1231
- const cadence2 = hourCadence(ir, +plan.minute, opts) ?? hourRangeCadence(ir, +plan.minute, opts);
1370
+ const cadence2 = hourCadence(schedule, +plan.minute, opts) ?? hourRangeCadence(schedule, +plan.minute, opts);
1232
1371
  if (cadence2 !== null) {
1233
1372
  return cadence2;
1234
1373
  }
1235
- const hasRange = ir.analyses.segments.hour.some(function range(segment) {
1374
+ const hasRange = segmentsOf(schedule, "hour").some(function range(segment) {
1236
1375
  return segment.kind === "range";
1237
1376
  });
1238
- if (hasRange && !ir.analyses.clockSecond) {
1239
- return foldedHourWindows(ir, plan, opts) + trailingQualifier(ir, opts);
1377
+ if (hasRange && !schedule.analyses.clockSecond) {
1378
+ return foldedHourWindows(schedule, plan, opts) + trailingQualifier(schedule, opts);
1240
1379
  }
1241
- const fold = { minute: plan.minute, second: ir.analyses.clockSecond };
1242
- return interpretDayQualifier(ir, opts) + "at " + hourSegmentTimes(ir, fold, true, opts) + dayUnionTrail(ir, opts);
1380
+ const fold = { minute: plan.minute, second: schedule.analyses.clockSecond };
1381
+ return interpretDayQualifier(schedule, opts) + "at " + hourSegmentTimes(schedule, fold, true, opts) + dayUnionTrail(schedule, opts);
1243
1382
  }
1244
1383
  const minuteLead = (
1245
1384
  // The non-fold branch is a minute list, which has segments. An
1246
1385
  // offset/uneven step enumerated to that list reads as a stride.
1247
- strideFromSegments(ir.analyses.segments.minute, "minute", "hour", opts) ?? listPastThe(
1248
- segmentWords(ir.analyses.segments.minute, opts),
1386
+ strideFromSegments(
1387
+ segmentsOf(schedule, "minute"),
1388
+ "minute",
1389
+ "hour",
1390
+ opts
1391
+ ) ?? listPastThe(
1392
+ segmentWords(segmentsOf(schedule, "minute"), opts),
1249
1393
  "minute",
1250
1394
  "hour",
1251
1395
  opts
1252
1396
  )
1253
1397
  );
1254
- const cadence = unevenHourCadence(ir, opts);
1255
- const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(ir, opts) : minuteLead + ", at " + hourSegmentTimes(ir, { minute: 0, second: null }, true, opts) + trailingQualifier(ir, opts);
1256
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
1398
+ const cadence = unevenHourCadence(schedule, opts);
1399
+ const phrase = cadence ? minuteLead + ", " + cadence + trailingQualifier(schedule, opts) : minuteLead + ", at " + hourSegmentTimes(schedule, { minute: 0, second: null }, true, opts) + trailingQualifier(schedule, opts);
1400
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
1257
1401
  }
1258
- function foldedHourWindows(ir, plan, opts) {
1402
+ function foldedHourWindows(schedule, plan, opts) {
1259
1403
  const minute = plan.minute;
1260
1404
  const windows = [];
1261
- const outliers = collectHourOutliers(ir);
1262
- const times = outliers.hours.map(function time(hour) {
1405
+ const times = collectHourOutliers(schedule).map(function time(hour) {
1263
1406
  return getTime({ hour, minute }, opts);
1264
1407
  });
1265
- ir.analyses.segments.hour.forEach(function classify(segment) {
1408
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
1266
1409
  if (segment.kind === "range") {
1267
- windows.push(rangeWindow(
1268
- +segment.bounds[0],
1269
- +segment.bounds[1],
1270
- minute,
1271
- opts
1272
- ));
1410
+ windows.push(rangeWindow({
1411
+ continuous: false,
1412
+ from: +segment.bounds[0],
1413
+ throughMinute: minute,
1414
+ to: +segment.bounds[1]
1415
+ }, opts));
1273
1416
  }
1274
1417
  });
1275
- const phrase = rangeMinuteLead(ir, opts) + " " + joinList(windows, opts);
1276
- return phrase + outlierTail(times, outliers.pureStrays, opts);
1418
+ const phrase = rangeMinuteLead(schedule, opts) + " " + joinList(windows, opts);
1419
+ return phrase + outlierTail(times, opts);
1277
1420
  }
1278
- function collectHourOutliers(ir) {
1421
+ function collectHourOutliers(schedule) {
1279
1422
  const hours = [];
1280
- let pureStrays = true;
1281
- ir.analyses.segments.hour.forEach(function classify(segment) {
1423
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
1282
1424
  if (segment.kind === "step") {
1283
1425
  hours.push(...segment.fires);
1284
- pureStrays = false;
1285
1426
  } else if (segment.kind !== "range") {
1286
1427
  hours.push(+segment.value);
1287
1428
  }
1288
1429
  });
1289
- return { hours, pureStrays };
1430
+ return hours;
1290
1431
  }
1291
- function outlierTail(times, pureStrays, opts) {
1432
+ function outlierTail(times, opts) {
1292
1433
  if (!times.length) {
1293
1434
  return "";
1294
1435
  }
1295
- const connector = pureStrays && opts.style.untilWindow && !opts.short ? " plus " : " and at ";
1296
- return connector + joinList(times, opts);
1436
+ return " and at " + joinList(times, opts);
1297
1437
  }
1298
1438
  function isCadenceField(token) {
1299
1439
  return token === "*" || token.startsWith("*/") && token.indexOf("-") === -1;
1300
1440
  }
1301
- function leadingCadence(ir, opts) {
1302
- const { second, minute } = ir.pattern;
1441
+ function leadingCadence(schedule, opts) {
1442
+ const { second, minute } = schedule.pattern;
1303
1443
  if (isCadenceField(second)) {
1304
- return { secondLead: true, text: secondsClause(ir, "minute", opts) };
1444
+ return { secondLead: true, text: secondsClause(schedule, "minute", opts) };
1305
1445
  }
1306
1446
  if (second === "0" && isCadenceField(minute)) {
1307
1447
  const text = minute === "*" ? "every minute" : (
1308
1448
  // A clean minute step's first segment is a step segment.
1309
1449
  stepCycle60(
1310
- ir.analyses.segments.minute[0],
1450
+ stepSegment(schedule, "minute"),
1311
1451
  "minute",
1312
1452
  "hour",
1313
1453
  opts
@@ -1317,19 +1457,19 @@ function leadingCadence(ir, opts) {
1317
1457
  }
1318
1458
  return null;
1319
1459
  }
1320
- function minuteConfinement(ir, opts) {
1321
- const minute = ir.pattern.minute;
1460
+ function minuteConfinement(schedule, opts) {
1461
+ const minute = schedule.pattern.minute;
1322
1462
  if (minute === "*") {
1323
1463
  return "";
1324
1464
  }
1325
1465
  if (isCadenceField(minute)) {
1326
1466
  return " of every other minute";
1327
1467
  }
1328
- const segments = ir.analyses.segments.minute;
1329
- if (ir.shapes.minute === "single") {
1468
+ const segments = segmentsOf(schedule, "minute");
1469
+ if (schedule.shapes.minute === "single") {
1330
1470
  return " during minute :" + pad(minute);
1331
1471
  }
1332
- if (ir.shapes.minute === "range") {
1472
+ if (schedule.shapes.minute === "range") {
1333
1473
  const bounds = minute.split("-");
1334
1474
  return " during minutes :" + pad(bounds[0]) + through(opts) + ":" + pad(bounds[1]);
1335
1475
  }
@@ -1338,59 +1478,61 @@ function minuteConfinement(ir, opts) {
1338
1478
  });
1339
1479
  return " during minutes " + joinList(values, opts);
1340
1480
  }
1341
- function hourConfinement(ir, opts) {
1342
- const hour = ir.pattern.hour;
1481
+ function hourConfinement(schedule, opts) {
1482
+ const hour = schedule.pattern.hour;
1343
1483
  if (hour === "*") {
1344
- const minutePinned = ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute);
1484
+ const minutePinned = schedule.pattern.minute !== "*" && !isCadenceField(schedule.pattern.minute);
1345
1485
  return minutePinned ? " of every hour" : "";
1346
1486
  }
1347
1487
  if (isCadenceField(hour)) {
1348
1488
  return hour === "*/2" ? " of every other hour" : "";
1349
1489
  }
1350
- if (ir.shapes.hour === "single") {
1490
+ if (schedule.shapes.hour === "single") {
1351
1491
  const h = +hour;
1352
- if (ir.shapes.minute === "step") {
1492
+ if (schedule.shapes.minute === "step") {
1353
1493
  return " from " + getTime({ hour: h, minute: 0 }, opts) + " until " + getTime({ hour: (h + 1) % 24, minute: 0 }, opts);
1354
1494
  }
1355
- if (ir.pattern.minute !== "*" && !isCadenceField(ir.pattern.minute)) {
1495
+ if (schedule.pattern.minute !== "*" && !isCadenceField(schedule.pattern.minute)) {
1356
1496
  return " at " + getTime({ hour: h, minute: 0 }, opts);
1357
1497
  }
1358
1498
  return " of the " + getTime({ hour: h, minute: 0 }, opts) + " hour";
1359
1499
  }
1360
- if (ir.shapes.hour === "range") {
1500
+ if (schedule.shapes.hour === "range") {
1361
1501
  const bounds = hour.split("-");
1362
- return " " + rangeWindow(+bounds[0], +bounds[1], 0, opts);
1502
+ return " " + rangeWindow({
1503
+ continuous: schedule.pattern.minute === "*",
1504
+ from: +bounds[0],
1505
+ throughMinute: 0,
1506
+ to: +bounds[1]
1507
+ }, opts);
1363
1508
  }
1364
- return " during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours";
1509
+ return " during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours";
1365
1510
  }
1366
- function isContiguousHourRange(ir) {
1367
- return ir.shapes.hour === "range";
1368
- }
1369
- function confinableHour(ir) {
1370
- if (ir.shapes.hour !== "step") {
1511
+ function confinableHour(schedule) {
1512
+ if (schedule.shapes.hour !== "step") {
1371
1513
  return true;
1372
1514
  }
1373
- const segment = ir.analyses.segments.hour[0];
1374
- return ir.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
1515
+ const segment = stepSegment(schedule, "hour");
1516
+ return schedule.pattern.hour === "*/2" || segment.startToken.indexOf("-") !== -1;
1375
1517
  }
1376
- function isMinuteStride(ir) {
1377
- if (ir.shapes.minute !== "list") {
1518
+ function isMinuteStride(schedule) {
1519
+ if (schedule.shapes.minute !== "list") {
1378
1520
  return false;
1379
1521
  }
1380
- const values = singleValues(ir.analyses.segments.minute);
1522
+ const values = singleValues(segmentsOf(schedule, "minute"));
1381
1523
  return values !== null && arithmeticStep(values) !== null;
1382
1524
  }
1383
- function confinementEligible(ir, lead) {
1384
- const { minute, hour } = ir.pattern;
1525
+ function confinementEligible(schedule, lead) {
1526
+ const { minute, hour } = schedule.pattern;
1385
1527
  const minuteStep = isCadenceField(minute) && minute !== "*";
1386
- if (!confinableHour(ir)) {
1528
+ if (!confinableHour(schedule)) {
1387
1529
  return false;
1388
1530
  }
1389
1531
  if (lead.secondLead) {
1390
1532
  if (minuteStep) {
1391
- return minute === "*/2" && !isContiguousHourRange(ir);
1533
+ return minute === "*/2" && schedule.shapes.hour !== "range";
1392
1534
  }
1393
- if (isMinuteStride(ir) || ir.shapes.minute === "list" && ir.shapes.hour === "list") {
1535
+ if (isMinuteStride(schedule) || schedule.shapes.minute === "list" && schedule.shapes.hour === "list") {
1394
1536
  return false;
1395
1537
  }
1396
1538
  return true;
@@ -1398,21 +1540,21 @@ function confinementEligible(ir, lead) {
1398
1540
  if (hour === "*/2") {
1399
1541
  return true;
1400
1542
  }
1401
- return ir.shapes.hour === "single" && minute === "*/2";
1543
+ return schedule.shapes.hour === "single" && minute === "*/2";
1402
1544
  }
1403
- function confinement(ir, opts) {
1545
+ function confinement(schedule, opts) {
1404
1546
  if (!opts.style.untilWindow || opts.short) {
1405
1547
  return null;
1406
1548
  }
1407
- if (ir.pattern.minute === "*" && ir.pattern.hour === "*") {
1549
+ if (schedule.pattern.minute === "*" && schedule.pattern.hour === "*") {
1408
1550
  return null;
1409
1551
  }
1410
- const lead = leadingCadence(ir, opts);
1411
- if (!lead || !confinementEligible(ir, lead)) {
1552
+ const lead = leadingCadence(schedule, opts);
1553
+ if (!lead || !confinementEligible(schedule, lead)) {
1412
1554
  return null;
1413
1555
  }
1414
- const minutePart = lead.secondLead ? minuteConfinement(ir, opts) : "";
1415
- return lead.text + minutePart + hourConfinement(ir, opts) + trailingQualifier(ir, opts);
1556
+ const minutePart = lead.secondLead ? minuteConfinement(schedule, opts) : "";
1557
+ return lead.text + minutePart + hourConfinement(schedule, opts) + trailingQualifier(schedule, opts);
1416
1558
  }
1417
1559
  var renderers = {
1418
1560
  clockTimes: renderClockTimes,
@@ -1434,33 +1576,25 @@ var renderers = {
1434
1576
  singleMinute: renderSingleMinute,
1435
1577
  standaloneSeconds: renderStandaloneSeconds
1436
1578
  };
1437
- function renderStride(stride, opts) {
1579
+ function renderStride2(stride, opts) {
1438
1580
  const { interval, start, last, cycle, unit, anchor } = stride;
1439
1581
  const cadence = "every " + getNumber(interval, opts) + " " + unit + "s";
1440
- const tiles = cycle % interval === 0;
1441
- if (start === 0 && tiles) {
1442
- return cadence;
1443
- }
1444
- if (start < interval && tiles) {
1445
- return cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
1446
- }
1447
- const num = seriesNumber();
1448
- return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
1449
- }
1450
- function singleValues(segments) {
1451
- const values = [];
1452
- for (const segment of segments) {
1453
- if (segment.kind !== "single") {
1454
- return null;
1582
+ return renderStride({ start, interval, cycle }, {
1583
+ bare: () => cadence,
1584
+ // A clean wrap from a non-zero offset: name the start, no endpoint.
1585
+ offset: () => cadence + " from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor,
1586
+ // A bounded, non-wrapping set: pin both endpoints. Each bound is a value,
1587
+ // so it reads as a digit, matching the range idiom ("from 0 through 30").
1588
+ bounded: () => {
1589
+ const num = seriesNumber();
1590
+ return cadence + " from " + num(start) + through(opts) + num(last) + " " + pluralize(last, unit) + " past the " + anchor;
1455
1591
  }
1456
- values.push(+segment.value);
1457
- }
1458
- return values;
1592
+ });
1459
1593
  }
1460
1594
  function strideFromSegments(segments, unit, anchor, opts) {
1461
1595
  const values = singleValues(segments);
1462
1596
  const step = values && arithmeticStep(values);
1463
- return step ? renderStride({ ...step, cycle: 60, unit, anchor }, opts) : null;
1597
+ return step ? renderStride2({ ...step, cycle: 60, unit, anchor }, opts) : null;
1464
1598
  }
1465
1599
  function stepCycle60(segment, unit, anchor, opts) {
1466
1600
  if (segment.startToken.indexOf("-") !== -1) {
@@ -1470,7 +1604,7 @@ function stepCycle60(segment, unit, anchor, opts) {
1470
1604
  if (start !== 0 && segment.fires.length <= 3) {
1471
1605
  return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1472
1606
  }
1473
- return renderStride({
1607
+ return renderStride2({
1474
1608
  interval: segment.interval,
1475
1609
  start,
1476
1610
  last: segment.fires[segment.fires.length - 1],
@@ -1496,45 +1630,21 @@ function stepHours(segment, opts) {
1496
1630
  function hourStrideCadence(stride, opts) {
1497
1631
  const { start, interval, last } = stride;
1498
1632
  const cadence = "every " + getNumber(interval, opts) + " hours";
1499
- const tiles = 24 % interval === 0;
1500
- if (start === 0 && tiles) {
1501
- return cadence;
1502
- }
1503
- if (start < interval && tiles) {
1504
- return cadence + " from " + getTime({ hour: start, minute: 0 }, opts);
1505
- }
1506
- return cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts);
1507
- }
1508
- function offsetCleanStride(stride) {
1509
- return stride.start < stride.interval && 24 % stride.interval === 0;
1633
+ return renderStride({ start, interval, cycle: 24 }, {
1634
+ bare: () => cadence,
1635
+ offset: () => cadence + " from " + getTime({ hour: start, minute: 0 }, opts),
1636
+ bounded: () => cadence + " from " + getTime({ hour: start, minute: 0 }, opts) + through(opts) + getTime({ hour: last, minute: 0 }, opts)
1637
+ });
1510
1638
  }
1511
- function unevenHourCadence(ir, opts) {
1512
- const stride = hourStride(ir);
1639
+ function unevenHourCadence(schedule, opts) {
1640
+ const stride = hourStride(schedule);
1513
1641
  if (!stride || offsetCleanStride(stride)) {
1514
1642
  return null;
1515
1643
  }
1516
1644
  return hourStrideCadence(stride, opts);
1517
1645
  }
1518
- function hourListStride(values) {
1519
- if (values.length < 2) {
1520
- return null;
1521
- }
1522
- const interval = values[1] - values[0];
1523
- if (interval < 2) {
1524
- return null;
1525
- }
1526
- for (let i = 2; i < values.length; i += 1) {
1527
- if (values[i] - values[i - 1] !== interval) {
1528
- return null;
1529
- }
1530
- }
1531
- if (values[0] !== 0 && values.length < 5) {
1532
- return null;
1533
- }
1534
- return { interval, last: values[values.length - 1], start: values[0] };
1535
- }
1536
- function hourStride(ir) {
1537
- const segments = ir.analyses.segments.hour;
1646
+ function hourStride(schedule) {
1647
+ const segments = segmentsOf(schedule, "hour");
1538
1648
  if (segments.length === 1 && segments[0].kind === "step") {
1539
1649
  const segment = segments[0];
1540
1650
  if (segment.fires.length < 2) {
@@ -1546,83 +1656,83 @@ function hourStride(ir) {
1546
1656
  const values = singleValues(segments);
1547
1657
  return values && hourListStride(values);
1548
1658
  }
1549
- function subMinuteSecond(ir) {
1550
- return ir.pattern.second === "*" || ir.shapes.second === "step";
1659
+ function subMinuteSecond(schedule) {
1660
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
1551
1661
  }
1552
- function hourCadenceLead(ir, minute, opts) {
1662
+ function hourCadenceLead(schedule, minute, opts) {
1553
1663
  if (minute === 0) {
1554
- if (subMinuteSecond(ir)) {
1555
- return secondsClause(ir, "minute", opts) + " for one minute";
1664
+ if (subMinuteSecond(schedule)) {
1665
+ return secondsClause(schedule, "minute", opts) + " for one minute";
1556
1666
  }
1557
- return secondsClause(ir, "hour", opts);
1667
+ return secondsClause(schedule, "hour", opts);
1558
1668
  }
1559
1669
  const minutePhrase = getNumber(minute, opts) + " " + pluralize(minute, "minute") + " past the hour";
1560
- if (ir.pattern.second === "0") {
1670
+ if (schedule.pattern.second === "0") {
1561
1671
  return minutePhrase;
1562
1672
  }
1563
- return secondsClause(ir, "minute", opts) + ", " + minutePhrase;
1673
+ return secondsClause(schedule, "minute", opts) + ", " + minutePhrase;
1564
1674
  }
1565
- function hourCadence(ir, minute, opts) {
1566
- const stride = hourStride(ir);
1675
+ function hourCadence(schedule, minute, opts) {
1676
+ const stride = hourStride(schedule);
1567
1677
  if (!stride) {
1568
1678
  return null;
1569
1679
  }
1570
1680
  const fires = (stride.last - stride.start) / stride.interval + 1;
1571
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
1681
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
1572
1682
  return null;
1573
1683
  }
1574
- const minuteZeroStride = minute === 0 && subMinuteSecond(ir) && cleanStrideSegment(ir);
1684
+ const minuteZeroStride = minute === 0 && subMinuteSecond(schedule) && cleanStrideSegment(schedule);
1575
1685
  if (minuteZeroStride) {
1576
- return secondsClause(ir, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(ir, opts);
1686
+ return secondsClause(schedule, "minute", opts) + " for one minute " + everyNthHour(minuteZeroStride, opts) + trailingQualifier(schedule, opts);
1577
1687
  }
1578
- if (minute === 0 && ir.pattern.second === "0") {
1579
- return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1688
+ if (minute === 0 && schedule.pattern.second === "0") {
1689
+ return hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
1580
1690
  }
1581
- return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
1691
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
1582
1692
  }
1583
- function cleanStrideSegment(ir) {
1584
- const segments = ir.analyses.segments.hour;
1693
+ function cleanStrideSegment(schedule) {
1694
+ const segments = segmentsOf(schedule, "hour");
1585
1695
  const segment = segments.length === 1 && segments[0];
1586
1696
  if (!segment || segment.kind !== "step" || segment.startToken.indexOf("-") !== -1 || !(segment.interval in stepOrdinals)) {
1587
1697
  return null;
1588
1698
  }
1589
1699
  return segment;
1590
1700
  }
1591
- function hasHourWindow(ir) {
1592
- return ir.analyses.segments.hour.some(function range(segment) {
1701
+ function hasHourWindow(schedule) {
1702
+ return segmentsOf(schedule, "hour").some(function range(segment) {
1593
1703
  return segment.kind === "range";
1594
1704
  });
1595
1705
  }
1596
- function hourRangeWindowTail(ir, opts) {
1706
+ function hourRangeWindowTail(schedule, opts) {
1597
1707
  const windows = [];
1598
- const outliers = collectHourOutliers(ir);
1599
- ir.analyses.segments.hour.forEach(function classify(segment) {
1708
+ const outlierHours = collectHourOutliers(schedule);
1709
+ segmentsOf(schedule, "hour").forEach(function classify(segment) {
1600
1710
  if (segment.kind === "range") {
1601
- windows.push(rangeWindow(
1602
- +segment.bounds[0],
1603
- +segment.bounds[1],
1604
- 0,
1605
- opts
1606
- ));
1711
+ windows.push(rangeWindow({
1712
+ continuous: false,
1713
+ from: +segment.bounds[0],
1714
+ throughMinute: 0,
1715
+ to: +segment.bounds[1]
1716
+ }, opts));
1607
1717
  }
1608
1718
  });
1609
1719
  const phrase = "every hour " + joinList(windows, opts);
1610
- const times = outliers.hours.map(function time(hour) {
1720
+ const times = outlierHours.map(function time(hour) {
1611
1721
  return getTime({ hour, minute: 0 }, opts);
1612
1722
  });
1613
- return phrase + outlierTail(times, outliers.pureStrays, opts);
1723
+ return phrase + outlierTail(times, opts);
1614
1724
  }
1615
- function hourRangeCadence(ir, minute, opts) {
1616
- if (minute !== 0 || !hasHourWindow(ir)) {
1725
+ function hourRangeCadence(schedule, minute, opts) {
1726
+ if (minute !== 0 || !hasHourWindow(schedule)) {
1617
1727
  return null;
1618
1728
  }
1619
- if (ir.pattern.second === "0") {
1729
+ if (schedule.pattern.second === "0") {
1620
1730
  return null;
1621
1731
  }
1622
- if (subMinuteSecond(ir)) {
1623
- return secondsClause(ir, "minute", opts) + " for one minute during the " + hourSegmentTimes(ir, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(ir, opts);
1732
+ if (subMinuteSecond(schedule)) {
1733
+ return secondsClause(schedule, "minute", opts) + " for one minute during the " + hourSegmentTimes(schedule, { minute: 0, second: null }, false, opts) + " hours" + trailingQualifier(schedule, opts);
1624
1734
  }
1625
- return hourCadenceLead(ir, minute, opts) + ", " + hourRangeWindowTail(ir, opts) + trailingQualifier(ir, opts);
1735
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourRangeWindowTail(schedule, opts) + trailingQualifier(schedule, opts);
1626
1736
  }
1627
1737
  function seriesNumber() {
1628
1738
  return function format(n) {
@@ -1681,11 +1791,11 @@ function hourTimes(hours, opts) {
1681
1791
  function singleHourFire(times) {
1682
1792
  return times.kind === "fires" && times.fires.length === 1;
1683
1793
  }
1684
- function hourTimesFromPlan(ir, times, atContext, opts) {
1794
+ function hourTimesFromPlan(schedule, times, atContext, opts) {
1685
1795
  if (times.kind === "fires") {
1686
1796
  return hourTimes(times.fires, opts);
1687
1797
  }
1688
- return hourSegmentTimes(ir, { minute: 0, second: null }, atContext, opts);
1798
+ return hourSegmentTimes(schedule, { minute: 0, second: null }, atContext, opts);
1689
1799
  }
1690
1800
  function segmentHours(segment) {
1691
1801
  if (segment.kind === "range") {
@@ -1693,9 +1803,9 @@ function segmentHours(segment) {
1693
1803
  }
1694
1804
  return segment.kind === "step" ? segment.fires : [segment.value];
1695
1805
  }
1696
- function hourSegmentTimes(ir, fold, atContext, opts) {
1806
+ function hourSegmentTimes(schedule, fold, atContext, opts) {
1697
1807
  const { minute, second } = fold;
1698
- const segments = ir.analyses.segments.hour;
1808
+ const segments = segmentsOf(schedule, "hour");
1699
1809
  const plain = mixedTwelve(segments.flatMap(function entries(segment) {
1700
1810
  return segmentHours(segment).map(function entry(hour) {
1701
1811
  return { hour: +hour, minute, second };
@@ -1758,85 +1868,85 @@ var leadingWords = {
1758
1868
  stepDate: "",
1759
1869
  weekday: "every "
1760
1870
  };
1761
- function trailingQualifier(ir, opts) {
1762
- if (isDayUnion(ir, opts)) {
1763
- return dayUnionCondition(ir, opts);
1871
+ function trailingQualifier(schedule, opts) {
1872
+ if (isDayUnion(schedule, opts)) {
1873
+ return dayUnionCondition(schedule, opts);
1764
1874
  }
1765
- const phrase = dayQualifier(ir, trailingWords, opts);
1875
+ const phrase = dayQualifier(schedule, trailingWords, opts);
1766
1876
  return phrase && " " + phrase;
1767
1877
  }
1768
- function interpretDayQualifier(ir, opts) {
1769
- if (isDayUnion(ir, opts)) {
1878
+ function interpretDayQualifier(schedule, opts) {
1879
+ if (isDayUnion(schedule, opts)) {
1770
1880
  return "";
1771
1881
  }
1772
- return dayQualifier(ir, leadingWords, opts) + " ";
1882
+ return dayQualifier(schedule, leadingWords, opts) + " ";
1773
1883
  }
1774
- function dayQualifier(ir, words, opts) {
1775
- const pattern = ir.pattern;
1884
+ function dayQualifier(schedule, words, opts) {
1885
+ const pattern = schedule.pattern;
1776
1886
  if (pattern.date !== "*" && pattern.weekday !== "*") {
1777
- return dateOrWeekday(ir, opts);
1887
+ return dateOrWeekday(schedule, opts);
1778
1888
  }
1779
1889
  if (pattern.date !== "*") {
1780
- return datePhrase(ir, words, opts);
1890
+ return datePhrase(schedule, words, opts);
1781
1891
  }
1782
1892
  if (pattern.weekday !== "*") {
1783
1893
  const quartzWeekday = quartzWeekdayPhrase(pattern.weekday, opts);
1784
1894
  if (quartzWeekday) {
1785
- return monthScopeForRecurrence(quartzWeekday, ir, opts);
1895
+ return monthScopeForRecurrence(quartzWeekday, schedule, opts);
1786
1896
  }
1787
- const weekdays = words.weekday + weekdayPhrase(ir, words.recurringWeekday, opts);
1788
- return weekdays + monthScope(ir, opts);
1897
+ const weekdays = words.weekday + weekdayPhrase(schedule, words.recurringWeekday, opts);
1898
+ return weekdays + monthScope(schedule, opts);
1789
1899
  }
1790
1900
  if (pattern.month !== "*") {
1791
- return words.month + monthName(ir, opts);
1901
+ return words.month + monthName(schedule, opts);
1792
1902
  }
1793
1903
  return words.all;
1794
1904
  }
1795
- function datePhrase(ir, words, opts) {
1796
- const pattern = ir.pattern;
1905
+ function datePhrase(schedule, words, opts) {
1906
+ const pattern = schedule.pattern;
1797
1907
  const quartzDate = quartzDatePhrase(pattern.date, opts);
1798
1908
  if (quartzDate) {
1799
- return monthScopeForRecurrence(quartzDate, ir, opts);
1909
+ return monthScopeForRecurrence(quartzDate, schedule, opts);
1800
1910
  }
1801
1911
  if (isOpenStep(pattern.date)) {
1802
1912
  return monthScopeForRecurrence(
1803
1913
  words.stepDate + stepDates(pattern.date),
1804
- ir,
1914
+ schedule,
1805
1915
  opts
1806
1916
  );
1807
1917
  }
1808
- if (pattern.month !== "*" && !monthFoldsIntoDate(ir)) {
1809
- return "on the " + dateOrdinals(ir, opts) + monthScope(ir, opts);
1918
+ if (pattern.month !== "*" && !monthFoldsIntoDate(schedule)) {
1919
+ return "on the " + dateOrdinals(schedule, opts) + monthScope(schedule, opts);
1810
1920
  }
1811
1921
  if (pattern.month !== "*") {
1812
- return "on " + monthDatePhrase(ir, opts);
1922
+ return "on " + monthDatePhrase(schedule, opts);
1813
1923
  }
1814
- return "on the " + dateOrdinals(ir, opts);
1924
+ return "on the " + dateOrdinals(schedule, opts);
1815
1925
  }
1816
- function monthFoldsIntoDate(ir) {
1817
- return !oddEvenMonth(ir.pattern.month) && // Reached only with a restricted month, which has segments.
1818
- ir.analyses.segments.month.every(function flat(segment) {
1926
+ function monthFoldsIntoDate(schedule) {
1927
+ return !oddEvenMonth(schedule.pattern.month) && // Reached only with a restricted month, which has segments.
1928
+ segmentsOf(schedule, "month").every(function flat(segment) {
1819
1929
  return segment.kind !== "range";
1820
1930
  });
1821
1931
  }
1822
- function isDayUnion(ir, opts) {
1823
- return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
1932
+ function isDayUnion(schedule, opts) {
1933
+ return schedule.pattern.date !== "*" && schedule.pattern.weekday !== "*" && !!opts.style.untilWindow && !opts.short;
1824
1934
  }
1825
- function dayUnionCondition(ir, opts) {
1935
+ function dayUnionCondition(schedule, opts) {
1826
1936
  const pieces = [
1827
- ...dayUnionDatePieces(ir, opts),
1828
- ...dayUnionWeekdayPieces(ir, opts)
1937
+ ...dayUnionDatePieces(schedule, opts),
1938
+ ...dayUnionWeekdayPieces(schedule, opts)
1829
1939
  ];
1830
1940
  return " whenever the day is " + joinOr(pieces, opts);
1831
1941
  }
1832
- function dayUnionMonthLead(ir, opts) {
1833
- if (ir.pattern.month === "*") {
1942
+ function dayUnionMonthLead(schedule, opts) {
1943
+ if (schedule.pattern.month === "*") {
1834
1944
  return "";
1835
1945
  }
1836
- return "in " + monthName(ir, opts) + " ";
1946
+ return "in " + monthName(schedule, opts) + " ";
1837
1947
  }
1838
- function dayUnionDatePieces(ir, opts) {
1839
- const dateField = ir.pattern.date;
1948
+ function dayUnionDatePieces(schedule, opts) {
1949
+ const dateField = schedule.pattern.date;
1840
1950
  const quartz = quartzDatePhrase(dateField, opts);
1841
1951
  if (quartz) {
1842
1952
  return [quartz.replace(/^on /, "")];
@@ -1846,7 +1956,7 @@ function dayUnionDatePieces(ir, opts) {
1846
1956
  return [oddEven];
1847
1957
  }
1848
1958
  const pieces = [];
1849
- ir.analyses.segments.date.forEach(function expand(segment) {
1959
+ segmentsOf(schedule, "date").forEach(function expand(segment) {
1850
1960
  if (segment.kind === "range") {
1851
1961
  pieces.push("from the " + getOrdinal(segment.bounds[0]) + through(opts) + "the " + getOrdinal(segment.bounds[1]));
1852
1962
  } else if (segment.kind === "step") {
@@ -1859,14 +1969,14 @@ function dayUnionDatePieces(ir, opts) {
1859
1969
  });
1860
1970
  return pieces;
1861
1971
  }
1862
- function dayUnionWeekdayPieces(ir, opts) {
1863
- const weekdayField = ir.pattern.weekday;
1972
+ function dayUnionWeekdayPieces(schedule, opts) {
1973
+ const weekdayField = schedule.pattern.weekday;
1864
1974
  const quartz = quartzWeekdayPhrase(weekdayField, opts);
1865
1975
  if (quartz) {
1866
1976
  return [quartz.replace(/^on /, "")];
1867
1977
  }
1868
1978
  const pieces = [];
1869
- ir.analyses.segments.weekday.forEach(function expand(segment) {
1979
+ segmentsOf(schedule, "weekday").forEach(function expand(segment) {
1870
1980
  if (segment.kind === "range" && segment.bounds[0] === "1" && segment.bounds[1] === "5") {
1871
1981
  pieces.push("a weekday");
1872
1982
  } else if (segment.kind === "range") {
@@ -1894,16 +2004,16 @@ function oddEvenDay(dateField) {
1894
2004
  }
1895
2005
  return start === "2" ? "an even-numbered day" : null;
1896
2006
  }
1897
- function dateOrWeekday(ir, opts) {
1898
- const pattern = ir.pattern;
1899
- const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(ir, false, opts);
1900
- if (pattern.month !== "*" && monthFoldsIntoDate(ir) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
1901
- return "on " + monthDatePhrase(ir, opts) + " or " + weekdayPart + " in " + monthName(ir, opts);
2007
+ function dateOrWeekday(schedule, opts) {
2008
+ const pattern = schedule.pattern;
2009
+ const weekdayPart = quartzWeekdayPhrase(pattern.weekday, opts) || "on " + weekdayPhrase(schedule, false, opts);
2010
+ if (pattern.month !== "*" && monthFoldsIntoDate(schedule) && !quartzDatePhrase(pattern.date, opts) && !isOpenStep(pattern.date)) {
2011
+ return "on " + monthDatePhrase(schedule, opts) + " or " + weekdayPart + " in " + monthName(schedule, opts);
1902
2012
  }
1903
- return datePart(ir, opts) + " or " + weekdayPart + orMonthScope(ir, opts);
2013
+ return datePart(schedule, opts) + " or " + weekdayPart + orMonthScope(schedule, opts);
1904
2014
  }
1905
- function datePart(ir, opts) {
1906
- const pattern = ir.pattern;
2015
+ function datePart(schedule, opts) {
2016
+ const pattern = schedule.pattern;
1907
2017
  const quartzDate = quartzDatePhrase(pattern.date, opts);
1908
2018
  if (quartzDate) {
1909
2019
  return quartzDate;
@@ -1911,13 +2021,13 @@ function datePart(ir, opts) {
1911
2021
  if (isOpenStep(pattern.date)) {
1912
2022
  return stepDates(pattern.date);
1913
2023
  }
1914
- return "on the " + dateOrdinals(ir, opts);
2024
+ return "on the " + dateOrdinals(schedule, opts);
1915
2025
  }
1916
- function orMonthScope(ir, opts) {
1917
- if (ir.pattern.month === "*") {
2026
+ function orMonthScope(schedule, opts) {
2027
+ if (schedule.pattern.month === "*") {
1918
2028
  return "";
1919
2029
  }
1920
- return ", in " + monthName(ir, opts);
2030
+ return ", in " + monthName(schedule, opts);
1921
2031
  }
1922
2032
  function quartzDatePhrase(dateField, opts) {
1923
2033
  if (dateField === "L") {
@@ -1944,39 +2054,39 @@ function quartzWeekdayPhrase(weekdayField, opts) {
1944
2054
  return "on the last " + getWeekday(weekdayField.slice(0, -1), opts) + " of the month";
1945
2055
  }
1946
2056
  }
1947
- function monthDatePhrase(ir, opts) {
1948
- const month = monthName(ir, opts);
2057
+ function monthDatePhrase(schedule, opts) {
2058
+ const month = monthName(schedule, opts);
1949
2059
  const days = renderSegments(
1950
- ir.analyses.segments.date,
2060
+ segmentsOf(schedule, "date"),
1951
2061
  opts.style.ordinals ? getOrdinal : cardinalDay,
1952
2062
  opts
1953
2063
  );
1954
- if (opts.style.dayFirst && ir.shapes.date === "single" && ir.shapes.month !== "single") {
1955
- return "the " + getOrdinal(ir.pattern.date) + " of " + month;
2064
+ if (opts.style.dayFirst && schedule.shapes.date === "single" && schedule.shapes.month !== "single") {
2065
+ return "the " + getOrdinal(schedule.pattern.date) + " of " + month;
1956
2066
  }
1957
2067
  return opts.style.dayFirst ? days + " " + month : month + " " + days;
1958
2068
  }
1959
2069
  function cardinalDay(value) {
1960
2070
  return "" + value;
1961
2071
  }
1962
- function monthScope(ir, opts) {
1963
- if (ir.pattern.month === "*") {
2072
+ function monthScope(schedule, opts) {
2073
+ if (schedule.pattern.month === "*") {
1964
2074
  return "";
1965
2075
  }
1966
- return " in " + monthName(ir, opts);
2076
+ return " in " + monthName(schedule, opts);
1967
2077
  }
1968
- function monthScopeForRecurrence(phrase, ir, opts) {
1969
- if (ir.pattern.month === "*") {
2078
+ function monthScopeForRecurrence(phrase, schedule, opts) {
2079
+ if (schedule.pattern.month === "*") {
1970
2080
  return phrase;
1971
2081
  }
1972
2082
  const carriesRecurrence = phrase.indexOf(" of the month") !== -1;
1973
- if (carriesRecurrence && ir.shapes.month === "range") {
1974
- return phrase.replace(" of the month", " of each month") + " from " + monthName(ir, opts);
2083
+ if (carriesRecurrence && schedule.shapes.month === "range") {
2084
+ return phrase.replace(" of the month", " of each month") + " from " + monthName(schedule, opts);
1975
2085
  }
1976
- if (carriesRecurrence && (ir.shapes.month === "single" || ir.shapes.month === "step")) {
1977
- return phrase.replace(" of the month", "") + " in " + monthName(ir, opts);
2086
+ if (carriesRecurrence && (schedule.shapes.month === "single" || schedule.shapes.month === "step")) {
2087
+ return phrase.replace(" of the month", "") + " in " + monthName(schedule, opts);
1978
2088
  }
1979
- return phrase + " in " + monthName(ir, opts);
2089
+ return phrase + " in " + monthName(schedule, opts);
1980
2090
  }
1981
2091
  function stepDates(dateField) {
1982
2092
  const parts = dateField.split("/");
@@ -1989,15 +2099,15 @@ function stepDates(dateField) {
1989
2099
  }
1990
2100
  return phrase;
1991
2101
  }
1992
- function dateOrdinals(ir, opts) {
1993
- return renderSegments(ir.analyses.segments.date, getOrdinal, opts);
2102
+ function dateOrdinals(schedule, opts) {
2103
+ return renderSegments(segmentsOf(schedule, "date"), getOrdinal, opts);
1994
2104
  }
1995
- function monthName(ir, opts) {
1996
- const oddEven = oddEvenMonth(ir.pattern.month);
2105
+ function monthName(schedule, opts) {
2106
+ const oddEven = oddEvenMonth(schedule.pattern.month);
1997
2107
  if (oddEven) {
1998
2108
  return oddEven;
1999
2109
  }
2000
- return renderSegments(ir.analyses.segments.month, function name(value) {
2110
+ return renderSegments(segmentsOf(schedule, "month"), function name(value) {
2001
2111
  return getMonth(value, opts);
2002
2112
  }, opts);
2003
2113
  }
@@ -2014,8 +2124,8 @@ function oddEvenMonth(monthField) {
2014
2124
  }
2015
2125
  return start === "2" ? "every even-numbered month" : null;
2016
2126
  }
2017
- function weekdayPhrase(ir, recurring, opts) {
2018
- const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
2127
+ function weekdayPhrase(schedule, recurring, opts) {
2128
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
2019
2129
  const hasRange = segments.some(function range(segment) {
2020
2130
  return segment.kind === "range";
2021
2131
  });
@@ -2043,11 +2153,8 @@ function renderSegments(segments, word, opts) {
2043
2153
  });
2044
2154
  return joinList(pieces, opts);
2045
2155
  }
2046
- function isOpenStep(field) {
2047
- return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
2048
- }
2049
- function applyYear(description, ir, opts) {
2050
- const yearField = ir.pattern.year;
2156
+ function applyYear(description, schedule, opts) {
2157
+ const yearField = schedule.pattern.year;
2051
2158
  if (yearField === "*") {
2052
2159
  return description;
2053
2160
  }
@@ -2055,7 +2162,7 @@ function applyYear(description, ir, opts) {
2055
2162
  return description + ", " + stepYears(yearField, opts);
2056
2163
  }
2057
2164
  const label = yearLabel(yearField, opts);
2058
- if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && ir.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
2165
+ if (yearField.indexOf("-") === -1 && yearField.indexOf(",") === -1 && schedule.pattern.date !== "*" && description.indexOf(" at ") !== -1) {
2059
2166
  const yearGlue = opts.style.dayFirst ? " " : ", ";
2060
2167
  return description.replace(" at ", yearGlue + label + " at ");
2061
2168
  }
@@ -2178,9 +2285,9 @@ function interpretCronPattern(cronPattern, lang, opts) {
2178
2285
  if (typeof cronPattern === "string" && cronPattern.trim().toLowerCase() === "@reboot") {
2179
2286
  return lang.reboot;
2180
2287
  }
2181
- const ir = analyze(prepare(cronPattern, opts));
2182
- const plan = lang.strategy ? lang.strategy(ir, ir.plan) : ir.plan;
2183
- return lang.describe({ ...ir, plan }, opts);
2288
+ const schedule = analyze(prepare(cronPattern, opts));
2289
+ const plan = lang.plan ? lang.plan(schedule, schedule.plan) : schedule.plan;
2290
+ return lang.describe({ ...schedule, plan }, opts);
2184
2291
  }
2185
2292
  var cronli5_default = cronli5;
2186
2293
  export {