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.js CHANGED
@@ -31,6 +31,16 @@ function isNonNegativeInteger(value) {
31
31
  const digits = /^\d+$/;
32
32
  return digits.test(value);
33
33
  }
34
+ function toFieldNumber(token, numberMap) {
35
+ return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
36
+ }
37
+
38
+ // src/core/shapes.ts
39
+ function isOpenStep(field) {
40
+ return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
41
+ }
42
+
43
+ // src/core/cadence.ts
34
44
  function arithmeticStep(values) {
35
45
  if (values.length < 5) {
36
46
  return null;
@@ -46,34 +56,11 @@ function arithmeticStep(values) {
46
56
  }
47
57
  return { start: values[0], interval, last: values[values.length - 1] };
48
58
  }
49
- function weekdayDisplayKey(value) {
50
- return value === 0 ? 7 : value;
51
- }
52
- function orderWeekdaysForDisplay(segments) {
53
- const flattened = segments.flatMap(function flat(segment) {
54
- return segment.kind === "step" ? segment.fires.map(function single(value) {
55
- return { kind: "single", value: "" + value };
56
- }) : [segment];
57
- });
58
- function key(segment) {
59
- return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
60
- }
61
- return flattened.map(function index(segment, position) {
62
- return [segment, position];
63
- }).sort(function byDisplayKey(a, b) {
64
- return key(a[0]) - key(b[0]) || a[1] - b[1];
65
- }).map(function unwrap(pair) {
66
- return pair[0];
67
- });
68
- }
69
- function toFieldNumber(token, numberMap) {
70
- return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
59
+ function segmentsOf(schedule, field) {
60
+ return schedule.analyses.segments[field] ?? [];
71
61
  }
72
- function segmentsOf(ir, field) {
73
- return ir.analyses.segments[field] ?? [];
74
- }
75
- function stepSegment(ir, field) {
76
- return segmentsOf(ir, field)[0];
62
+ function stepSegment(schedule, field) {
63
+ return segmentsOf(schedule, field)[0];
77
64
  }
78
65
  function singleValues(segments) {
79
66
  const values = [];
@@ -88,6 +75,17 @@ function singleValues(segments) {
88
75
  function offsetCleanStride(stride) {
89
76
  return stride.start < stride.interval && 24 % stride.interval === 0;
90
77
  }
78
+ function renderStride(spec, parts) {
79
+ const { start, interval, cycle } = spec;
80
+ const tiles = cycle % interval === 0;
81
+ if (start === 0 && tiles) {
82
+ return parts.bare();
83
+ }
84
+ if (start < interval && tiles) {
85
+ return parts.offset();
86
+ }
87
+ return parts.bounded();
88
+ }
91
89
  function hourListStride(values) {
92
90
  if (values.length < 2) {
93
91
  return null;
@@ -107,9 +105,26 @@ function hourListStride(values) {
107
105
  return { interval, last: values[values.length - 1], start: values[0] };
108
106
  }
109
107
 
110
- // src/core/shapes.ts
111
- function isOpenStep(field) {
112
- return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
108
+ // src/core/weekday.ts
109
+ function weekdayDisplayKey(value) {
110
+ return value === 0 ? 7 : value;
111
+ }
112
+ function orderWeekdaysForDisplay(segments) {
113
+ const flattened = segments.flatMap(function flat(segment) {
114
+ return segment.kind === "step" ? segment.fires.map(function single(value) {
115
+ return { kind: "single", value: "" + value };
116
+ }) : [segment];
117
+ });
118
+ function key(segment) {
119
+ return segment.kind === "range" ? weekdayDisplayKey(+segment.bounds[0]) : weekdayDisplayKey(+segment.value);
120
+ }
121
+ return flattened.map(function index(segment, position) {
122
+ return [segment, position];
123
+ }).sort(function byDisplayKey(a, b) {
124
+ return key(a[0]) - key(b[0]) || a[1] - b[1];
125
+ }).map(function unwrap(pair) {
126
+ return pair[0];
127
+ });
113
128
  }
114
129
 
115
130
  // src/lang/fi/dialects.ts
@@ -266,94 +281,108 @@ function normalizeOptions(options) {
266
281
  years: !!options.years
267
282
  };
268
283
  }
269
- function restrictedMonthUnion(ir) {
270
- return ir.pattern.date !== "*" && ir.pattern.weekday !== "*" && ir.pattern.month !== "*";
284
+ function restrictedMonthUnion(schedule) {
285
+ return schedule.pattern.date !== "*" && schedule.pattern.weekday !== "*" && schedule.pattern.month !== "*";
271
286
  }
272
- function unionDateArm(ir) {
273
- return quartzDatePhrase(ir.pattern.date) || dateWords(ir) + " p\xE4iv\xE4n\xE4";
287
+ function oddDayUnion(dateField) {
288
+ if (!isOpenStep(dateField)) {
289
+ return null;
290
+ }
291
+ const [start, step] = dateField.split("/");
292
+ return (start === "*" || start === "1") && +step === 2 ? "kuukauden parittomina p\xE4ivin\xE4" : null;
293
+ }
294
+ function unionDateArm(schedule) {
295
+ return quartzDatePhrase(schedule.pattern.date) || oddDayUnion(schedule.pattern.date) || dateWords(schedule) + " p\xE4iv\xE4n\xE4";
274
296
  }
275
- function describe(ir, opts) {
276
- if (restrictedMonthUnion(ir)) {
277
- const timePart = render(ir, ir.plan, opts);
297
+ function unionWeekdayArm(schedule) {
298
+ const segments = segmentsOf(schedule, "weekday");
299
+ if (segments.length === 1 && segments[0].kind === "range" && segments[0].bounds[0] === "1" && segments[0].bounds[1] === "5") {
300
+ return "arkisin";
301
+ }
302
+ return weekdayQualifier(schedule);
303
+ }
304
+ function describe(schedule, opts) {
305
+ if (restrictedMonthUnion(schedule)) {
306
+ const timePart = render(schedule, schedule.plan, opts);
278
307
  return applyYear(
279
- monthPhrase(ir) + " " + timePart + " joko " + unionDateArm(ir) + " tai " + weekdayQualifier(ir),
280
- ir,
308
+ monthPhrase(schedule) + " " + timePart + " " + unionDateArm(schedule) + " tai " + unionWeekdayArm(schedule),
309
+ schedule,
281
310
  opts
282
311
  );
283
312
  }
284
- return applyYear(render(ir, ir.plan, opts), ir, opts);
313
+ return applyYear(render(schedule, schedule.plan, opts), schedule, opts);
285
314
  }
286
- function render(ir, plan, opts) {
315
+ function render(schedule, plan, opts) {
287
316
  const renderer = renderers[plan.kind];
288
- return renderer(ir, plan, opts);
317
+ return renderer(schedule, plan, opts);
289
318
  }
290
- function renderEverySecond(ir, plan, opts) {
291
- return "joka sekunti" + trailingQualifier(ir, opts);
319
+ function renderEverySecond(schedule, plan, opts) {
320
+ return "joka sekunti" + trailingQualifier(schedule, opts);
292
321
  }
293
- function renderStandaloneSeconds(ir, plan, opts) {
294
- return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
322
+ function renderStandaloneSeconds(schedule, plan, opts) {
323
+ return secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
295
324
  }
296
- function renderSecondPastMinute(ir, plan, opts) {
297
- return atMarks(ir.pattern.second, units.second, true) + trailingQualifier(ir, opts);
325
+ function renderSecondPastMinute(schedule, plan, opts) {
326
+ return atMarks(schedule.pattern.second, units.second, true) + trailingQualifier(schedule, opts);
298
327
  }
299
- function renderSecondsWithinMinute(ir, plan, opts) {
300
- const minuteField = ir.pattern.minute;
328
+ function renderSecondsWithinMinute(schedule, plan, opts) {
329
+ const minuteField = schedule.pattern.minute;
301
330
  if (plan.singleSecond) {
302
- return units.minute.mark + " " + minuteField + " " + units.minute.gen + " ja " + ir.pattern.second + " " + units.second.gen + " kohdalla" + trailingQualifier(ir, opts);
331
+ return units.minute.mark + " " + minuteField + " " + units.minute.gen + " ja " + schedule.pattern.second + " " + units.second.gen + " kohdalla" + trailingQualifier(schedule, opts);
303
332
  }
304
- return secondsLeadClause(ir, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(ir, opts);
333
+ return secondsLeadClause(schedule, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(schedule, opts);
305
334
  }
306
- function composeSecondsOverMinuteStep(ir, freq, opts) {
307
- const seg = stepSegment(ir, "minute");
335
+ function composeSecondsOverMinuteStep(schedule, freq, opts) {
336
+ const seg = stepSegment(schedule, "minute");
308
337
  const stepPhrase = stepCycle60(seg, units.minute, opts);
309
338
  if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
310
- const bareHours = kloFromTimes(ir, freq.hours.times, opts);
311
- return hoursFirstMinutes(bareHours, ir, opts) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
339
+ const bareHours = kloFromTimes(schedule, freq.hours.times, opts);
340
+ return hoursFirstMinutes(bareHours, schedule, opts) + ", " + secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
312
341
  }
313
342
  let hourClause = "";
314
343
  if (freq.hours.kind === "during") {
315
- hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
344
+ hourClause = " " + hourWindowsFromTimes(schedule, freq.hours.times, opts);
316
345
  } else if (freq.hours.kind === "window") {
317
346
  hourClause = " " + hourWindow(freq.hours, opts);
318
347
  } else if (freq.hours.kind === "step") {
319
- hourClause = " " + everyNthHour(stepSegment(ir, "hour"), opts);
348
+ hourClause = " " + everyNthHour(stepSegment(schedule, "hour"), opts);
320
349
  }
321
- return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
350
+ return stepPhrase + ", " + secondsLeadClause(schedule, opts) + hourClause + trailingQualifier(schedule, opts);
322
351
  }
323
- function composeHourCadence(ir, plan, opts) {
352
+ function composeHourCadence(schedule, plan, opts) {
324
353
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
325
- if (!clockRest || ir.shapes.minute !== "single") {
354
+ if (!clockRest || schedule.shapes.minute !== "single") {
326
355
  return null;
327
356
  }
328
- const minute = +ir.pattern.minute;
329
- return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
357
+ const minute = +schedule.pattern.minute;
358
+ return hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
330
359
  }
331
- function renderComposeSeconds(ir, plan, opts) {
332
- const cadence = composeHourCadence(ir, plan, opts);
360
+ function renderComposeSeconds(schedule, plan, opts) {
361
+ const cadence = composeHourCadence(schedule, plan, opts);
333
362
  if (cadence !== null) {
334
363
  return cadence;
335
364
  }
336
- if (plan.rest.kind === "minuteFrequency" && ir.pattern.second !== "*") {
337
- return composeSecondsOverMinuteStep(ir, plan.rest, opts);
365
+ if (plan.rest.kind === "minuteFrequency" && schedule.pattern.second !== "*") {
366
+ return composeSecondsOverMinuteStep(schedule, plan.rest, opts);
338
367
  }
339
368
  if (plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0)) {
340
- return composeMinuteZero(ir, plan.rest, opts);
369
+ return composeMinuteZero(schedule, plan.rest, opts);
341
370
  }
342
- if (isEveryOtherMinuteSeconds(ir, plan)) {
343
- return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
371
+ if (isEveryOtherMinuteSeconds(schedule, plan)) {
372
+ return secondsLeadClause(schedule, opts) + " joka toisena minuuttina";
344
373
  }
345
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
346
- const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
347
- return lead + render(ir, plan.rest, opts);
374
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && schedule.analyses.clockSecond;
375
+ const lead = restOwnsLead ? "" : secondsLeadClause(schedule, opts) + ", ";
376
+ return lead + render(schedule, plan.rest, opts);
348
377
  }
349
- function isEveryOtherMinuteSeconds(ir, plan) {
350
- if (plan.rest.kind !== "minuteFrequency" || ir.pattern.second !== "*" || ir.shapes.hour !== "wildcard") {
378
+ function isEveryOtherMinuteSeconds(schedule, plan) {
379
+ if (plan.rest.kind !== "minuteFrequency" || schedule.pattern.second !== "*" || schedule.shapes.hour !== "wildcard") {
351
380
  return false;
352
381
  }
353
- const seg = stepSegment(ir, "minute");
382
+ const seg = stepSegment(schedule, "minute");
354
383
  return seg.startToken === "*" && seg.interval === 2;
355
384
  }
356
- function composeMinuteZero(ir, rest, opts) {
385
+ function composeMinuteZero(schedule, rest, opts) {
357
386
  const clocks = rest.times.map(function clock(time) {
358
387
  return clockDigits(
359
388
  { hour: time.hour, minute: time.minute },
@@ -361,54 +390,66 @@ function composeMinuteZero(ir, rest, opts) {
361
390
  );
362
391
  });
363
392
  const frame = clocks.length === 1 ? "minuutin " + clocks[0] : "minuuttien " + joinList(clocks);
364
- const dayTrail = leadingQualifier(ir, opts).trimEnd();
365
- return secondsLeadClause(ir, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
393
+ const dayTrail = leadingQualifier(schedule, opts).trimEnd();
394
+ return secondsLeadClause(schedule, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
366
395
  }
367
- function secondsLeadClause(ir, opts) {
368
- const secondField = ir.pattern.second;
369
- const shape = ir.shapes.second;
396
+ function secondsLeadClause(schedule, opts) {
397
+ const secondField = schedule.pattern.second;
398
+ const shape = schedule.shapes.second;
370
399
  if (secondField === "*") {
371
400
  return "joka sekunti";
372
401
  }
373
402
  if (shape === "step") {
374
403
  return stepCycle60(
375
- stepSegment(ir, "second"),
404
+ stepSegment(schedule, "second"),
376
405
  units.second,
377
406
  opts
378
407
  );
379
408
  }
380
- const marked = ir.pattern.minute === "*";
409
+ const marked = schedule.pattern.minute === "*";
381
410
  if (shape === "single") {
382
411
  return atMarks(secondField, units.second, marked);
383
412
  }
384
- return strideFromSegments(segmentsOf(ir, "second"), units.second, opts) ?? atMarks(
385
- joinList(segmentWords(segmentsOf(ir, "second"))),
413
+ return strideFromSegments(
414
+ segmentsOf(schedule, "second"),
415
+ units.second,
416
+ opts
417
+ ) ?? atMarks(
418
+ joinList(segmentWords(segmentsOf(schedule, "second"))),
386
419
  units.second,
387
420
  marked
388
421
  );
389
422
  }
390
- function renderEveryMinute(ir, plan, opts) {
391
- return "joka minuutti" + trailingQualifier(ir, opts);
423
+ function renderEveryMinute(schedule, plan, opts) {
424
+ return "joka minuutti" + trailingQualifier(schedule, opts);
392
425
  }
393
- function renderSingleMinute(ir, plan, opts) {
394
- return atMarks(ir.pattern.minute, units.minute, true) + trailingQualifier(ir, opts);
426
+ function renderSingleMinute(schedule, plan, opts) {
427
+ return atMarks(schedule.pattern.minute, units.minute, true) + trailingQualifier(schedule, opts);
395
428
  }
396
- function renderRangeOfMinutes(ir, plan, opts) {
397
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
429
+ function renderRangeOfMinutes(schedule, plan, opts) {
430
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
398
431
  }
399
- function renderMultipleMinutes(ir, plan, opts) {
400
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
432
+ function renderMultipleMinutes(schedule, plan, opts) {
433
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
401
434
  }
402
- function minutesList(ir, opts) {
403
- return strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts) ?? atMarks(
404
- joinList(segmentWords(segmentsOf(ir, "minute"))),
435
+ function minutesList(schedule, opts) {
436
+ return strideFromSegments(
437
+ segmentsOf(schedule, "minute"),
438
+ units.minute,
439
+ opts
440
+ ) ?? atMarks(
441
+ joinList(segmentWords(segmentsOf(schedule, "minute"))),
405
442
  units.minute,
406
443
  true
407
444
  );
408
445
  }
409
- function bareMinutes(ir, opts) {
410
- return strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts) ?? atMarks(
411
- joinList(segmentWords(segmentsOf(ir, "minute"))),
446
+ function bareMinutes(schedule, opts) {
447
+ return strideFromSegments(
448
+ segmentsOf(schedule, "minute"),
449
+ units.minute,
450
+ opts
451
+ ) ?? atMarks(
452
+ joinList(segmentWords(segmentsOf(schedule, "minute"))),
412
453
  units.minute,
413
454
  false
414
455
  );
@@ -438,16 +479,16 @@ function hoursAreRangeIsolated(segments) {
438
479
  }
439
480
  return hasRange && hasSingle;
440
481
  }
441
- function hoursFirstMinutes(hoursStr, ir, opts) {
442
- const stride = strideFromSegments(segmentsOf(ir, "minute"), units.minute, opts);
482
+ function hoursFirstMinutes(hoursStr, schedule, opts) {
483
+ const stride = strideFromSegments(segmentsOf(schedule, "minute"), units.minute, opts);
443
484
  if (stride) {
444
485
  return hoursStr + " aina " + stride;
445
486
  }
446
- return hoursStr + " aina minuuttien " + joinList(segmentWords(segmentsOf(ir, "minute"))) + " kohdalla";
487
+ return hoursStr + " aina minuuttien " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " kohdalla";
447
488
  }
448
- function hourSegmentTimesWithSeka(ir, minute, second, opts) {
489
+ function hourSegmentTimesWithSeka(schedule, minute, second, opts) {
449
490
  const pieces = [];
450
- segmentsOf(ir, "hour").forEach(function clock(segment) {
491
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
451
492
  if (segment.kind === "range") {
452
493
  pieces.push(rangeDigits(
453
494
  { hour: +segment.bounds[0], minute, second },
@@ -460,61 +501,61 @@ function hourSegmentTimesWithSeka(ir, minute, second, opts) {
460
501
  });
461
502
  return "klo " + pieces.slice(0, -1).join(", ") + " sek\xE4 klo " + pieces[pieces.length - 1];
462
503
  }
463
- function renderMinuteFrequency(ir, plan, opts) {
464
- const seg = stepSegment(ir, "minute");
504
+ function renderMinuteFrequency(schedule, plan, opts) {
505
+ const seg = stepSegment(schedule, "minute");
465
506
  if (plan.hours.kind === "during") {
466
- const cadence = unevenHourCadence(ir, opts);
507
+ const cadence = unevenHourCadence(schedule, opts);
467
508
  if (cadence !== null) {
468
- return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(ir, opts);
509
+ return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(schedule, opts);
469
510
  }
470
511
  if (minuteStepIsAnchored(seg)) {
471
- const bareHours = kloFromTimes(ir, plan.hours.times, opts);
472
- return hoursFirstMinutes(bareHours, ir, opts) + trailingQualifier(ir, opts);
512
+ const bareHours = kloFromTimes(schedule, plan.hours.times, opts);
513
+ return hoursFirstMinutes(bareHours, schedule, opts) + trailingQualifier(schedule, opts);
473
514
  }
474
- return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(ir, plan.hours.times, opts) + trailingQualifier(ir, opts);
515
+ return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(schedule, plan.hours.times, opts) + trailingQualifier(schedule, opts);
475
516
  }
476
517
  let phrase = stepCycle60(seg, units.minute, opts);
477
518
  if (plan.hours.kind === "window") {
478
519
  phrase += " " + hourWindow(plan.hours, opts);
479
520
  } else if (plan.hours.kind === "step") {
480
- phrase += " " + everyNthHour(stepSegment(ir, "hour"), opts);
521
+ phrase += " " + everyNthHour(stepSegment(schedule, "hour"), opts);
481
522
  }
482
- return phrase + trailingQualifier(ir, opts);
523
+ return phrase + trailingQualifier(schedule, opts);
483
524
  }
484
- function renderMinuteSpanInHour(ir, plan, opts) {
485
- if (ir.pattern.minute === "*") {
486
- return "joka minuutti kello " + plan.hour + " aikana" + trailingQualifier(ir, opts);
525
+ function renderMinuteSpanInHour(schedule, plan, opts) {
526
+ if (schedule.pattern.minute === "*") {
527
+ return "joka minuutti kello " + plan.hour + " aikana" + trailingQualifier(schedule, opts);
487
528
  }
488
529
  return "joka minuutti " + kloRange(
489
530
  { hour: plan.hour, minute: plan.span[0] },
490
531
  { hour: plan.hour, minute: plan.span[1] },
491
532
  opts
492
- ) + trailingQualifier(ir, opts);
533
+ ) + trailingQualifier(schedule, opts);
493
534
  }
494
- function renderMinutesAcrossHours(ir, plan, opts) {
495
- const cadence = unevenHourCadence(ir, opts);
535
+ function renderMinutesAcrossHours(schedule, plan, opts) {
536
+ const cadence = unevenHourCadence(schedule, opts);
496
537
  if (plan.form === "wildcard") {
497
- return cadence ? "joka minuutti, " + cadence + trailingQualifier(ir, opts) : "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
538
+ return cadence ? "joka minuutti, " + cadence + trailingQualifier(schedule, opts) : "joka minuutti " + hourWindowsFromTimes(schedule, plan.times, opts) + trailingQualifier(schedule, opts);
498
539
  }
499
540
  if (cadence !== null) {
500
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
541
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
501
542
  }
502
- if (hoursAreRangeIsolated(segmentsOf(ir, "hour"))) {
503
- return bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
543
+ if (hoursAreRangeIsolated(segmentsOf(schedule, "hour"))) {
544
+ return bareMinutes(schedule, opts) + " " + hourSegmentTimesWithSeka(schedule, 0, null, opts) + trailingQualifier(schedule, opts);
504
545
  }
505
- const hoursStr = kloFromTimes(ir, plan.times, opts);
506
- return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
546
+ const hoursStr = kloFromTimes(schedule, plan.times, opts);
547
+ return hoursFirstMinutes(hoursStr, schedule, opts) + trailingQualifier(schedule, opts);
507
548
  }
508
- function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
509
- const segment = stepSegment(ir, "hour");
510
- const cadence = unevenHourCadence(ir, opts);
549
+ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
550
+ const segment = stepSegment(schedule, "hour");
551
+ const cadence = unevenHourCadence(schedule, opts);
511
552
  if (plan.form === "wildcard") {
512
- return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
553
+ return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
513
554
  }
514
555
  if (cadence !== null) {
515
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
556
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
516
557
  }
517
- return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
558
+ return bareMinutes(schedule, opts) + hourStepTail(segment, opts) + trailingQualifier(schedule, opts);
518
559
  }
519
560
  function plainHourStep(segment) {
520
561
  return segment.startToken === "*" && 24 % segment.interval === 0;
@@ -563,35 +604,35 @@ function hourStepTail(segment, opts) {
563
604
  const sep = plainHourStep(segment) ? " " : ", ";
564
605
  return sep + plainOrFullHourStep(segment, opts);
565
606
  }
566
- function renderEveryHour(ir, plan, opts) {
567
- return "joka tunti" + trailingQualifier(ir, opts);
607
+ function renderEveryHour(schedule, plan, opts) {
608
+ return "joka tunti" + trailingQualifier(schedule, opts);
568
609
  }
569
- function renderHourRange(ir, plan, opts) {
610
+ function renderHourRange(schedule, plan, opts) {
570
611
  const window = hourWindow(boundedWindow(plan), opts);
571
612
  if (plan.minuteForm === "wildcard") {
572
- return "joka minuutti " + window + trailingQualifier(ir, opts);
613
+ return "joka minuutti " + window + trailingQualifier(schedule, opts);
573
614
  }
574
615
  if (plan.minuteForm === "range") {
575
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
616
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
576
617
  }
577
- if (ir.pattern.minute === "0") {
578
- return "joka tunti " + window + trailingQualifier(ir, opts);
618
+ if (schedule.pattern.minute === "0") {
619
+ return "joka tunti " + window + trailingQualifier(schedule, opts);
579
620
  }
580
- if (ir.shapes.minute === "single") {
581
- return atMarks(ir.pattern.minute, units.minute, false) + " " + kloRange(
582
- { hour: plan.from, minute: +ir.pattern.minute },
621
+ if (schedule.shapes.minute === "single") {
622
+ return atMarks(schedule.pattern.minute, units.minute, false) + " " + kloRange(
623
+ { hour: plan.from, minute: +schedule.pattern.minute },
583
624
  { hour: plan.to, minute: plan.last },
584
625
  opts
585
- ) + trailingQualifier(ir, opts);
626
+ ) + trailingQualifier(schedule, opts);
586
627
  }
587
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
628
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
588
629
  }
589
- function renderHourStep(ir, plan, opts) {
590
- const cadence = unevenHourCadence(ir, opts);
630
+ function renderHourStep(schedule, plan, opts) {
631
+ const cadence = unevenHourCadence(schedule, opts);
591
632
  if (cadence !== null) {
592
- return cadence + trailingQualifier(ir, opts);
633
+ return cadence + trailingQualifier(schedule, opts);
593
634
  }
594
- return stepHours(stepSegment(ir, "hour"), opts) + trailingQualifier(ir, opts);
635
+ return stepHours(stepSegment(schedule, "hour"), opts) + trailingQualifier(schedule, opts);
595
636
  }
596
637
  function boundedWindow(plan) {
597
638
  return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
@@ -603,53 +644,62 @@ function hourWindow(window, opts) {
603
644
  opts
604
645
  );
605
646
  }
606
- function renderClockTimes(ir, plan, opts) {
607
- if (ir.shapes.minute === "single") {
608
- const minute = +ir.pattern.minute;
609
- const cadence = hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
647
+ function renderClockTimes(schedule, plan, opts) {
648
+ if (schedule.shapes.minute === "single") {
649
+ const minute = +schedule.pattern.minute;
650
+ const cadence = hourCadence(schedule, minute, opts) ?? hourRangeCadence(schedule, minute, opts);
610
651
  if (cadence !== null) {
611
652
  return cadence;
612
653
  }
613
654
  }
614
655
  if (plan.times.length === 1) {
615
656
  const time = plan.times[0];
616
- return leadingQualifier(ir, opts) + timeWord(time.hour, time.minute, time.second, opts);
657
+ return leadingQualifier(schedule, opts) + timeWord(time.hour, time.minute, time.second, opts);
617
658
  }
618
659
  const digits = plan.times.map(function clock(time) {
619
660
  return timeDigits(time.hour, time.minute, time.second, opts);
620
661
  });
621
- return leadingQualifier(ir, opts) + "klo " + joinList(digits);
662
+ return leadingQualifier(schedule, opts) + "klo " + joinList(digits);
622
663
  }
623
- function renderCompactClockTimes(ir, plan, opts) {
664
+ function renderCompactClockTimes(schedule, plan, opts) {
624
665
  if (plan.fold) {
625
- const cadence2 = hourCadence(ir, plan.minute, opts) ?? hourRangeCadence(ir, plan.minute, opts);
666
+ const cadence2 = hourCadence(schedule, plan.minute, opts) ?? hourRangeCadence(schedule, plan.minute, opts);
626
667
  if (cadence2 !== null) {
627
668
  return cadence2;
628
669
  }
629
670
  }
630
- const hourSegs = segmentsOf(ir, "hour");
671
+ const hourSegs = segmentsOf(schedule, "hour");
631
672
  if (hoursAreRangeIsolated(hourSegs)) {
632
673
  if (plan.fold) {
633
- return leadingQualifier(ir, opts) + hourSegmentTimesWithSeka(
634
- ir,
674
+ return leadingQualifier(schedule, opts) + hourSegmentTimesWithSeka(
675
+ schedule,
635
676
  plan.minute,
636
- ir.analyses.clockSecond,
677
+ schedule.analyses.clockSecond,
637
678
  opts
638
679
  );
639
680
  }
640
- const phrase2 = bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
641
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase2 : phrase2;
681
+ const phrase2 = bareMinutes(schedule, opts) + " " + hourSegmentTimesWithSeka(schedule, 0, null, opts) + trailingQualifier(schedule, opts);
682
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase2 : phrase2;
642
683
  }
643
684
  if (plan.fold) {
644
- return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
685
+ return leadingQualifier(schedule, opts) + hourSegmentTimes(
686
+ schedule,
687
+ plan.minute,
688
+ schedule.analyses.clockSecond,
689
+ opts
690
+ );
645
691
  }
646
- const cadence = unevenHourCadence(ir, opts);
647
- const phrase = cadence ? bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts) : (
692
+ const cadence = unevenHourCadence(schedule, opts);
693
+ const phrase = cadence ? bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts) : (
648
694
  // A minute list over purely enumerated hours (step fires, all singles) —
649
695
  // hours-first, drop "joka tunti".
650
- hoursFirstMinutes(hourSegmentTimes(ir, 0, null, opts), ir, opts) + trailingQualifier(ir, opts)
696
+ hoursFirstMinutes(
697
+ hourSegmentTimes(schedule, 0, null, opts),
698
+ schedule,
699
+ opts
700
+ ) + trailingQualifier(schedule, opts)
651
701
  );
652
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
702
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
653
703
  }
654
704
  var renderers = {
655
705
  clockTimes: renderClockTimes,
@@ -671,22 +721,19 @@ var renderers = {
671
721
  singleMinute: renderSingleMinute,
672
722
  standaloneSeconds: renderStandaloneSeconds
673
723
  };
674
- function renderStride(stride, opts) {
724
+ function renderStride2(stride, opts) {
675
725
  const { interval, start, last, cycle, unit } = stride;
676
726
  const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
677
- const tiles = cycle % interval === 0;
678
- if (start === 0 && tiles) {
679
- return cadence;
680
- }
681
- if (start < interval && tiles) {
682
- return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
683
- }
684
- return cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last;
727
+ return renderStride({ start, interval, cycle }, {
728
+ bare: () => cadence,
729
+ offset: () => cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen",
730
+ bounded: () => cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last
731
+ });
685
732
  }
686
733
  function strideFromSegments(segments, unit, opts) {
687
734
  const values = singleValues(segments);
688
735
  const step = values && arithmeticStep(values);
689
- return step ? renderStride({ ...step, cycle: 60, unit }, opts) : null;
736
+ return step ? renderStride2({ ...step, cycle: 60, unit }, opts) : null;
690
737
  }
691
738
  function stepCycle60(segment, unit, opts) {
692
739
  if (segment.startToken.indexOf("-") !== -1) {
@@ -696,7 +743,7 @@ function stepCycle60(segment, unit, opts) {
696
743
  if (start !== 0 && segment.fires.length <= 3) {
697
744
  return atMarks(joinList(wordList(segment.fires)), unit, true);
698
745
  }
699
- return renderStride({
746
+ return renderStride2({
700
747
  interval: segment.interval,
701
748
  start,
702
749
  last: segment.fires[segment.fires.length - 1],
@@ -722,17 +769,14 @@ function stepHours(segment, opts) {
722
769
  function hourStrideCadence(stride, opts) {
723
770
  const { start, interval, last } = stride;
724
771
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
725
- const tiles = 24 % interval === 0;
726
- if (start === 0 && tiles) {
727
- return cadence;
728
- }
729
- if (start < interval && tiles) {
730
- return cadence + " klo " + hourElatives[start] + " alkaen";
731
- }
732
- return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
772
+ return renderStride({ start, interval, cycle: 24 }, {
773
+ bare: () => cadence,
774
+ offset: () => cadence + " klo " + hourElatives[start] + " alkaen",
775
+ bounded: () => cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts)
776
+ });
733
777
  }
734
- function hourStride(ir) {
735
- const segments = ir.analyses.segments.hour;
778
+ function hourStride(schedule) {
779
+ const segments = schedule.analyses.segments.hour;
736
780
  if (!segments) {
737
781
  return null;
738
782
  }
@@ -747,47 +791,47 @@ function hourStride(ir) {
747
791
  const values = singleValues(segments);
748
792
  return values && hourListStride(values);
749
793
  }
750
- function unevenHourCadence(ir, opts) {
751
- const stride = hourStride(ir);
794
+ function unevenHourCadence(schedule, opts) {
795
+ const stride = hourStride(schedule);
752
796
  if (!stride || offsetCleanStride(stride)) {
753
797
  return null;
754
798
  }
755
799
  return hourStrideCadence(stride, opts);
756
800
  }
757
- function subMinuteSecond(ir) {
758
- return ir.pattern.second === "*" || ir.shapes.second === "step";
801
+ function subMinuteSecond(schedule) {
802
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
759
803
  }
760
- function hourCadenceLead(ir, minute, opts) {
804
+ function hourCadenceLead(schedule, minute, opts) {
761
805
  if (minute === 0) {
762
- if (subMinuteSecond(ir)) {
763
- return secondsLeadClause(ir, opts) + " minuutin ajan";
806
+ if (subMinuteSecond(schedule)) {
807
+ return secondsLeadClause(schedule, opts) + " minuutin ajan";
764
808
  }
765
- return secondsLeadClause(ir, opts);
809
+ return secondsLeadClause(schedule, opts);
766
810
  }
767
811
  const minutePhrase = atMarks(String(minute), units.minute, false);
768
- if (ir.pattern.second === "0") {
812
+ if (schedule.pattern.second === "0") {
769
813
  return minutePhrase;
770
814
  }
771
- return secondsLeadClause(ir, opts) + ", " + minutePhrase;
815
+ return secondsLeadClause(schedule, opts) + ", " + minutePhrase;
772
816
  }
773
- function hourCadence(ir, minute, opts) {
774
- const stride = hourStride(ir);
817
+ function hourCadence(schedule, minute, opts) {
818
+ const stride = hourStride(schedule);
775
819
  if (!stride) {
776
820
  return null;
777
821
  }
778
822
  const fires = (stride.last - stride.start) / stride.interval + 1;
779
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
823
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
780
824
  return null;
781
825
  }
782
- const segment = segmentsOf(ir, "hour")[0];
783
- const confined = minute === 0 && subMinuteSecond(ir) && segmentsOf(ir, "hour").length === 1 && segment.kind === "step" && cleanHourStride(segment);
826
+ const segment = segmentsOf(schedule, "hour")[0];
827
+ const confined = minute === 0 && subMinuteSecond(schedule) && segmentsOf(schedule, "hour").length === 1 && segment.kind === "step" && cleanHourStride(segment);
784
828
  if (confined) {
785
- return secondsLeadClause(ir, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
829
+ return secondsLeadClause(schedule, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
786
830
  }
787
- if (minute === 0 && ir.pattern.second === "0") {
788
- return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
831
+ if (minute === 0 && schedule.pattern.second === "0") {
832
+ return hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
789
833
  }
790
- return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
834
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
791
835
  }
792
836
  function cleanHourStride(segment) {
793
837
  if (segment.startToken.indexOf("-") !== -1) {
@@ -796,22 +840,22 @@ function cleanHourStride(segment) {
796
840
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
797
841
  return 24 % segment.interval === 0 && start < segment.interval;
798
842
  }
799
- function hasHourWindow(ir) {
800
- const segments = ir.analyses.segments.hour;
843
+ function hasHourWindow(schedule) {
844
+ const segments = schedule.analyses.segments.hour;
801
845
  return !!segments && segments.some(function range(segment) {
802
846
  return segment.kind === "range";
803
847
  });
804
848
  }
805
- function hourRangeWindowTail(ir, opts) {
806
- return segmentsOf(ir, "hour").length === 1 ? hourSegmentTimes(ir, 0, null, opts) : hourSegmentTimesWithSeka(ir, 0, null, opts);
849
+ function hourRangeWindowTail(schedule, opts) {
850
+ return segmentsOf(schedule, "hour").length === 1 ? hourSegmentTimes(schedule, 0, null, opts) : hourSegmentTimesWithSeka(schedule, 0, null, opts);
807
851
  }
808
- function hourRangeCadence(ir, minute, opts) {
809
- if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
852
+ function hourRangeCadence(schedule, minute, opts) {
853
+ if (minute !== 0 || !hasHourWindow(schedule) || schedule.pattern.second === "0") {
810
854
  return null;
811
855
  }
812
- const tail = hourRangeWindowTail(ir, opts);
813
- const joiner = subMinuteSecond(ir) ? " " : ", ";
814
- return hourCadenceLead(ir, minute, opts) + joiner + tail + trailingQualifier(ir, opts);
856
+ const tail = hourRangeWindowTail(schedule, opts);
857
+ const joiner = subMinuteSecond(schedule) ? " " : ", ";
858
+ return hourCadenceLead(schedule, minute, opts) + joiner + tail + trailingQualifier(schedule, opts);
815
859
  }
816
860
  function kloList(hours, opts) {
817
861
  if (hours.length === 1) {
@@ -821,17 +865,17 @@ function kloList(hours, opts) {
821
865
  return timeDigits(hour, 0, null, opts);
822
866
  }));
823
867
  }
824
- function kloFromTimes(ir, times, opts) {
868
+ function kloFromTimes(schedule, times, opts) {
825
869
  if (times.kind === "fires") {
826
870
  return kloList(times.fires, opts);
827
871
  }
828
- return hourSegmentTimes(ir, 0, null, opts);
872
+ return hourSegmentTimes(schedule, 0, null, opts);
829
873
  }
830
- function hourWindowsFromTimes(ir, times, opts) {
874
+ function hourWindowsFromTimes(schedule, times, opts) {
831
875
  if (times.kind === "fires") {
832
876
  return kloList(times.fires, opts);
833
877
  }
834
- const segments = segmentsOf(ir, "hour");
878
+ const segments = segmentsOf(schedule, "hour");
835
879
  if (!segments.some(function ranged(segment) {
836
880
  return segment.kind === "range";
837
881
  })) {
@@ -869,9 +913,9 @@ function hourSegmentFires(segments) {
869
913
  function hourWindowDigits(hour, opts) {
870
914
  return rangeDigits({ hour, minute: 0 }, { hour, minute: 59 }, opts);
871
915
  }
872
- function hourSegmentTimes(ir, minute, second, opts) {
916
+ function hourSegmentTimes(schedule, minute, second, opts) {
873
917
  const pieces = [];
874
- segmentsOf(ir, "hour").forEach(function clock(segment) {
918
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
875
919
  if (segment.kind === "step") {
876
920
  pieces.push(...segment.fires.map(function each(hour) {
877
921
  return timeDigits(hour, minute, second, opts);
@@ -921,53 +965,54 @@ function timeDigits(hour, minute, second, opts) {
921
965
  { lean: true, sep: opts.style.sep }
922
966
  );
923
967
  }
924
- function leadingQualifier(ir, opts) {
925
- const pattern = ir.pattern;
926
- if (restrictedMonthUnion(ir)) {
968
+ function leadingQualifier(schedule, opts) {
969
+ const pattern = schedule.pattern;
970
+ if (restrictedMonthUnion(schedule)) {
927
971
  return "";
928
972
  }
929
973
  if (pattern.date !== "*" && pattern.weekday !== "*") {
930
- return dateOrWeekday(ir, opts) + " ";
974
+ return dateOrWeekday(schedule, opts) + " ";
931
975
  }
932
976
  if (pattern.date !== "*") {
933
- return datePhrase(ir, opts) + " ";
977
+ return datePhrase(schedule, opts) + " ";
934
978
  }
935
979
  if (pattern.weekday !== "*") {
936
- return weekdayQualifier(ir) + monthScope(ir) + " ";
980
+ return weekdayQualifier(schedule) + monthScope(schedule) + " ";
937
981
  }
938
982
  if (pattern.month !== "*") {
939
- return "joka p\xE4iv\xE4 " + monthPhrase(ir) + " ";
983
+ return "joka p\xE4iv\xE4 " + monthPhrase(schedule) + " ";
940
984
  }
941
985
  return "joka p\xE4iv\xE4 ";
942
986
  }
943
- function trailingQualifier(ir, opts) {
944
- const pattern = ir.pattern;
945
- if (restrictedMonthUnion(ir)) {
987
+ function trailingQualifier(schedule, opts) {
988
+ const pattern = schedule.pattern;
989
+ if (restrictedMonthUnion(schedule)) {
946
990
  return "";
947
991
  }
948
992
  if (pattern.date !== "*" && pattern.weekday !== "*") {
949
- return " " + dateOrWeekday(ir, opts);
993
+ return " " + dateOrWeekday(schedule, opts);
950
994
  }
951
995
  if (pattern.date !== "*") {
952
- return " " + datePhrase(ir, opts);
996
+ return " " + datePhrase(schedule, opts);
953
997
  }
954
998
  if (pattern.weekday !== "*") {
955
- return " " + weekdayQualifier(ir) + monthScope(ir);
999
+ return " " + weekdayQualifier(schedule) + monthScope(schedule);
956
1000
  }
957
1001
  if (pattern.month !== "*") {
958
- return " " + monthPhrase(ir);
1002
+ return " " + monthPhrase(schedule);
959
1003
  }
960
1004
  return "";
961
1005
  }
962
- function dateOrWeekday(ir, opts) {
963
- return datePhrase(ir, opts) + " tai " + weekdayQualifier(ir) + monthScope(ir);
1006
+ function dateOrWeekday(schedule, opts) {
1007
+ const dateArm = oddDayUnion(schedule.pattern.date) || datePhrase(schedule, opts);
1008
+ return dateArm + " tai " + unionWeekdayArm(schedule) + monthScope(schedule);
964
1009
  }
965
- function weekdayQualifier(ir) {
966
- const quartz = quartzWeekdayPhrase(ir.pattern.weekday);
1010
+ function weekdayQualifier(schedule) {
1011
+ const quartz = quartzWeekdayPhrase(schedule.pattern.weekday);
967
1012
  if (quartz) {
968
1013
  return quartz;
969
1014
  }
970
- const segments = orderWeekdaysForDisplay(segmentsOf(ir, "weekday"));
1015
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
971
1016
  return joinList(segments.map(function piece(segment) {
972
1017
  if (segment.kind === "range") {
973
1018
  return weekdays[weekdayNumber(segment.bounds[0])].ela + " " + weekdays[weekdayNumber(segment.bounds[1])].ill;
@@ -975,8 +1020,8 @@ function weekdayQualifier(ir) {
975
1020
  return weekdays[weekdayNumber(segment.value)].isin;
976
1021
  }));
977
1022
  }
978
- function monthPhrase(ir) {
979
- const segments = flattenSteps(segmentsOf(ir, "month"));
1023
+ function monthPhrase(schedule) {
1024
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
980
1025
  return joinList(segments.map(function piece(segment) {
981
1026
  if (segment.kind === "range") {
982
1027
  return monthStems[monthNumber(segment.bounds[0])] + "kuusta " + monthStems[monthNumber(segment.bounds[1])] + "kuuhun";
@@ -984,11 +1029,11 @@ function monthPhrase(ir) {
984
1029
  return monthStems[monthNumber(segment.value)] + "kuussa";
985
1030
  }));
986
1031
  }
987
- function monthScope(ir) {
988
- if (ir.pattern.month === "*") {
1032
+ function monthScope(schedule) {
1033
+ if (schedule.pattern.month === "*") {
989
1034
  return "";
990
1035
  }
991
- return " " + monthPhrase(ir);
1036
+ return " " + monthPhrase(schedule);
992
1037
  }
993
1038
  function flattenSteps(segments) {
994
1039
  return segments.flatMap(function flat(segment) {
@@ -997,16 +1042,16 @@ function flattenSteps(segments) {
997
1042
  }) : [segment];
998
1043
  });
999
1044
  }
1000
- function datePhrase(ir, opts) {
1001
- const pattern = ir.pattern;
1045
+ function datePhrase(schedule, opts) {
1046
+ const pattern = schedule.pattern;
1002
1047
  const quartz = quartzDatePhrase(pattern.date);
1003
1048
  if (quartz) {
1004
- return quartz + monthScope(ir);
1049
+ return quartz + monthScope(schedule);
1005
1050
  }
1006
1051
  if (isOpenStep(pattern.date)) {
1007
- return stepDates(pattern.date, opts) + monthScope(ir);
1052
+ return stepDates(pattern.date, opts) + monthScope(schedule);
1008
1053
  }
1009
- return monthAnchor(ir, opts) + " " + dateWords(ir) + " p\xE4iv\xE4n\xE4" + foldedYear(ir) + monthStepStart(pattern.month) + rangedMonthScope(ir);
1054
+ return monthAnchor(schedule, opts) + " " + dateWords(schedule) + " p\xE4iv\xE4n\xE4" + foldedYear(schedule) + monthStepStart(pattern.month) + rangedMonthScope(schedule);
1010
1055
  }
1011
1056
  function monthStepStart(monthField) {
1012
1057
  if (!isOpenStep(monthField)) {
@@ -1018,30 +1063,30 @@ function monthStepStart(monthField) {
1018
1063
  }
1019
1064
  return " " + monthStems[monthNumber(start)] + "kuusta alkaen";
1020
1065
  }
1021
- function monthAnchor(ir, opts) {
1022
- const monthField = ir.pattern.month;
1023
- if (monthField === "*" || monthRanged(ir)) {
1066
+ function monthAnchor(schedule, opts) {
1067
+ const monthField = schedule.pattern.month;
1068
+ if (monthField === "*" || monthRanged(schedule)) {
1024
1069
  return "kuukauden";
1025
1070
  }
1026
1071
  if (isOpenStep(monthField)) {
1027
1072
  return stepMonths(monthField, opts);
1028
1073
  }
1029
- const segments = flattenSteps(segmentsOf(ir, "month"));
1074
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
1030
1075
  return joinList(segments.map(function genitiveOf(segment) {
1031
1076
  const single = segment;
1032
1077
  return monthStems[monthNumber(single.value)] + "kuun";
1033
1078
  }));
1034
1079
  }
1035
- function rangedMonthScope(ir) {
1036
- return monthRanged(ir) ? " " + monthPhrase(ir) : "";
1080
+ function rangedMonthScope(schedule) {
1081
+ return monthRanged(schedule) ? " " + monthPhrase(schedule) : "";
1037
1082
  }
1038
- function monthRanged(ir) {
1039
- return ir.pattern.month !== "*" && segmentsOf(ir, "month").some(function range(segment) {
1083
+ function monthRanged(schedule) {
1084
+ return schedule.pattern.month !== "*" && segmentsOf(schedule, "month").some(function range(segment) {
1040
1085
  return segment.kind === "range";
1041
1086
  });
1042
1087
  }
1043
- function dateWords(ir) {
1044
- return joinList(segmentsOf(ir, "date").flatMap(
1088
+ function dateWords(schedule) {
1089
+ return joinList(segmentsOf(schedule, "date").flatMap(
1045
1090
  function word(segment) {
1046
1091
  if (segment.kind === "range") {
1047
1092
  return [segment.bounds[0] + ".\u2013" + segment.bounds[1] + "."];
@@ -1097,15 +1142,15 @@ function weekdayNumber(token) {
1097
1142
  function monthNumber(token) {
1098
1143
  return +token;
1099
1144
  }
1100
- function applyYear(description, ir, opts) {
1101
- const yearField = ir.pattern.year;
1145
+ function applyYear(description, schedule, opts) {
1146
+ const yearField = schedule.pattern.year;
1102
1147
  if (yearField === "*") {
1103
1148
  return description;
1104
1149
  }
1105
1150
  if (yearField.indexOf("/") !== -1) {
1106
1151
  return description + " " + stepYears(yearField, opts);
1107
1152
  }
1108
- if (foldedYear(ir) && ir.pattern.date !== "*") {
1153
+ if (foldedYear(schedule) && schedule.pattern.date !== "*") {
1109
1154
  return description;
1110
1155
  }
1111
1156
  if (yearField.indexOf(",") !== -1) {
@@ -1125,8 +1170,8 @@ function stepYears(yearField, opts) {
1125
1170
  }
1126
1171
  return phrase;
1127
1172
  }
1128
- function foldedYear(ir) {
1129
- const yearField = ir.pattern.year;
1173
+ function foldedYear(schedule) {
1174
+ const yearField = schedule.pattern.year;
1130
1175
  if (yearField === "*" || yearField.indexOf("/") !== -1 || yearField.indexOf("-") !== -1 || yearField.indexOf(",") !== -1) {
1131
1176
  return "";
1132
1177
  }