cronli5 0.2.1 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +4 -4
  3. package/cronli5.min.js +2 -2
  4. package/dist/cronli5.cjs +471 -383
  5. package/dist/cronli5.js +471 -383
  6. package/dist/lang/de.cjs +286 -215
  7. package/dist/lang/de.js +286 -215
  8. package/dist/lang/en.cjs +413 -327
  9. package/dist/lang/en.js +413 -327
  10. package/dist/lang/es.cjs +303 -265
  11. package/dist/lang/es.js +303 -265
  12. package/dist/lang/fi.cjs +311 -266
  13. package/dist/lang/fi.js +311 -266
  14. package/dist/lang/zh.cjs +320 -240
  15. package/dist/lang/zh.js +320 -240
  16. package/package.json +6 -6
  17. package/src/core/analyze.ts +12 -12
  18. package/src/core/cadence.ts +164 -0
  19. package/src/core/index.ts +3 -1
  20. package/src/core/normalize.ts +3 -3
  21. package/src/core/parse.ts +1 -1
  22. package/src/core/{ir.ts → schedule.ts} +17 -18
  23. package/src/core/specs.ts +1 -1
  24. package/src/core/util.ts +3 -165
  25. package/src/core/validate.ts +1 -1
  26. package/src/core/weekday.ts +54 -0
  27. package/src/cronli5.ts +5 -5
  28. package/src/lang/de/index.ts +329 -219
  29. package/src/lang/en/dialects.ts +1 -1
  30. package/src/lang/en/index.ts +521 -372
  31. package/src/lang/es/index.ts +338 -286
  32. package/src/lang/es/notes.md +1 -1
  33. package/src/lang/fi/dialects.ts +1 -1
  34. package/src/lang/fi/index.ts +365 -299
  35. package/src/lang/fi/notes.md +23 -8
  36. package/src/lang/fi/status.json +1 -1
  37. package/src/lang/zh/index.ts +386 -245
  38. package/src/types.ts +6 -6
  39. package/types/core/analyze.d.ts +3 -3
  40. package/types/core/cadence.d.ts +33 -0
  41. package/types/core/index.d.ts +3 -1
  42. package/types/core/normalize.d.ts +1 -1
  43. package/types/core/parse.d.ts +1 -1
  44. package/types/core/{ir.d.ts → schedule.d.ts} +11 -16
  45. package/types/core/specs.d.ts +1 -1
  46. package/types/core/util.d.ts +1 -30
  47. package/types/core/weekday.d.ts +10 -0
  48. package/types/lang/de/index.d.ts +1 -1
  49. package/types/lang/en/dialects.d.ts +1 -1
  50. package/types/lang/en/index.d.ts +1 -1
  51. package/types/lang/es/index.d.ts +1 -1
  52. package/types/lang/fi/dialects.d.ts +1 -1
  53. package/types/lang/fi/index.d.ts +1 -1
  54. package/types/lang/zh/index.d.ts +1 -1
  55. package/types/types.d.ts +5 -5
package/dist/lang/fi.cjs CHANGED
@@ -57,6 +57,16 @@ function isNonNegativeInteger(value) {
57
57
  const digits = /^\d+$/;
58
58
  return digits.test(value);
59
59
  }
60
+ function toFieldNumber(token, numberMap) {
61
+ return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
62
+ }
63
+
64
+ // src/core/shapes.ts
65
+ function isOpenStep(field) {
66
+ return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
67
+ }
68
+
69
+ // src/core/cadence.ts
60
70
  function arithmeticStep(values) {
61
71
  if (values.length < 5) {
62
72
  return null;
@@ -72,34 +82,11 @@ function arithmeticStep(values) {
72
82
  }
73
83
  return { start: values[0], interval, last: values[values.length - 1] };
74
84
  }
75
- function weekdayDisplayKey(value) {
76
- return value === 0 ? 7 : value;
77
- }
78
- function orderWeekdaysForDisplay(segments) {
79
- const flattened = segments.flatMap(function flat(segment) {
80
- return segment.kind === "step" ? segment.fires.map(function single(value) {
81
- return { kind: "single", value: "" + value };
82
- }) : [segment];
83
- });
84
- function key(segment) {
85
- return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
86
- }
87
- return flattened.map(function index(segment, position) {
88
- return [segment, position];
89
- }).sort(function byDisplayKey(a, b) {
90
- return key(a[0]) - key(b[0]) || a[1] - b[1];
91
- }).map(function unwrap(pair) {
92
- return pair[0];
93
- });
94
- }
95
- function toFieldNumber(token, numberMap) {
96
- return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
85
+ function segmentsOf(schedule, field) {
86
+ return schedule.analyses.segments[field] ?? [];
97
87
  }
98
- function segmentsOf(ir, field) {
99
- return ir.analyses.segments[field] ?? [];
100
- }
101
- function stepSegment(ir, field) {
102
- return segmentsOf(ir, field)[0];
88
+ function stepSegment(schedule, field) {
89
+ return segmentsOf(schedule, field)[0];
103
90
  }
104
91
  function singleValues(segments) {
105
92
  const values = [];
@@ -114,6 +101,17 @@ function singleValues(segments) {
114
101
  function offsetCleanStride(stride) {
115
102
  return stride.start < stride.interval && 24 % stride.interval === 0;
116
103
  }
104
+ function renderStride(spec, parts) {
105
+ const { start, interval, cycle } = spec;
106
+ const tiles = cycle % interval === 0;
107
+ if (start === 0 && tiles) {
108
+ return parts.bare();
109
+ }
110
+ if (start < interval && tiles) {
111
+ return parts.offset();
112
+ }
113
+ return parts.bounded();
114
+ }
117
115
  function hourListStride(values) {
118
116
  if (values.length < 2) {
119
117
  return null;
@@ -133,9 +131,26 @@ function hourListStride(values) {
133
131
  return { interval, last: values[values.length - 1], start: values[0] };
134
132
  }
135
133
 
136
- // src/core/shapes.ts
137
- function isOpenStep(field) {
138
- return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
134
+ // src/core/weekday.ts
135
+ function weekdayDisplayKey(value) {
136
+ return value === 0 ? 7 : value;
137
+ }
138
+ function orderWeekdaysForDisplay(segments) {
139
+ const flattened = segments.flatMap(function flat(segment) {
140
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
141
+ return { kind: "single", value: "" + value };
142
+ }) : [segment];
143
+ });
144
+ function key(segment) {
145
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
146
+ }
147
+ return flattened.map(function index(segment, position) {
148
+ return [segment, position];
149
+ }).sort(function byDisplayKey(a, b) {
150
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
151
+ }).map(function unwrap(pair) {
152
+ return pair[0];
153
+ });
139
154
  }
140
155
 
141
156
  // src/lang/fi/dialects.ts
@@ -292,94 +307,108 @@ function normalizeOptions(options) {
292
307
  years: !!options.years
293
308
  };
294
309
  }
295
- function restrictedMonthUnion(ir) {
296
- return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && ir.pattern.month !== "*";
310
+ function restrictedMonthUnion(schedule) {
311
+ return schedule.pattern.date !== "*" && schedule.pattern.weekday !== "*" && schedule.pattern.month !== "*";
297
312
  }
298
- function unionDateArm(ir) {
299
- return quartzDatePhrase(ir.pattern.date) || dateWords(ir) + " p\xE4iv\xE4n\xE4";
313
+ function oddDayUnion(dateField) {
314
+ if (!isOpenStep(dateField)) {
315
+ return null;
316
+ }
317
+ const [start, step] = dateField.split("/");
318
+ return (start === "*" || start === "1") && +step === 2 ? "kuukauden parittomina p\xE4ivin\xE4" : null;
319
+ }
320
+ function unionDateArm(schedule) {
321
+ return quartzDatePhrase(schedule.pattern.date) || oddDayUnion(schedule.pattern.date) || dateWords(schedule) + " p\xE4iv\xE4n\xE4";
300
322
  }
301
- function describe(ir, opts) {
302
- if (restrictedMonthUnion(ir)) {
303
- const timePart = render(ir, ir.plan, opts);
323
+ function unionWeekdayArm(schedule) {
324
+ const segments = segmentsOf(schedule, "weekday");
325
+ if (segments.length === 1 && segments[0].kind === "range" && segments[0].bounds[0] === "1" && segments[0].bounds[1] === "5") {
326
+ return "arkisin";
327
+ }
328
+ return weekdayQualifier(schedule);
329
+ }
330
+ function describe(schedule, opts) {
331
+ if (restrictedMonthUnion(schedule)) {
332
+ const timePart = render(schedule, schedule.plan, opts);
304
333
  return applyYear(
305
- monthPhrase(ir) + " " + timePart + " joko " + unionDateArm(ir) + " tai " + weekdayQualifier(ir),
306
- ir,
334
+ monthPhrase(schedule) + " " + timePart + " " + unionDateArm(schedule) + " tai " + unionWeekdayArm(schedule),
335
+ schedule,
307
336
  opts
308
337
  );
309
338
  }
310
- return applyYear(render(ir, ir.plan, opts), ir, opts);
339
+ return applyYear(render(schedule, schedule.plan, opts), schedule, opts);
311
340
  }
312
- function render(ir, plan, opts) {
341
+ function render(schedule, plan, opts) {
313
342
  const renderer = renderers[plan.kind];
314
- return renderer(ir, plan, opts);
343
+ return renderer(schedule, plan, opts);
315
344
  }
316
- function renderEverySecond(ir, plan, opts) {
317
- return "joka sekunti" + trailingQualifier(ir, opts);
345
+ function renderEverySecond(schedule, plan, opts) {
346
+ return "joka sekunti" + trailingQualifier(schedule, opts);
318
347
  }
319
- function renderStandaloneSeconds(ir, plan, opts) {
320
- return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
348
+ function renderStandaloneSeconds(schedule, plan, opts) {
349
+ return secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
321
350
  }
322
- function renderSecondPastMinute(ir, plan, opts) {
323
- return atMarks(ir.pattern.second, units.second, true) + trailingQualifier(ir, opts);
351
+ function renderSecondPastMinute(schedule, plan, opts) {
352
+ return atMarks(schedule.pattern.second, units.second, true) + trailingQualifier(schedule, opts);
324
353
  }
325
- function renderSecondsWithinMinute(ir, plan, opts) {
326
- const minuteField = ir.pattern.minute;
354
+ function renderSecondsWithinMinute(schedule, plan, opts) {
355
+ const minuteField = schedule.pattern.minute;
327
356
  if (plan.singleSecond) {
328
- return units.minute.mark + " " + minuteField + " " + units.minute.gen + " ja " + ir.pattern.second + " " + units.second.gen + " kohdalla" + trailingQualifier(ir, opts);
357
+ return units.minute.mark + " " + minuteField + " " + units.minute.gen + " ja " + schedule.pattern.second + " " + units.second.gen + " kohdalla" + trailingQualifier(schedule, opts);
329
358
  }
330
- return secondsLeadClause(ir, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(ir, opts);
359
+ return secondsLeadClause(schedule, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(schedule, opts);
331
360
  }
332
- function composeSecondsOverMinuteStep(ir, freq, opts) {
333
- const seg = stepSegment(ir, "minute");
361
+ function composeSecondsOverMinuteStep(schedule, freq, opts) {
362
+ const seg = stepSegment(schedule, "minute");
334
363
  const stepPhrase = stepCycle60(seg, units.minute, opts);
335
364
  if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
336
- const bareHours = kloFromTimes(ir, freq.hours.times, opts);
337
- return hoursFirstMinutes(bareHours, ir, opts) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
365
+ const bareHours = kloFromTimes(schedule, freq.hours.times, opts);
366
+ return hoursFirstMinutes(bareHours, schedule, opts) + ", " + secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
338
367
  }
339
368
  let hourClause = "";
340
369
  if (freq.hours.kind === "during") {
341
- hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
370
+ hourClause = " " + hourWindowsFromTimes(schedule, freq.hours.times, opts);
342
371
  } else if (freq.hours.kind === "window") {
343
372
  hourClause = " " + hourWindow(freq.hours, opts);
344
373
  } else if (freq.hours.kind === "step") {
345
- hourClause = " " + everyNthHour(stepSegment(ir, "hour"), opts);
374
+ hourClause = " " + everyNthHour(stepSegment(schedule, "hour"), opts);
346
375
  }
347
- return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
376
+ return stepPhrase + ", " + secondsLeadClause(schedule, opts) + hourClause + trailingQualifier(schedule, opts);
348
377
  }
349
- function composeHourCadence(ir, plan, opts) {
378
+ function composeHourCadence(schedule, plan, opts) {
350
379
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
351
- if (!clockRest || ir.shapes.minute !== "single") {
380
+ if (!clockRest || schedule.shapes.minute !== "single") {
352
381
  return null;
353
382
  }
354
- const minute = +ir.pattern.minute;
355
- return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
383
+ const minute = +schedule.pattern.minute;
384
+ return hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
356
385
  }
357
- function renderComposeSeconds(ir, plan, opts) {
358
- const cadence = composeHourCadence(ir, plan, opts);
386
+ function renderComposeSeconds(schedule, plan, opts) {
387
+ const cadence = composeHourCadence(schedule, plan, opts);
359
388
  if (cadence !== null) {
360
389
  return cadence;
361
390
  }
362
- if (plan.rest.kind === "minuteFrequency" && ir.pattern.second !== "*") {
363
- return composeSecondsOverMinuteStep(ir, plan.rest, opts);
391
+ if (plan.rest.kind === "minuteFrequency" && schedule.pattern.second !== "*") {
392
+ return composeSecondsOverMinuteStep(schedule, plan.rest, opts);
364
393
  }
365
394
  if (plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0)) {
366
- return composeMinuteZero(ir, plan.rest, opts);
395
+ return composeMinuteZero(schedule, plan.rest, opts);
367
396
  }
368
- if (isEveryOtherMinuteSeconds(ir, plan)) {
369
- return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
397
+ if (isEveryOtherMinuteSeconds(schedule, plan)) {
398
+ return secondsLeadClause(schedule, opts) + " joka toisena minuuttina";
370
399
  }
371
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
372
- const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
373
- return lead + render(ir, plan.rest, opts);
400
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && schedule.analyses.clockSecond;
401
+ const lead = restOwnsLead ? "" : secondsLeadClause(schedule, opts) + ", ";
402
+ return lead + render(schedule, plan.rest, opts);
374
403
  }
375
- function isEveryOtherMinuteSeconds(ir, plan) {
376
- if (plan.rest.kind !== "minuteFrequency" || ir.pattern.second !== "*" || ir.shapes.hour !== "wildcard") {
404
+ function isEveryOtherMinuteSeconds(schedule, plan) {
405
+ if (plan.rest.kind !== "minuteFrequency" || schedule.pattern.second !== "*" || schedule.shapes.hour !== "wildcard") {
377
406
  return false;
378
407
  }
379
- const seg = stepSegment(ir, "minute");
408
+ const seg = stepSegment(schedule, "minute");
380
409
  return seg.startToken === "*" && seg.interval === 2;
381
410
  }
382
- function composeMinuteZero(ir, rest, opts) {
411
+ function composeMinuteZero(schedule, rest, opts) {
383
412
  const clocks = rest.times.map(function clock(time) {
384
413
  return clockDigits(
385
414
  { hour: time.hour, minute: time.minute },
@@ -387,54 +416,66 @@ function composeMinuteZero(ir, rest, opts) {
387
416
  );
388
417
  });
389
418
  const frame = clocks.length === 1 ? "minuutin " + clocks[0] : "minuuttien " + joinList(clocks);
390
- const dayTrail = leadingQualifier(ir, opts).trimEnd();
391
- return secondsLeadClause(ir, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
419
+ const dayTrail = leadingQualifier(schedule, opts).trimEnd();
420
+ return secondsLeadClause(schedule, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
392
421
  }
393
- function secondsLeadClause(ir, opts) {
394
- const secondField = ir.pattern.second;
395
- const shape = ir.shapes.second;
422
+ function secondsLeadClause(schedule, opts) {
423
+ const secondField = schedule.pattern.second;
424
+ const shape = schedule.shapes.second;
396
425
  if (secondField === "*") {
397
426
  return "joka sekunti";
398
427
  }
399
428
  if (shape === "step") {
400
429
  return stepCycle60(
401
- stepSegment(ir, "second"),
430
+ stepSegment(schedule, "second"),
402
431
  units.second,
403
432
  opts
404
433
  );
405
434
  }
406
- const marked = ir.pattern.minute === "*";
435
+ const marked = schedule.pattern.minute === "*";
407
436
  if (shape === "single") {
408
437
  return atMarks(secondField, units.second, marked);
409
438
  }
410
- return strideFromSegments(segmentsOf(ir, "second"), units.second, opts) ?? atMarks(
411
- joinList(segmentWords(segmentsOf(ir, "second"))),
439
+ return strideFromSegments(
440
+ segmentsOf(schedule, "second"),
441
+ units.second,
442
+ opts
443
+ ) ?? atMarks(
444
+ joinList(segmentWords(segmentsOf(schedule, "second"))),
412
445
  units.second,
413
446
  marked
414
447
  );
415
448
  }
416
- function renderEveryMinute(ir, plan, opts) {
417
- return "joka minuutti" + trailingQualifier(ir, opts);
449
+ function renderEveryMinute(schedule, plan, opts) {
450
+ return "joka minuutti" + trailingQualifier(schedule, opts);
418
451
  }
419
- function renderSingleMinute(ir, plan, opts) {
420
- return atMarks(ir.pattern.minute, units.minute, true) + trailingQualifier(ir, opts);
452
+ function renderSingleMinute(schedule, plan, opts) {
453
+ return atMarks(schedule.pattern.minute, units.minute, true) + trailingQualifier(schedule, opts);
421
454
  }
422
- function renderRangeOfMinutes(ir, plan, opts) {
423
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
455
+ function renderRangeOfMinutes(schedule, plan, opts) {
456
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
424
457
  }
425
- function renderMultipleMinutes(ir, plan, opts) {
426
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
458
+ function renderMultipleMinutes(schedule, plan, opts) {
459
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
427
460
  }
428
- function minutesList(ir, opts) {
429
- return strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts) ?? atMarks(
430
- joinList(segmentWords(segmentsOf(ir, "minute"))),
461
+ function minutesList(schedule, opts) {
462
+ return strideFromSegments(
463
+ segmentsOf(schedule, "minute"),
464
+ units.minute,
465
+ opts
466
+ ) ?? atMarks(
467
+ joinList(segmentWords(segmentsOf(schedule, "minute"))),
431
468
  units.minute,
432
469
  true
433
470
  );
434
471
  }
435
- function bareMinutes(ir, opts) {
436
- return strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts) ?? atMarks(
437
- joinList(segmentWords(segmentsOf(ir, "minute"))),
472
+ function bareMinutes(schedule, opts) {
473
+ return strideFromSegments(
474
+ segmentsOf(schedule, "minute"),
475
+ units.minute,
476
+ opts
477
+ ) ?? atMarks(
478
+ joinList(segmentWords(segmentsOf(schedule, "minute"))),
438
479
  units.minute,
439
480
  false
440
481
  );
@@ -464,16 +505,16 @@ function hoursAreRangeIsolated(segments) {
464
505
  }
465
506
  return hasRange && hasSingle;
466
507
  }
467
- function hoursFirstMinutes(hoursStr, ir, opts) {
468
- const stride = strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts);
508
+ function hoursFirstMinutes(hoursStr, schedule, opts) {
509
+ const stride = strideFromSegments(segmentsOf(schedule, "minute"), units.minute, opts);
469
510
  if (stride) {
470
511
  return hoursStr + " aina " + stride;
471
512
  }
472
- return hoursStr + " aina minuuttien " + joinList(segmentWords(segmentsOf(ir, "minute"))) + " kohdalla";
513
+ return hoursStr + " aina minuuttien " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " kohdalla";
473
514
  }
474
- function hourSegmentTimesWithSeka(ir, minute, second, opts) {
515
+ function hourSegmentTimesWithSeka(schedule, minute, second, opts) {
475
516
  const pieces = [];
476
- segmentsOf(ir, "hour").forEach(function clock(segment) {
517
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
477
518
  if (segment.kind === "range") {
478
519
  pieces.push(rangeDigits(
479
520
  { hour: +segment.bounds[0], minute, second },
@@ -486,61 +527,61 @@ function hourSegmentTimesWithSeka(ir, minute, second, opts) {
486
527
  });
487
528
  return "klo " + pieces.slice(0, -1).join(", ") + " sek\xE4 klo " + pieces[pieces.length - 1];
488
529
  }
489
- function renderMinuteFrequency(ir, plan, opts) {
490
- const seg = stepSegment(ir, "minute");
530
+ function renderMinuteFrequency(schedule, plan, opts) {
531
+ const seg = stepSegment(schedule, "minute");
491
532
  if (plan.hours.kind === "during") {
492
- const cadence = unevenHourCadence(ir, opts);
533
+ const cadence = unevenHourCadence(schedule, opts);
493
534
  if (cadence !== null) {
494
- return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(ir, opts);
535
+ return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(schedule, opts);
495
536
  }
496
537
  if (minuteStepIsAnchored(seg)) {
497
- const bareHours = kloFromTimes(ir, plan.hours.times, opts);
498
- return hoursFirstMinutes(bareHours, ir, opts) + trailingQualifier(ir, opts);
538
+ const bareHours = kloFromTimes(schedule, plan.hours.times, opts);
539
+ return hoursFirstMinutes(bareHours, schedule, opts) + trailingQualifier(schedule, opts);
499
540
  }
500
- return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(ir, plan.hours.times, opts) + trailingQualifier(ir, opts);
541
+ return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(schedule, plan.hours.times, opts) + trailingQualifier(schedule, opts);
501
542
  }
502
543
  let phrase = stepCycle60(seg, units.minute, opts);
503
544
  if (plan.hours.kind === "window") {
504
545
  phrase += " " + hourWindow(plan.hours, opts);
505
546
  } else if (plan.hours.kind === "step") {
506
- phrase += " " + everyNthHour(stepSegment(ir, "hour"), opts);
547
+ phrase += " " + everyNthHour(stepSegment(schedule, "hour"), opts);
507
548
  }
508
- return phrase + trailingQualifier(ir, opts);
549
+ return phrase + trailingQualifier(schedule, opts);
509
550
  }
510
- function renderMinuteSpanInHour(ir, plan, opts) {
511
- if (ir.pattern.minute === "*") {
512
- return "joka minuutti kello " + plan.hour + " aikana" + trailingQualifier(ir, opts);
551
+ function renderMinuteSpanInHour(schedule, plan, opts) {
552
+ if (schedule.pattern.minute === "*") {
553
+ return "joka minuutti kello " + plan.hour + " aikana" + trailingQualifier(schedule, opts);
513
554
  }
514
555
  return "joka minuutti " + kloRange(
515
556
  { hour: plan.hour, minute: plan.span[0] },
516
557
  { hour: plan.hour, minute: plan.span[1] },
517
558
  opts
518
- ) + trailingQualifier(ir, opts);
559
+ ) + trailingQualifier(schedule, opts);
519
560
  }
520
- function renderMinutesAcrossHours(ir, plan, opts) {
521
- const cadence = unevenHourCadence(ir, opts);
561
+ function renderMinutesAcrossHours(schedule, plan, opts) {
562
+ const cadence = unevenHourCadence(schedule, opts);
522
563
  if (plan.form === "wildcard") {
523
- return cadence ? "joka minuutti, " + cadence + trailingQualifier(ir, opts) : "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
564
+ return cadence ? "joka minuutti, " + cadence + trailingQualifier(schedule, opts) : "joka minuutti " + hourWindowsFromTimes(schedule, plan.times, opts) + trailingQualifier(schedule, opts);
524
565
  }
525
566
  if (cadence !== null) {
526
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
567
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
527
568
  }
528
- if (hoursAreRangeIsolated(segmentsOf(ir, "hour"))) {
529
- return bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
569
+ if (hoursAreRangeIsolated(segmentsOf(schedule, "hour"))) {
570
+ return bareMinutes(schedule, opts) + " " + hourSegmentTimesWithSeka(schedule, 0, null, opts) + trailingQualifier(schedule, opts);
530
571
  }
531
- const hoursStr = kloFromTimes(ir, plan.times, opts);
532
- return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
572
+ const hoursStr = kloFromTimes(schedule, plan.times, opts);
573
+ return hoursFirstMinutes(hoursStr, schedule, opts) + trailingQualifier(schedule, opts);
533
574
  }
534
- function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
535
- const segment = stepSegment(ir, "hour");
536
- const cadence = unevenHourCadence(ir, opts);
575
+ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
576
+ const segment = stepSegment(schedule, "hour");
577
+ const cadence = unevenHourCadence(schedule, opts);
537
578
  if (plan.form === "wildcard") {
538
- return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
579
+ return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
539
580
  }
540
581
  if (cadence !== null) {
541
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
582
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
542
583
  }
543
- return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
584
+ return bareMinutes(schedule, opts) + hourStepTail(segment, opts) + trailingQualifier(schedule, opts);
544
585
  }
545
586
  function plainHourStep(segment) {
546
587
  return segment.startToken === "*" && 24 % segment.interval === 0;
@@ -589,35 +630,35 @@ function hourStepTail(segment, opts) {
589
630
  const sep = plainHourStep(segment) ? " " : ", ";
590
631
  return sep + plainOrFullHourStep(segment, opts);
591
632
  }
592
- function renderEveryHour(ir, plan, opts) {
593
- return "joka tunti" + trailingQualifier(ir, opts);
633
+ function renderEveryHour(schedule, plan, opts) {
634
+ return "joka tunti" + trailingQualifier(schedule, opts);
594
635
  }
595
- function renderHourRange(ir, plan, opts) {
636
+ function renderHourRange(schedule, plan, opts) {
596
637
  const window = hourWindow(boundedWindow(plan), opts);
597
638
  if (plan.minuteForm === "wildcard") {
598
- return "joka minuutti " + window + trailingQualifier(ir, opts);
639
+ return "joka minuutti " + window + trailingQualifier(schedule, opts);
599
640
  }
600
641
  if (plan.minuteForm === "range") {
601
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
642
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
602
643
  }
603
- if (ir.pattern.minute === "0") {
604
- return "joka tunti " + window + trailingQualifier(ir, opts);
644
+ if (schedule.pattern.minute === "0") {
645
+ return "joka tunti " + window + trailingQualifier(schedule, opts);
605
646
  }
606
- if (ir.shapes.minute === "single") {
607
- return atMarks(ir.pattern.minute, units.minute, false) + " " + kloRange(
608
- { hour: plan.from, minute: +ir.pattern.minute },
647
+ if (schedule.shapes.minute === "single") {
648
+ return atMarks(schedule.pattern.minute, units.minute, false) + " " + kloRange(
649
+ { hour: plan.from, minute: +schedule.pattern.minute },
609
650
  { hour: plan.to, minute: plan.last },
610
651
  opts
611
- ) + trailingQualifier(ir, opts);
652
+ ) + trailingQualifier(schedule, opts);
612
653
  }
613
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
654
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
614
655
  }
615
- function renderHourStep(ir, plan, opts) {
616
- const cadence = unevenHourCadence(ir, opts);
656
+ function renderHourStep(schedule, plan, opts) {
657
+ const cadence = unevenHourCadence(schedule, opts);
617
658
  if (cadence !== null) {
618
- return cadence + trailingQualifier(ir, opts);
659
+ return cadence + trailingQualifier(schedule, opts);
619
660
  }
620
- return stepHours(stepSegment(ir, "hour"), opts) + trailingQualifier(ir, opts);
661
+ return stepHours(stepSegment(schedule, "hour"), opts) + trailingQualifier(schedule, opts);
621
662
  }
622
663
  function boundedWindow(plan) {
623
664
  return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
@@ -629,53 +670,62 @@ function hourWindow(window, opts) {
629
670
  opts
630
671
  );
631
672
  }
632
- function renderClockTimes(ir, plan, opts) {
633
- if (ir.shapes.minute === "single") {
634
- const minute = +ir.pattern.minute;
635
- const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
673
+ function renderClockTimes(schedule, plan, opts) {
674
+ if (schedule.shapes.minute === "single") {
675
+ const minute = +schedule.pattern.minute;
676
+ const cadence = hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
636
677
  if (cadence !== null) {
637
678
  return cadence;
638
679
  }
639
680
  }
640
681
  if (plan.times.length === 1) {
641
682
  const time = plan.times[0];
642
- return leadingQualifier(ir, opts) + timeWord(time.hour, time.minute, time.second, opts);
683
+ return leadingQualifier(schedule, opts) + timeWord(time.hour, time.minute, time.second, opts);
643
684
  }
644
685
  const digits = plan.times.map(function clock(time) {
645
686
  return timeDigits(time.hour, time.minute, time.second, opts);
646
687
  });
647
- return leadingQualifier(ir, opts) + "klo " + joinList(digits);
688
+ return leadingQualifier(schedule, opts) + "klo " + joinList(digits);
648
689
  }
649
- function renderCompactClockTimes(ir, plan, opts) {
690
+ function renderCompactClockTimes(schedule, plan, opts) {
650
691
  if (plan.fold) {
651
- const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
692
+ const cadence2 = hourCadence(schedule, plan.minute, opts) ?? hourRangeCadence(schedule, plan.minute, opts);
652
693
  if (cadence2 !== null) {
653
694
  return cadence2;
654
695
  }
655
696
  }
656
- const hourSegs = segmentsOf(ir, "hour");
697
+ const hourSegs = segmentsOf(schedule, "hour");
657
698
  if (hoursAreRangeIsolated(hourSegs)) {
658
699
  if (plan.fold) {
659
- return leadingQualifier(ir, opts) + hourSegmentTimesWithSeka(
660
- ir,
700
+ return leadingQualifier(schedule, opts) + hourSegmentTimesWithSeka(
701
+ schedule,
661
702
  plan.minute,
662
- ir.analyses.clockSecond,
703
+ schedule.analyses.clockSecond,
663
704
  opts
664
705
  );
665
706
  }
666
- const phrase2 = bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
667
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase2 : phrase2;
707
+ const phrase2 = bareMinutes(schedule, opts) + " " + hourSegmentTimesWithSeka(schedule, 0, null, opts) + trailingQualifier(schedule, opts);
708
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase2 : phrase2;
668
709
  }
669
710
  if (plan.fold) {
670
- return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
711
+ return leadingQualifier(schedule, opts) + hourSegmentTimes(
712
+ schedule,
713
+ plan.minute,
714
+ schedule.analyses.clockSecond,
715
+ opts
716
+ );
671
717
  }
672
- const cadence = unevenHourCadence(ir, opts);
673
- const phrase = cadence ? bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : (
718
+ const cadence = unevenHourCadence(schedule, opts);
719
+ const phrase = cadence ? bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts) : (
674
720
  // A minute list over purely enumerated hours (step fires, all singles) —
675
721
  // hours-first, drop "joka tunti".
676
- hoursFirstMinutes(hourSegmentTimes(ir, 0, null, opts), ir, opts) + trailingQualifier(ir, opts)
722
+ hoursFirstMinutes(
723
+ hourSegmentTimes(schedule, 0, null, opts),
724
+ schedule,
725
+ opts
726
+ ) + trailingQualifier(schedule, opts)
677
727
  );
678
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
728
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
679
729
  }
680
730
  var renderers = {
681
731
  clockTimes: renderClockTimes,
@@ -697,22 +747,19 @@ var renderers = {
697
747
  singleMinute: renderSingleMinute,
698
748
  standaloneSeconds: renderStandaloneSeconds
699
749
  };
700
- function renderStride(stride, opts) {
750
+ function renderStride2(stride, opts) {
701
751
  const { interval, start, last, cycle, unit } = stride;
702
752
  const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
703
- const tiles = cycle % interval === 0;
704
- if (start === 0 && tiles) {
705
- return cadence;
706
- }
707
- if (start < interval && tiles) {
708
- return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
709
- }
710
- return cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last;
753
+ return renderStride({ start, interval, cycle }, {
754
+ bare: () => cadence,
755
+ offset: () => cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen",
756
+ bounded: () => cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last
757
+ });
711
758
  }
712
759
  function strideFromSegments(segments, unit, opts) {
713
760
  const values = singleValues(segments);
714
761
  const step = values && arithmeticStep(values);
715
- return step ? renderStride({ ...step, cycle: 60, unit }, opts) : null;
762
+ return step ? renderStride2({ ...step, cycle: 60, unit }, opts) : null;
716
763
  }
717
764
  function stepCycle60(segment, unit, opts) {
718
765
  if (segment.startToken.indexOf("-") !== -1) {
@@ -722,7 +769,7 @@ function stepCycle60(segment, unit, opts) {
722
769
  if (start !== 0 && segment.fires.length <= 3) {
723
770
  return atMarks(joinList(wordList(segment.fires)), unit, true);
724
771
  }
725
- return renderStride({
772
+ return renderStride2({
726
773
  interval: segment.interval,
727
774
  start,
728
775
  last: segment.fires[segment.fires.length - 1],
@@ -748,17 +795,14 @@ function stepHours(segment, opts) {
748
795
  function hourStrideCadence(stride, opts) {
749
796
  const { start, interval, last } = stride;
750
797
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
751
- const tiles = 24 % interval === 0;
752
- if (start === 0 && tiles) {
753
- return cadence;
754
- }
755
- if (start < interval && tiles) {
756
- return cadence + " klo " + hourElatives[start] + " alkaen";
757
- }
758
- return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
798
+ return renderStride({ start, interval, cycle: 24 }, {
799
+ bare: () => cadence,
800
+ offset: () => cadence + " klo " + hourElatives[start] + " alkaen",
801
+ bounded: () => cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts)
802
+ });
759
803
  }
760
- function hourStride(ir) {
761
- const segments = ir.analyses.segments.hour;
804
+ function hourStride(schedule) {
805
+ const segments = schedule.analyses.segments.hour;
762
806
  if (!segments) {
763
807
  return null;
764
808
  }
@@ -773,47 +817,47 @@ function hourStride(ir) {
773
817
  const values = singleValues(segments);
774
818
  return values && hourListStride(values);
775
819
  }
776
- function unevenHourCadence(ir, opts) {
777
- const stride = hourStride(ir);
820
+ function unevenHourCadence(schedule, opts) {
821
+ const stride = hourStride(schedule);
778
822
  if (!stride || offsetCleanStride(stride)) {
779
823
  return null;
780
824
  }
781
825
  return hourStrideCadence(stride, opts);
782
826
  }
783
- function subMinuteSecond(ir) {
784
- return ir.pattern.second === "*" || ir.shapes.second === "step";
827
+ function subMinuteSecond(schedule) {
828
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
785
829
  }
786
- function hourCadenceLead(ir, minute, opts) {
830
+ function hourCadenceLead(schedule, minute, opts) {
787
831
  if (minute === 0) {
788
- if (subMinuteSecond(ir)) {
789
- return secondsLeadClause(ir, opts) + " minuutin ajan";
832
+ if (subMinuteSecond(schedule)) {
833
+ return secondsLeadClause(schedule, opts) + " minuutin ajan";
790
834
  }
791
- return secondsLeadClause(ir, opts);
835
+ return secondsLeadClause(schedule, opts);
792
836
  }
793
837
  const minutePhrase = atMarks(String(minute), units.minute, false);
794
- if (ir.pattern.second === "0") {
838
+ if (schedule.pattern.second === "0") {
795
839
  return minutePhrase;
796
840
  }
797
- return secondsLeadClause(ir, opts) + ", " + minutePhrase;
841
+ return secondsLeadClause(schedule, opts) + ", " + minutePhrase;
798
842
  }
799
- function hourCadence(ir, minute, opts) {
800
- const stride = hourStride(ir);
843
+ function hourCadence(schedule, minute, opts) {
844
+ const stride = hourStride(schedule);
801
845
  if (!stride) {
802
846
  return null;
803
847
  }
804
848
  const fires = (stride.last - stride.start) / stride.interval + 1;
805
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
849
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
806
850
  return null;
807
851
  }
808
- const segment = segmentsOf(ir, "hour")[0];
809
- const confined = minute === 0 && subMinuteSecond(ir) && segmentsOf(ir, "hour").length === 1 && segment.kind === "step" && cleanHourStride(segment);
852
+ const segment = segmentsOf(schedule, "hour")[0];
853
+ const confined = minute === 0 && subMinuteSecond(schedule) && segmentsOf(schedule, "hour").length === 1 && segment.kind === "step" && cleanHourStride(segment);
810
854
  if (confined) {
811
- return secondsLeadClause(ir, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
855
+ return secondsLeadClause(schedule, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
812
856
  }
813
- if (minute === 0 && ir.pattern.second === "0") {
814
- return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
857
+ if (minute === 0 && schedule.pattern.second === "0") {
858
+ return hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
815
859
  }
816
- return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
860
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
817
861
  }
818
862
  function cleanHourStride(segment) {
819
863
  if (segment.startToken.indexOf("-") !== -1) {
@@ -822,22 +866,22 @@ function cleanHourStride(segment) {
822
866
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
823
867
  return 24 % segment.interval === 0 && start < segment.interval;
824
868
  }
825
- function hasHourWindow(ir) {
826
- const segments = ir.analyses.segments.hour;
869
+ function hasHourWindow(schedule) {
870
+ const segments = schedule.analyses.segments.hour;
827
871
  return !!segments && segments.some(function range(segment) {
828
872
  return segment.kind === "range";
829
873
  });
830
874
  }
831
- function hourRangeWindowTail(ir, opts) {
832
- return segmentsOf(ir, "hour").length === 1 ? hourSegmentTimes(ir, 0, null, opts) : hourSegmentTimesWithSeka(ir, 0, null, opts);
875
+ function hourRangeWindowTail(schedule, opts) {
876
+ return segmentsOf(schedule, "hour").length === 1 ? hourSegmentTimes(schedule, 0, null, opts) : hourSegmentTimesWithSeka(schedule, 0, null, opts);
833
877
  }
834
- function hourRangeCadence(ir, minute, opts) {
835
- if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
878
+ function hourRangeCadence(schedule, minute, opts) {
879
+ if (minute !== 0 || !hasHourWindow(schedule) || schedule.pattern.second === "0") {
836
880
  return null;
837
881
  }
838
- const tail = hourRangeWindowTail(ir, opts);
839
- const joiner = subMinuteSecond(ir) ? " " : ", ";
840
- return hourCadenceLead(ir, minute, opts) + joiner + tail + trailingQualifier(ir, opts);
882
+ const tail = hourRangeWindowTail(schedule, opts);
883
+ const joiner = subMinuteSecond(schedule) ? " " : ", ";
884
+ return hourCadenceLead(schedule, minute, opts) + joiner + tail + trailingQualifier(schedule, opts);
841
885
  }
842
886
  function kloList(hours, opts) {
843
887
  if (hours.length === 1) {
@@ -847,17 +891,17 @@ function kloList(hours, opts) {
847
891
  return timeDigits(hour, 0, null, opts);
848
892
  }));
849
893
  }
850
- function kloFromTimes(ir, times, opts) {
894
+ function kloFromTimes(schedule, times, opts) {
851
895
  if (times.kind === "fires") {
852
896
  return kloList(times.fires, opts);
853
897
  }
854
- return hourSegmentTimes(ir, 0, null, opts);
898
+ return hourSegmentTimes(schedule, 0, null, opts);
855
899
  }
856
- function hourWindowsFromTimes(ir, times, opts) {
900
+ function hourWindowsFromTimes(schedule, times, opts) {
857
901
  if (times.kind === "fires") {
858
902
  return kloList(times.fires, opts);
859
903
  }
860
- const segments = segmentsOf(ir, "hour");
904
+ const segments = segmentsOf(schedule, "hour");
861
905
  if (!segments.some(function ranged(segment) {
862
906
  return segment.kind === "range";
863
907
  })) {
@@ -895,9 +939,9 @@ function hourSegmentFires(segments) {
895
939
  function hourWindowDigits(hour, opts) {
896
940
  return rangeDigits({ hour, minute: 0 }, { hour, minute: 59 }, opts);
897
941
  }
898
- function hourSegmentTimes(ir, minute, second, opts) {
942
+ function hourSegmentTimes(schedule, minute, second, opts) {
899
943
  const pieces = [];
900
- segmentsOf(ir, "hour").forEach(function clock(segment) {
944
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
901
945
  if (segment.kind === "step") {
902
946
  pieces.push(...segment.fires.map(function each(hour) {
903
947
  return timeDigits(hour, minute, second, opts);
@@ -947,53 +991,54 @@ function timeDigits(hour, minute, second, opts) {
947
991
  { lean: true, sep: opts.style.sep }
948
992
  );
949
993
  }
950
- function leadingQualifier(ir, opts) {
951
- const pattern = ir.pattern;
952
- if (restrictedMonthUnion(ir)) {
994
+ function leadingQualifier(schedule, opts) {
995
+ const pattern = schedule.pattern;
996
+ if (restrictedMonthUnion(schedule)) {
953
997
  return "";
954
998
  }
955
999
  if (pattern.date !== "*" && pattern.weekday !== "*") {
956
- return dateOrWeekday(ir, opts) + " ";
1000
+ return dateOrWeekday(schedule, opts) + " ";
957
1001
  }
958
1002
  if (pattern.date !== "*") {
959
- return datePhrase(ir, opts) + " ";
1003
+ return datePhrase(schedule, opts) + " ";
960
1004
  }
961
1005
  if (pattern.weekday !== "*") {
962
- return weekdayQualifier(ir) + monthScope(ir) + " ";
1006
+ return weekdayQualifier(schedule) + monthScope(schedule) + " ";
963
1007
  }
964
1008
  if (pattern.month !== "*") {
965
- return "joka p\xE4iv\xE4 " + monthPhrase(ir) + " ";
1009
+ return "joka p\xE4iv\xE4 " + monthPhrase(schedule) + " ";
966
1010
  }
967
1011
  return "joka p\xE4iv\xE4 ";
968
1012
  }
969
- function trailingQualifier(ir, opts) {
970
- const pattern = ir.pattern;
971
- if (restrictedMonthUnion(ir)) {
1013
+ function trailingQualifier(schedule, opts) {
1014
+ const pattern = schedule.pattern;
1015
+ if (restrictedMonthUnion(schedule)) {
972
1016
  return "";
973
1017
  }
974
1018
  if (pattern.date !== "*" && pattern.weekday !== "*") {
975
- return " " + dateOrWeekday(ir, opts);
1019
+ return " " + dateOrWeekday(schedule, opts);
976
1020
  }
977
1021
  if (pattern.date !== "*") {
978
- return " " + datePhrase(ir, opts);
1022
+ return " " + datePhrase(schedule, opts);
979
1023
  }
980
1024
  if (pattern.weekday !== "*") {
981
- return " " + weekdayQualifier(ir) + monthScope(ir);
1025
+ return " " + weekdayQualifier(schedule) + monthScope(schedule);
982
1026
  }
983
1027
  if (pattern.month !== "*") {
984
- return " " + monthPhrase(ir);
1028
+ return " " + monthPhrase(schedule);
985
1029
  }
986
1030
  return "";
987
1031
  }
988
- function dateOrWeekday(ir, opts) {
989
- return datePhrase(ir, opts) + " tai " + weekdayQualifier(ir) + monthScope(ir);
1032
+ function dateOrWeekday(schedule, opts) {
1033
+ const dateArm = oddDayUnion(schedule.pattern.date) || datePhrase(schedule, opts);
1034
+ return dateArm + " tai " + unionWeekdayArm(schedule) + monthScope(schedule);
990
1035
  }
991
- function weekdayQualifier(ir) {
992
- const quartz = quartzWeekdayPhrase(ir.pattern.weekday);
1036
+ function weekdayQualifier(schedule) {
1037
+ const quartz = quartzWeekdayPhrase(schedule.pattern.weekday);
993
1038
  if (quartz) {
994
1039
  return quartz;
995
1040
  }
996
- const segments = orderWeekdaysForDisplay(segmentsOf(ir, "weekday"));
1041
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
997
1042
  return joinList(segments.map(function piece(segment) {
998
1043
  if (segment.kind === "range") {
999
1044
  return weekdays[weekdayNumber(segment.bounds[0])].ela + " " + weekdays[weekdayNumber(segment.bounds[1])].ill;
@@ -1001,8 +1046,8 @@ function weekdayQualifier(ir) {
1001
1046
  return weekdays[weekdayNumber(segment.value)].isin;
1002
1047
  }));
1003
1048
  }
1004
- function monthPhrase(ir) {
1005
- const segments = flattenSteps(segmentsOf(ir, "month"));
1049
+ function monthPhrase(schedule) {
1050
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
1006
1051
  return joinList(segments.map(function piece(segment) {
1007
1052
  if (segment.kind === "range") {
1008
1053
  return monthStems[monthNumber(segment.bounds[0])] + "kuusta " + monthStems[monthNumber(segment.bounds[1])] + "kuuhun";
@@ -1010,11 +1055,11 @@ function monthPhrase(ir) {
1010
1055
  return monthStems[monthNumber(segment.value)] + "kuussa";
1011
1056
  }));
1012
1057
  }
1013
- function monthScope(ir) {
1014
- if (ir.pattern.month === "*") {
1058
+ function monthScope(schedule) {
1059
+ if (schedule.pattern.month === "*") {
1015
1060
  return "";
1016
1061
  }
1017
- return " " + monthPhrase(ir);
1062
+ return " " + monthPhrase(schedule);
1018
1063
  }
1019
1064
  function flattenSteps(segments) {
1020
1065
  return segments.flatMap(function flat(segment) {
@@ -1023,16 +1068,16 @@ function flattenSteps(segments) {
1023
1068
  }) : [segment];
1024
1069
  });
1025
1070
  }
1026
- function datePhrase(ir, opts) {
1027
- const pattern = ir.pattern;
1071
+ function datePhrase(schedule, opts) {
1072
+ const pattern = schedule.pattern;
1028
1073
  const quartz = quartzDatePhrase(pattern.date);
1029
1074
  if (quartz) {
1030
- return quartz + monthScope(ir);
1075
+ return quartz + monthScope(schedule);
1031
1076
  }
1032
1077
  if (isOpenStep(pattern.date)) {
1033
- return stepDates(pattern.date, opts) + monthScope(ir);
1078
+ return stepDates(pattern.date, opts) + monthScope(schedule);
1034
1079
  }
1035
- return monthAnchor(ir, opts) + " " + dateWords(ir) + " p\xE4iv\xE4n\xE4" + foldedYear(ir) + monthStepStart(pattern.month) + rangedMonthScope(ir);
1080
+ return monthAnchor(schedule, opts) + " " + dateWords(schedule) + " p\xE4iv\xE4n\xE4" + foldedYear(schedule) + monthStepStart(pattern.month) + rangedMonthScope(schedule);
1036
1081
  }
1037
1082
  function monthStepStart(monthField) {
1038
1083
  if (!isOpenStep(monthField)) {
@@ -1044,30 +1089,30 @@ function monthStepStart(monthField) {
1044
1089
  }
1045
1090
  return " " + monthStems[monthNumber(start)] + "kuusta alkaen";
1046
1091
  }
1047
- function monthAnchor(ir, opts) {
1048
- const monthField = ir.pattern.month;
1049
- if (monthField === "*" || monthRanged(ir)) {
1092
+ function monthAnchor(schedule, opts) {
1093
+ const monthField = schedule.pattern.month;
1094
+ if (monthField === "*" || monthRanged(schedule)) {
1050
1095
  return "kuukauden";
1051
1096
  }
1052
1097
  if (isOpenStep(monthField)) {
1053
1098
  return stepMonths(monthField, opts);
1054
1099
  }
1055
- const segments = flattenSteps(segmentsOf(ir, "month"));
1100
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
1056
1101
  return joinList(segments.map(function genitiveOf(segment) {
1057
1102
  const single = segment;
1058
1103
  return monthStems[monthNumber(single.value)] + "kuun";
1059
1104
  }));
1060
1105
  }
1061
- function rangedMonthScope(ir) {
1062
- return monthRanged(ir) ? " " + monthPhrase(ir) : "";
1106
+ function rangedMonthScope(schedule) {
1107
+ return monthRanged(schedule) ? " " + monthPhrase(schedule) : "";
1063
1108
  }
1064
- function monthRanged(ir) {
1065
- return ir.pattern.month !== "*" && segmentsOf(ir, "month").some(function range(segment) {
1109
+ function monthRanged(schedule) {
1110
+ return schedule.pattern.month !== "*" && segmentsOf(schedule, "month").some(function range(segment) {
1066
1111
  return segment.kind === "range";
1067
1112
  });
1068
1113
  }
1069
- function dateWords(ir) {
1070
- return joinList(segmentsOf(ir, "date").flatMap(
1114
+ function dateWords(schedule) {
1115
+ return joinList(segmentsOf(schedule, "date").flatMap(
1071
1116
  function word(segment) {
1072
1117
  if (segment.kind === "range") {
1073
1118
  return [segment.bounds[0] + ".\u2013" + segment.bounds[1] + "."];
@@ -1123,15 +1168,15 @@ function weekdayNumber(token) {
1123
1168
  function monthNumber(token) {
1124
1169
  return +token;
1125
1170
  }
1126
- function applyYear(description, ir, opts) {
1127
- const yearField = ir.pattern.year;
1171
+ function applyYear(description, schedule, opts) {
1172
+ const yearField = schedule.pattern.year;
1128
1173
  if (yearField === "*") {
1129
1174
  return description;
1130
1175
  }
1131
1176
  if (yearField.indexOf("/") !== -1) {
1132
1177
  return description + " " + stepYears(yearField, opts);
1133
1178
  }
1134
- if (foldedYear(ir) && ir.pattern.date !== "*") {
1179
+ if (foldedYear(schedule) && schedule.pattern.date !== "*") {
1135
1180
  return description;
1136
1181
  }
1137
1182
  if (yearField.indexOf(",") !== -1) {
@@ -1151,8 +1196,8 @@ function stepYears(yearField, opts) {
1151
1196
  }
1152
1197
  return phrase;
1153
1198
  }
1154
- function foldedYear(ir) {
1155
- const yearField = ir.pattern.year;
1199
+ function foldedYear(schedule) {
1200
+ const yearField = schedule.pattern.year;
1156
1201
  if (yearField === "*" || yearField.indexOf("/") !== -1 || yearField.indexOf("-") !== -1 || yearField.indexOf(",") !== -1) {
1157
1202
  return "";
1158
1203
  }