cronli5 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +4 -4
  3. package/cronli5.min.js +2 -2
  4. package/dist/cronli5.cjs +514 -407
  5. package/dist/cronli5.js +514 -407
  6. package/dist/lang/de.cjs +296 -225
  7. package/dist/lang/de.js +296 -225
  8. package/dist/lang/en.cjs +471 -364
  9. package/dist/lang/en.js +471 -364
  10. package/dist/lang/es.cjs +318 -281
  11. package/dist/lang/es.js +318 -281
  12. package/dist/lang/fi.cjs +326 -276
  13. package/dist/lang/fi.js +326 -276
  14. package/dist/lang/zh.cjs +308 -236
  15. package/dist/lang/zh.js +308 -236
  16. package/package.json +1 -1
  17. package/src/core/analyze.ts +22 -21
  18. package/src/core/cadence.ts +164 -0
  19. package/src/core/index.ts +3 -1
  20. package/src/core/normalize.ts +3 -3
  21. package/src/core/parse.ts +1 -1
  22. package/src/core/{ir.ts → schedule.ts} +23 -24
  23. package/src/core/shapes.ts +8 -1
  24. package/src/core/specs.ts +1 -1
  25. package/src/core/util.ts +4 -83
  26. package/src/core/validate.ts +2 -2
  27. package/src/core/weekday.ts +54 -0
  28. package/src/cronli5.ts +7 -7
  29. package/src/lang/de/index.ts +329 -288
  30. package/src/lang/en/dialects.ts +1 -1
  31. package/src/lang/en/index.ts +640 -516
  32. package/src/lang/es/index.ts +342 -374
  33. package/src/lang/es/notes.md +1 -1
  34. package/src/lang/fi/dialects.ts +1 -1
  35. package/src/lang/fi/index.ts +367 -372
  36. package/src/lang/fi/notes.md +23 -8
  37. package/src/lang/fi/status.json +1 -1
  38. package/src/lang/zh/index.ts +344 -262
  39. package/src/types.ts +6 -6
  40. package/types/core/analyze.d.ts +4 -4
  41. package/types/core/cadence.d.ts +33 -0
  42. package/types/core/index.d.ts +3 -1
  43. package/types/core/normalize.d.ts +1 -1
  44. package/types/core/parse.d.ts +1 -1
  45. package/types/core/{ir.d.ts → schedule.d.ts} +16 -21
  46. package/types/core/shapes.d.ts +2 -1
  47. package/types/core/specs.d.ts +1 -1
  48. package/types/core/util.d.ts +1 -15
  49. package/types/core/weekday.d.ts +10 -0
  50. package/types/lang/de/index.d.ts +1 -1
  51. package/types/lang/en/dialects.d.ts +1 -1
  52. package/types/lang/en/index.d.ts +1 -1
  53. package/types/lang/es/index.d.ts +1 -1
  54. package/types/lang/fi/dialects.d.ts +1 -1
  55. package/types/lang/fi/index.d.ts +1 -1
  56. package/types/lang/zh/index.d.ts +1 -1
  57. package/types/types.d.ts +5 -5
package/dist/lang/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,6 +56,56 @@ function arithmeticStep(values) {
46
56
  }
47
57
  return { start: values[0], interval, last: values[values.length - 1] };
48
58
  }
59
+ function segmentsOf(schedule, field) {
60
+ return schedule.analyses.segments[field] ?? [];
61
+ }
62
+ function stepSegment(schedule, field) {
63
+ return segmentsOf(schedule, field)[0];
64
+ }
65
+ function singleValues(segments) {
66
+ const values = [];
67
+ for (const segment of segments) {
68
+ if (segment.kind !== "single") {
69
+ return null;
70
+ }
71
+ values.push(+segment.value);
72
+ }
73
+ return values;
74
+ }
75
+ function offsetCleanStride(stride) {
76
+ return stride.start < stride.interval && 24 % stride.interval === 0;
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
+ }
89
+ function hourListStride(values) {
90
+ if (values.length < 2) {
91
+ return null;
92
+ }
93
+ const interval = values[1] - values[0];
94
+ if (interval < 2) {
95
+ return null;
96
+ }
97
+ for (let i = 2; i < values.length; i += 1) {
98
+ if (values[i] - values[i - 1] !== interval) {
99
+ return null;
100
+ }
101
+ }
102
+ if (values[0] !== 0 && values.length < 5) {
103
+ return null;
104
+ }
105
+ return { interval, last: values[values.length - 1], start: values[0] };
106
+ }
107
+
108
+ // src/core/weekday.ts
49
109
  function weekdayDisplayKey(value) {
50
110
  return value === 0 ? 7 : value;
51
111
  }
@@ -66,9 +126,6 @@ function orderWeekdaysForDisplay(segments) {
66
126
  return pair[0];
67
127
  });
68
128
  }
69
- function toFieldNumber(token, numberMap) {
70
- return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
71
- }
72
129
 
73
130
  // src/lang/fi/dialects.ts
74
131
  var dialects = {
@@ -87,9 +144,6 @@ function resolveDialect(dialect) {
87
144
  }
88
145
 
89
146
  // src/lang/fi/index.ts
90
- function stepSegment(segments) {
91
- return segments[0];
92
- }
93
147
  var genitives = [
94
148
  null,
95
149
  "yhden",
@@ -227,94 +281,108 @@ function normalizeOptions(options) {
227
281
  years: !!options.years
228
282
  };
229
283
  }
230
- function restrictedMonthUnion(ir) {
231
- 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 !== "*";
232
286
  }
233
- function unionDateArm(ir) {
234
- 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";
296
+ }
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);
235
303
  }
236
- function describe(ir, opts) {
237
- if (restrictedMonthUnion(ir)) {
238
- const timePart = render(ir, ir.plan, opts);
304
+ function describe(schedule, opts) {
305
+ if (restrictedMonthUnion(schedule)) {
306
+ const timePart = render(schedule, schedule.plan, opts);
239
307
  return applyYear(
240
- monthPhrase(ir) + " " + timePart + " joko " + unionDateArm(ir) + " tai " + weekdayQualifier(ir),
241
- ir,
308
+ monthPhrase(schedule) + " " + timePart + " " + unionDateArm(schedule) + " tai " + unionWeekdayArm(schedule),
309
+ schedule,
242
310
  opts
243
311
  );
244
312
  }
245
- return applyYear(render(ir, ir.plan, opts), ir, opts);
313
+ return applyYear(render(schedule, schedule.plan, opts), schedule, opts);
246
314
  }
247
- function render(ir, plan, opts) {
315
+ function render(schedule, plan, opts) {
248
316
  const renderer = renderers[plan.kind];
249
- return renderer(ir, plan, opts);
317
+ return renderer(schedule, plan, opts);
250
318
  }
251
- function renderEverySecond(ir, plan, opts) {
252
- return "joka sekunti" + trailingQualifier(ir, opts);
319
+ function renderEverySecond(schedule, plan, opts) {
320
+ return "joka sekunti" + trailingQualifier(schedule, opts);
253
321
  }
254
- function renderStandaloneSeconds(ir, plan, opts) {
255
- return secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
322
+ function renderStandaloneSeconds(schedule, plan, opts) {
323
+ return secondsLeadClause(schedule, opts) + trailingQualifier(schedule, opts);
256
324
  }
257
- function renderSecondPastMinute(ir, plan, opts) {
258
- 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);
259
327
  }
260
- function renderSecondsWithinMinute(ir, plan, opts) {
261
- const minuteField = ir.pattern.minute;
328
+ function renderSecondsWithinMinute(schedule, plan, opts) {
329
+ const minuteField = schedule.pattern.minute;
262
330
  if (plan.singleSecond) {
263
- 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);
264
332
  }
265
- 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);
266
334
  }
267
- function composeSecondsOverMinuteStep(ir, freq, opts) {
268
- const seg = stepSegment(ir.analyses.segments.minute);
335
+ function composeSecondsOverMinuteStep(schedule, freq, opts) {
336
+ const seg = stepSegment(schedule, "minute");
269
337
  const stepPhrase = stepCycle60(seg, units.minute, opts);
270
338
  if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
271
- const bareHours = kloFromTimes(ir, freq.hours.times, opts);
272
- 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);
273
341
  }
274
342
  let hourClause = "";
275
343
  if (freq.hours.kind === "during") {
276
- hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
344
+ hourClause = " " + hourWindowsFromTimes(schedule, freq.hours.times, opts);
277
345
  } else if (freq.hours.kind === "window") {
278
346
  hourClause = " " + hourWindow(freq.hours, opts);
279
347
  } else if (freq.hours.kind === "step") {
280
- hourClause = " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
348
+ hourClause = " " + everyNthHour(stepSegment(schedule, "hour"), opts);
281
349
  }
282
- return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
350
+ return stepPhrase + ", " + secondsLeadClause(schedule, opts) + hourClause + trailingQualifier(schedule, opts);
283
351
  }
284
- function composeHourCadence(ir, plan, opts) {
352
+ function composeHourCadence(schedule, plan, opts) {
285
353
  const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
286
- if (!clockRest || ir.shapes.minute !== "single") {
354
+ if (!clockRest || schedule.shapes.minute !== "single") {
287
355
  return null;
288
356
  }
289
- const minute = +ir.pattern.minute;
290
- 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);
291
359
  }
292
- function renderComposeSeconds(ir, plan, opts) {
293
- const cadence = composeHourCadence(ir, plan, opts);
360
+ function renderComposeSeconds(schedule, plan, opts) {
361
+ const cadence = composeHourCadence(schedule, plan, opts);
294
362
  if (cadence !== null) {
295
363
  return cadence;
296
364
  }
297
- if (plan.rest.kind === "minuteFrequency" && ir.pattern.second !== "*") {
298
- return composeSecondsOverMinuteStep(ir, plan.rest, opts);
365
+ if (plan.rest.kind === "minuteFrequency" && schedule.pattern.second !== "*") {
366
+ return composeSecondsOverMinuteStep(schedule, plan.rest, opts);
299
367
  }
300
368
  if (plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0)) {
301
- return composeMinuteZero(ir, plan.rest, opts);
369
+ return composeMinuteZero(schedule, plan.rest, opts);
302
370
  }
303
- if (isEveryOtherMinuteSeconds(ir, plan)) {
304
- return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
371
+ if (isEveryOtherMinuteSeconds(schedule, plan)) {
372
+ return secondsLeadClause(schedule, opts) + " joka toisena minuuttina";
305
373
  }
306
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
307
- const lead = restOwnsLead ? "" : secondsLeadClause(ir, opts) + ", ";
308
- 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);
309
377
  }
310
- function isEveryOtherMinuteSeconds(ir, plan) {
311
- 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") {
312
380
  return false;
313
381
  }
314
- const seg = stepSegment(ir.analyses.segments.minute);
382
+ const seg = stepSegment(schedule, "minute");
315
383
  return seg.startToken === "*" && seg.interval === 2;
316
384
  }
317
- function composeMinuteZero(ir, rest, opts) {
385
+ function composeMinuteZero(schedule, rest, opts) {
318
386
  const clocks = rest.times.map(function clock(time) {
319
387
  return clockDigits(
320
388
  { hour: time.hour, minute: time.minute },
@@ -322,54 +390,66 @@ function composeMinuteZero(ir, rest, opts) {
322
390
  );
323
391
  });
324
392
  const frame = clocks.length === 1 ? "minuutin " + clocks[0] : "minuuttien " + joinList(clocks);
325
- const dayTrail = leadingQualifier(ir, opts).trimEnd();
326
- return secondsLeadClause(ir, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
393
+ const dayTrail = leadingQualifier(schedule, opts).trimEnd();
394
+ return secondsLeadClause(schedule, opts) + " " + frame + " aikana" + (dayTrail ? ", " + dayTrail : "");
327
395
  }
328
- function secondsLeadClause(ir, opts) {
329
- const secondField = ir.pattern.second;
330
- const shape = ir.shapes.second;
396
+ function secondsLeadClause(schedule, opts) {
397
+ const secondField = schedule.pattern.second;
398
+ const shape = schedule.shapes.second;
331
399
  if (secondField === "*") {
332
400
  return "joka sekunti";
333
401
  }
334
402
  if (shape === "step") {
335
403
  return stepCycle60(
336
- stepSegment(ir.analyses.segments.second),
404
+ stepSegment(schedule, "second"),
337
405
  units.second,
338
406
  opts
339
407
  );
340
408
  }
341
- const marked = ir.pattern.minute === "*";
409
+ const marked = schedule.pattern.minute === "*";
342
410
  if (shape === "single") {
343
411
  return atMarks(secondField, units.second, marked);
344
412
  }
345
- return strideFromSegments(ir.analyses.segments.second, units.second, opts) ?? atMarks(
346
- joinList(segmentWords(ir.analyses.segments.second)),
413
+ return strideFromSegments(
414
+ segmentsOf(schedule, "second"),
415
+ units.second,
416
+ opts
417
+ ) ?? atMarks(
418
+ joinList(segmentWords(segmentsOf(schedule, "second"))),
347
419
  units.second,
348
420
  marked
349
421
  );
350
422
  }
351
- function renderEveryMinute(ir, plan, opts) {
352
- return "joka minuutti" + trailingQualifier(ir, opts);
423
+ function renderEveryMinute(schedule, plan, opts) {
424
+ return "joka minuutti" + trailingQualifier(schedule, opts);
353
425
  }
354
- function renderSingleMinute(ir, plan, opts) {
355
- 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);
356
428
  }
357
- function renderRangeOfMinutes(ir, plan, opts) {
358
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
429
+ function renderRangeOfMinutes(schedule, plan, opts) {
430
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
359
431
  }
360
- function renderMultipleMinutes(ir, plan, opts) {
361
- return minutesList(ir, opts) + trailingQualifier(ir, opts);
432
+ function renderMultipleMinutes(schedule, plan, opts) {
433
+ return minutesList(schedule, opts) + trailingQualifier(schedule, opts);
362
434
  }
363
- function minutesList(ir, opts) {
364
- return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
365
- joinList(segmentWords(ir.analyses.segments.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"))),
366
442
  units.minute,
367
443
  true
368
444
  );
369
445
  }
370
- function bareMinutes(ir, opts) {
371
- return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
372
- joinList(segmentWords(ir.analyses.segments.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"))),
373
453
  units.minute,
374
454
  false
375
455
  );
@@ -399,16 +479,16 @@ function hoursAreRangeIsolated(segments) {
399
479
  }
400
480
  return hasRange && hasSingle;
401
481
  }
402
- function hoursFirstMinutes(hoursStr, ir, opts) {
403
- const stride = strideFromSegments(ir.analyses.segments.minute, units.minute, opts);
482
+ function hoursFirstMinutes(hoursStr, schedule, opts) {
483
+ const stride = strideFromSegments(segmentsOf(schedule, "minute"), units.minute, opts);
404
484
  if (stride) {
405
485
  return hoursStr + " aina " + stride;
406
486
  }
407
- return hoursStr + " aina minuuttien " + joinList(segmentWords(ir.analyses.segments.minute)) + " kohdalla";
487
+ return hoursStr + " aina minuuttien " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " kohdalla";
408
488
  }
409
- function hourSegmentTimesWithSeka(ir, minute, second, opts) {
489
+ function hourSegmentTimesWithSeka(schedule, minute, second, opts) {
410
490
  const pieces = [];
411
- ir.analyses.segments.hour.forEach(function clock(segment) {
491
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
412
492
  if (segment.kind === "range") {
413
493
  pieces.push(rangeDigits(
414
494
  { hour: +segment.bounds[0], minute, second },
@@ -421,61 +501,61 @@ function hourSegmentTimesWithSeka(ir, minute, second, opts) {
421
501
  });
422
502
  return "klo " + pieces.slice(0, -1).join(", ") + " sek\xE4 klo " + pieces[pieces.length - 1];
423
503
  }
424
- function renderMinuteFrequency(ir, plan, opts) {
425
- const seg = stepSegment(ir.analyses.segments.minute);
504
+ function renderMinuteFrequency(schedule, plan, opts) {
505
+ const seg = stepSegment(schedule, "minute");
426
506
  if (plan.hours.kind === "during") {
427
- const cadence = unevenHourCadence(ir, opts);
507
+ const cadence = unevenHourCadence(schedule, opts);
428
508
  if (cadence !== null) {
429
- return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(ir, opts);
509
+ return stepCycle60(seg, units.minute, opts) + ", " + cadence + trailingQualifier(schedule, opts);
430
510
  }
431
511
  if (minuteStepIsAnchored(seg)) {
432
- const bareHours = kloFromTimes(ir, plan.hours.times, opts);
433
- 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);
434
514
  }
435
- 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);
436
516
  }
437
517
  let phrase = stepCycle60(seg, units.minute, opts);
438
518
  if (plan.hours.kind === "window") {
439
519
  phrase += " " + hourWindow(plan.hours, opts);
440
520
  } else if (plan.hours.kind === "step") {
441
- phrase += " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
521
+ phrase += " " + everyNthHour(stepSegment(schedule, "hour"), opts);
442
522
  }
443
- return phrase + trailingQualifier(ir, opts);
523
+ return phrase + trailingQualifier(schedule, opts);
444
524
  }
445
- function renderMinuteSpanInHour(ir, plan, opts) {
446
- if (ir.pattern.minute === "*") {
447
- 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);
448
528
  }
449
529
  return "joka minuutti " + kloRange(
450
530
  { hour: plan.hour, minute: plan.span[0] },
451
531
  { hour: plan.hour, minute: plan.span[1] },
452
532
  opts
453
- ) + trailingQualifier(ir, opts);
533
+ ) + trailingQualifier(schedule, opts);
454
534
  }
455
- function renderMinutesAcrossHours(ir, plan, opts) {
456
- const cadence = unevenHourCadence(ir, opts);
535
+ function renderMinutesAcrossHours(schedule, plan, opts) {
536
+ const cadence = unevenHourCadence(schedule, opts);
457
537
  if (plan.form === "wildcard") {
458
- 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);
459
539
  }
460
540
  if (cadence !== null) {
461
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
541
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
462
542
  }
463
- if (hoursAreRangeIsolated(ir.analyses.segments.hour)) {
464
- 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);
465
545
  }
466
- const hoursStr = kloFromTimes(ir, plan.times, opts);
467
- 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);
468
548
  }
469
- function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
470
- const segment = stepSegment(ir.analyses.segments.hour);
471
- const cadence = unevenHourCadence(ir, opts);
549
+ function renderMinuteSpanAcrossHourStep(schedule, plan, opts) {
550
+ const segment = stepSegment(schedule, "hour");
551
+ const cadence = unevenHourCadence(schedule, opts);
472
552
  if (plan.form === "wildcard") {
473
- return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
553
+ return "joka minuutti " + everyNthHour(segment, opts) + trailingQualifier(schedule, opts);
474
554
  }
475
555
  if (cadence !== null) {
476
- return bareMinutes(ir, opts) + ", " + cadence + trailingQualifier(ir, opts);
556
+ return bareMinutes(schedule, opts) + ", " + cadence + trailingQualifier(schedule, opts);
477
557
  }
478
- return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
558
+ return bareMinutes(schedule, opts) + hourStepTail(segment, opts) + trailingQualifier(schedule, opts);
479
559
  }
480
560
  function plainHourStep(segment) {
481
561
  return segment.startToken === "*" && 24 % segment.interval === 0;
@@ -524,35 +604,35 @@ function hourStepTail(segment, opts) {
524
604
  const sep = plainHourStep(segment) ? " " : ", ";
525
605
  return sep + plainOrFullHourStep(segment, opts);
526
606
  }
527
- function renderEveryHour(ir, plan, opts) {
528
- return "joka tunti" + trailingQualifier(ir, opts);
607
+ function renderEveryHour(schedule, plan, opts) {
608
+ return "joka tunti" + trailingQualifier(schedule, opts);
529
609
  }
530
- function renderHourRange(ir, plan, opts) {
610
+ function renderHourRange(schedule, plan, opts) {
531
611
  const window = hourWindow(boundedWindow(plan), opts);
532
612
  if (plan.minuteForm === "wildcard") {
533
- return "joka minuutti " + window + trailingQualifier(ir, opts);
613
+ return "joka minuutti " + window + trailingQualifier(schedule, opts);
534
614
  }
535
615
  if (plan.minuteForm === "range") {
536
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
616
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
537
617
  }
538
- if (ir.pattern.minute === "0") {
539
- return "joka tunti " + window + trailingQualifier(ir, opts);
618
+ if (schedule.pattern.minute === "0") {
619
+ return "joka tunti " + window + trailingQualifier(schedule, opts);
540
620
  }
541
- if (ir.shapes.minute === "single") {
542
- return atMarks(ir.pattern.minute, units.minute, false) + " " + kloRange(
543
- { 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 },
544
624
  { hour: plan.to, minute: plan.last },
545
625
  opts
546
- ) + trailingQualifier(ir, opts);
626
+ ) + trailingQualifier(schedule, opts);
547
627
  }
548
- return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
628
+ return hoursFirstMinutes(window, schedule, opts) + trailingQualifier(schedule, opts);
549
629
  }
550
- function renderHourStep(ir, plan, opts) {
551
- const cadence = unevenHourCadence(ir, opts);
630
+ function renderHourStep(schedule, plan, opts) {
631
+ const cadence = unevenHourCadence(schedule, opts);
552
632
  if (cadence !== null) {
553
- return cadence + trailingQualifier(ir, opts);
633
+ return cadence + trailingQualifier(schedule, opts);
554
634
  }
555
- return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
635
+ return stepHours(stepSegment(schedule, "hour"), opts) + trailingQualifier(schedule, opts);
556
636
  }
557
637
  function boundedWindow(plan) {
558
638
  return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
@@ -564,53 +644,62 @@ function hourWindow(window, opts) {
564
644
  opts
565
645
  );
566
646
  }
567
- function renderClockTimes(ir, plan, opts) {
568
- if (ir.shapes.minute === "single") {
569
- const minute = +ir.pattern.minute;
570
- 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);
571
651
  if (cadence !== null) {
572
652
  return cadence;
573
653
  }
574
654
  }
575
655
  if (plan.times.length === 1) {
576
656
  const time = plan.times[0];
577
- 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);
578
658
  }
579
659
  const digits = plan.times.map(function clock(time) {
580
660
  return timeDigits(time.hour, time.minute, time.second, opts);
581
661
  });
582
- return leadingQualifier(ir, opts) + "klo " + joinList(digits);
662
+ return leadingQualifier(schedule, opts) + "klo " + joinList(digits);
583
663
  }
584
- function renderCompactClockTimes(ir, plan, opts) {
664
+ function renderCompactClockTimes(schedule, plan, opts) {
585
665
  if (plan.fold) {
586
- 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);
587
667
  if (cadence2 !== null) {
588
668
  return cadence2;
589
669
  }
590
670
  }
591
- const hourSegs = ir.analyses.segments.hour;
671
+ const hourSegs = segmentsOf(schedule, "hour");
592
672
  if (hoursAreRangeIsolated(hourSegs)) {
593
673
  if (plan.fold) {
594
- return leadingQualifier(ir, opts) + hourSegmentTimesWithSeka(
595
- ir,
674
+ return leadingQualifier(schedule, opts) + hourSegmentTimesWithSeka(
675
+ schedule,
596
676
  plan.minute,
597
- ir.analyses.clockSecond,
677
+ schedule.analyses.clockSecond,
598
678
  opts
599
679
  );
600
680
  }
601
- const phrase2 = bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
602
- 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;
603
683
  }
604
684
  if (plan.fold) {
605
- 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
+ );
606
691
  }
607
- const cadence = unevenHourCadence(ir, opts);
608
- 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) : (
609
694
  // A minute list over purely enumerated hours (step fires, all singles) —
610
695
  // hours-first, drop "joka tunti".
611
- 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)
612
701
  );
613
- return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
702
+ return schedule.analyses.clockSecond ? secondsLeadClause(schedule, opts) + ", " + phrase : phrase;
614
703
  }
615
704
  var renderers = {
616
705
  clockTimes: renderClockTimes,
@@ -632,32 +721,19 @@ var renderers = {
632
721
  singleMinute: renderSingleMinute,
633
722
  standaloneSeconds: renderStandaloneSeconds
634
723
  };
635
- function renderStride(stride, opts) {
724
+ function renderStride2(stride, opts) {
636
725
  const { interval, start, last, cycle, unit } = stride;
637
726
  const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
638
- const tiles = cycle % interval === 0;
639
- if (start === 0 && tiles) {
640
- return cadence;
641
- }
642
- if (start < interval && tiles) {
643
- return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
644
- }
645
- 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
+ });
646
732
  }
647
733
  function strideFromSegments(segments, unit, opts) {
648
734
  const values = singleValues(segments);
649
735
  const step = values && arithmeticStep(values);
650
- return step ? renderStride({ ...step, cycle: 60, unit }, opts) : null;
651
- }
652
- function singleValues(segments) {
653
- const values = [];
654
- for (const segment of segments) {
655
- if (segment.kind !== "single") {
656
- return null;
657
- }
658
- values.push(+segment.value);
659
- }
660
- return values;
736
+ return step ? renderStride2({ ...step, cycle: 60, unit }, opts) : null;
661
737
  }
662
738
  function stepCycle60(segment, unit, opts) {
663
739
  if (segment.startToken.indexOf("-") !== -1) {
@@ -667,7 +743,7 @@ function stepCycle60(segment, unit, opts) {
667
743
  if (start !== 0 && segment.fires.length <= 3) {
668
744
  return atMarks(joinList(wordList(segment.fires)), unit, true);
669
745
  }
670
- return renderStride({
746
+ return renderStride2({
671
747
  interval: segment.interval,
672
748
  start,
673
749
  last: segment.fires[segment.fires.length - 1],
@@ -693,38 +769,14 @@ function stepHours(segment, opts) {
693
769
  function hourStrideCadence(stride, opts) {
694
770
  const { start, interval, last } = stride;
695
771
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
696
- const tiles = 24 % interval === 0;
697
- if (start === 0 && tiles) {
698
- return cadence;
699
- }
700
- if (start < interval && tiles) {
701
- return cadence + " klo " + hourElatives[start] + " alkaen";
702
- }
703
- return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
704
- }
705
- function hourListStride(values) {
706
- if (values.length < 2) {
707
- return null;
708
- }
709
- const interval = values[1] - values[0];
710
- if (interval < 2) {
711
- return null;
712
- }
713
- for (let i = 2; i < values.length; i += 1) {
714
- if (values[i] - values[i - 1] !== interval) {
715
- return null;
716
- }
717
- }
718
- if (values[0] !== 0 && values.length < 5) {
719
- return null;
720
- }
721
- return { interval, last: values[values.length - 1], start: values[0] };
722
- }
723
- function offsetCleanStride(stride) {
724
- return stride.start < stride.interval && 24 % stride.interval === 0;
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
+ });
725
777
  }
726
- function hourStride(ir) {
727
- const segments = ir.analyses.segments.hour;
778
+ function hourStride(schedule) {
779
+ const segments = schedule.analyses.segments.hour;
728
780
  if (!segments) {
729
781
  return null;
730
782
  }
@@ -739,47 +791,47 @@ function hourStride(ir) {
739
791
  const values = singleValues(segments);
740
792
  return values && hourListStride(values);
741
793
  }
742
- function unevenHourCadence(ir, opts) {
743
- const stride = hourStride(ir);
794
+ function unevenHourCadence(schedule, opts) {
795
+ const stride = hourStride(schedule);
744
796
  if (!stride || offsetCleanStride(stride)) {
745
797
  return null;
746
798
  }
747
799
  return hourStrideCadence(stride, opts);
748
800
  }
749
- function subMinuteSecond(ir) {
750
- return ir.pattern.second === "*" || ir.shapes.second === "step";
801
+ function subMinuteSecond(schedule) {
802
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
751
803
  }
752
- function hourCadenceLead(ir, minute, opts) {
804
+ function hourCadenceLead(schedule, minute, opts) {
753
805
  if (minute === 0) {
754
- if (subMinuteSecond(ir)) {
755
- return secondsLeadClause(ir, opts) + " minuutin ajan";
806
+ if (subMinuteSecond(schedule)) {
807
+ return secondsLeadClause(schedule, opts) + " minuutin ajan";
756
808
  }
757
- return secondsLeadClause(ir, opts);
809
+ return secondsLeadClause(schedule, opts);
758
810
  }
759
811
  const minutePhrase = atMarks(String(minute), units.minute, false);
760
- if (ir.pattern.second === "0") {
812
+ if (schedule.pattern.second === "0") {
761
813
  return minutePhrase;
762
814
  }
763
- return secondsLeadClause(ir, opts) + ", " + minutePhrase;
815
+ return secondsLeadClause(schedule, opts) + ", " + minutePhrase;
764
816
  }
765
- function hourCadence(ir, minute, opts) {
766
- const stride = hourStride(ir);
817
+ function hourCadence(schedule, minute, opts) {
818
+ const stride = hourStride(schedule);
767
819
  if (!stride) {
768
820
  return null;
769
821
  }
770
822
  const fires = (stride.last - stride.start) / stride.interval + 1;
771
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
823
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
772
824
  return null;
773
825
  }
774
- const segment = ir.analyses.segments.hour[0];
775
- const confined = minute === 0 && subMinuteSecond(ir) && ir.analyses.segments.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);
776
828
  if (confined) {
777
- 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);
778
830
  }
779
- if (minute === 0 && ir.pattern.second === "0") {
780
- return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
831
+ if (minute === 0 && schedule.pattern.second === "0") {
832
+ return hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
781
833
  }
782
- return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
834
+ return hourCadenceLead(schedule, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(schedule, opts);
783
835
  }
784
836
  function cleanHourStride(segment) {
785
837
  if (segment.startToken.indexOf("-") !== -1) {
@@ -788,22 +840,22 @@ function cleanHourStride(segment) {
788
840
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
789
841
  return 24 % segment.interval === 0 && start < segment.interval;
790
842
  }
791
- function hasHourWindow(ir) {
792
- const segments = ir.analyses.segments.hour;
843
+ function hasHourWindow(schedule) {
844
+ const segments = schedule.analyses.segments.hour;
793
845
  return !!segments && segments.some(function range(segment) {
794
846
  return segment.kind === "range";
795
847
  });
796
848
  }
797
- function hourRangeWindowTail(ir, opts) {
798
- return ir.analyses.segments.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);
799
851
  }
800
- function hourRangeCadence(ir, minute, opts) {
801
- 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") {
802
854
  return null;
803
855
  }
804
- const tail = hourRangeWindowTail(ir, opts);
805
- const joiner = subMinuteSecond(ir) ? " " : ", ";
806
- 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);
807
859
  }
808
860
  function kloList(hours, opts) {
809
861
  if (hours.length === 1) {
@@ -813,17 +865,17 @@ function kloList(hours, opts) {
813
865
  return timeDigits(hour, 0, null, opts);
814
866
  }));
815
867
  }
816
- function kloFromTimes(ir, times, opts) {
868
+ function kloFromTimes(schedule, times, opts) {
817
869
  if (times.kind === "fires") {
818
870
  return kloList(times.fires, opts);
819
871
  }
820
- return hourSegmentTimes(ir, 0, null, opts);
872
+ return hourSegmentTimes(schedule, 0, null, opts);
821
873
  }
822
- function hourWindowsFromTimes(ir, times, opts) {
874
+ function hourWindowsFromTimes(schedule, times, opts) {
823
875
  if (times.kind === "fires") {
824
876
  return kloList(times.fires, opts);
825
877
  }
826
- const segments = ir.analyses.segments.hour;
878
+ const segments = segmentsOf(schedule, "hour");
827
879
  if (!segments.some(function ranged(segment) {
828
880
  return segment.kind === "range";
829
881
  })) {
@@ -861,9 +913,9 @@ function hourSegmentFires(segments) {
861
913
  function hourWindowDigits(hour, opts) {
862
914
  return rangeDigits({ hour, minute: 0 }, { hour, minute: 59 }, opts);
863
915
  }
864
- function hourSegmentTimes(ir, minute, second, opts) {
916
+ function hourSegmentTimes(schedule, minute, second, opts) {
865
917
  const pieces = [];
866
- ir.analyses.segments.hour.forEach(function clock(segment) {
918
+ segmentsOf(schedule, "hour").forEach(function clock(segment) {
867
919
  if (segment.kind === "step") {
868
920
  pieces.push(...segment.fires.map(function each(hour) {
869
921
  return timeDigits(hour, minute, second, opts);
@@ -913,53 +965,54 @@ function timeDigits(hour, minute, second, opts) {
913
965
  { lean: true, sep: opts.style.sep }
914
966
  );
915
967
  }
916
- function leadingQualifier(ir, opts) {
917
- const pattern = ir.pattern;
918
- if (restrictedMonthUnion(ir)) {
968
+ function leadingQualifier(schedule, opts) {
969
+ const pattern = schedule.pattern;
970
+ if (restrictedMonthUnion(schedule)) {
919
971
  return "";
920
972
  }
921
973
  if (pattern.date !== "*" && pattern.weekday !== "*") {
922
- return dateOrWeekday(ir, opts) + " ";
974
+ return dateOrWeekday(schedule, opts) + " ";
923
975
  }
924
976
  if (pattern.date !== "*") {
925
- return datePhrase(ir, opts) + " ";
977
+ return datePhrase(schedule, opts) + " ";
926
978
  }
927
979
  if (pattern.weekday !== "*") {
928
- return weekdayQualifier(ir) + monthScope(ir) + " ";
980
+ return weekdayQualifier(schedule) + monthScope(schedule) + " ";
929
981
  }
930
982
  if (pattern.month !== "*") {
931
- return "joka p\xE4iv\xE4 " + monthPhrase(ir) + " ";
983
+ return "joka p\xE4iv\xE4 " + monthPhrase(schedule) + " ";
932
984
  }
933
985
  return "joka p\xE4iv\xE4 ";
934
986
  }
935
- function trailingQualifier(ir, opts) {
936
- const pattern = ir.pattern;
937
- if (restrictedMonthUnion(ir)) {
987
+ function trailingQualifier(schedule, opts) {
988
+ const pattern = schedule.pattern;
989
+ if (restrictedMonthUnion(schedule)) {
938
990
  return "";
939
991
  }
940
992
  if (pattern.date !== "*" && pattern.weekday !== "*") {
941
- return " " + dateOrWeekday(ir, opts);
993
+ return " " + dateOrWeekday(schedule, opts);
942
994
  }
943
995
  if (pattern.date !== "*") {
944
- return " " + datePhrase(ir, opts);
996
+ return " " + datePhrase(schedule, opts);
945
997
  }
946
998
  if (pattern.weekday !== "*") {
947
- return " " + weekdayQualifier(ir) + monthScope(ir);
999
+ return " " + weekdayQualifier(schedule) + monthScope(schedule);
948
1000
  }
949
1001
  if (pattern.month !== "*") {
950
- return " " + monthPhrase(ir);
1002
+ return " " + monthPhrase(schedule);
951
1003
  }
952
1004
  return "";
953
1005
  }
954
- function dateOrWeekday(ir, opts) {
955
- 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);
956
1009
  }
957
- function weekdayQualifier(ir) {
958
- const quartz = quartzWeekdayPhrase(ir.pattern.weekday);
1010
+ function weekdayQualifier(schedule) {
1011
+ const quartz = quartzWeekdayPhrase(schedule.pattern.weekday);
959
1012
  if (quartz) {
960
1013
  return quartz;
961
1014
  }
962
- const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday);
1015
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
963
1016
  return joinList(segments.map(function piece(segment) {
964
1017
  if (segment.kind === "range") {
965
1018
  return weekdays[weekdayNumber(segment.bounds[0])].ela + " " + weekdays[weekdayNumber(segment.bounds[1])].ill;
@@ -967,8 +1020,8 @@ function weekdayQualifier(ir) {
967
1020
  return weekdays[weekdayNumber(segment.value)].isin;
968
1021
  }));
969
1022
  }
970
- function monthPhrase(ir) {
971
- const segments = flattenSteps(ir.analyses.segments.month);
1023
+ function monthPhrase(schedule) {
1024
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
972
1025
  return joinList(segments.map(function piece(segment) {
973
1026
  if (segment.kind === "range") {
974
1027
  return monthStems[monthNumber(segment.bounds[0])] + "kuusta " + monthStems[monthNumber(segment.bounds[1])] + "kuuhun";
@@ -976,11 +1029,11 @@ function monthPhrase(ir) {
976
1029
  return monthStems[monthNumber(segment.value)] + "kuussa";
977
1030
  }));
978
1031
  }
979
- function monthScope(ir) {
980
- if (ir.pattern.month === "*") {
1032
+ function monthScope(schedule) {
1033
+ if (schedule.pattern.month === "*") {
981
1034
  return "";
982
1035
  }
983
- return " " + monthPhrase(ir);
1036
+ return " " + monthPhrase(schedule);
984
1037
  }
985
1038
  function flattenSteps(segments) {
986
1039
  return segments.flatMap(function flat(segment) {
@@ -989,16 +1042,16 @@ function flattenSteps(segments) {
989
1042
  }) : [segment];
990
1043
  });
991
1044
  }
992
- function datePhrase(ir, opts) {
993
- const pattern = ir.pattern;
1045
+ function datePhrase(schedule, opts) {
1046
+ const pattern = schedule.pattern;
994
1047
  const quartz = quartzDatePhrase(pattern.date);
995
1048
  if (quartz) {
996
- return quartz + monthScope(ir);
1049
+ return quartz + monthScope(schedule);
997
1050
  }
998
1051
  if (isOpenStep(pattern.date)) {
999
- return stepDates(pattern.date, opts) + monthScope(ir);
1052
+ return stepDates(pattern.date, opts) + monthScope(schedule);
1000
1053
  }
1001
- 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);
1002
1055
  }
1003
1056
  function monthStepStart(monthField) {
1004
1057
  if (!isOpenStep(monthField)) {
@@ -1010,30 +1063,30 @@ function monthStepStart(monthField) {
1010
1063
  }
1011
1064
  return " " + monthStems[monthNumber(start)] + "kuusta alkaen";
1012
1065
  }
1013
- function monthAnchor(ir, opts) {
1014
- const monthField = ir.pattern.month;
1015
- if (monthField === "*" || monthRanged(ir)) {
1066
+ function monthAnchor(schedule, opts) {
1067
+ const monthField = schedule.pattern.month;
1068
+ if (monthField === "*" || monthRanged(schedule)) {
1016
1069
  return "kuukauden";
1017
1070
  }
1018
1071
  if (isOpenStep(monthField)) {
1019
1072
  return stepMonths(monthField, opts);
1020
1073
  }
1021
- const segments = flattenSteps(ir.analyses.segments.month);
1074
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
1022
1075
  return joinList(segments.map(function genitiveOf(segment) {
1023
1076
  const single = segment;
1024
1077
  return monthStems[monthNumber(single.value)] + "kuun";
1025
1078
  }));
1026
1079
  }
1027
- function rangedMonthScope(ir) {
1028
- return monthRanged(ir) ? " " + monthPhrase(ir) : "";
1080
+ function rangedMonthScope(schedule) {
1081
+ return monthRanged(schedule) ? " " + monthPhrase(schedule) : "";
1029
1082
  }
1030
- function monthRanged(ir) {
1031
- return ir.pattern.month !== "*" && ir.analyses.segments.month.some(function range(segment) {
1083
+ function monthRanged(schedule) {
1084
+ return schedule.pattern.month !== "*" && segmentsOf(schedule, "month").some(function range(segment) {
1032
1085
  return segment.kind === "range";
1033
1086
  });
1034
1087
  }
1035
- function dateWords(ir) {
1036
- return joinList(ir.analyses.segments.date.flatMap(
1088
+ function dateWords(schedule) {
1089
+ return joinList(segmentsOf(schedule, "date").flatMap(
1037
1090
  function word(segment) {
1038
1091
  if (segment.kind === "range") {
1039
1092
  return [segment.bounds[0] + ".\u2013" + segment.bounds[1] + "."];
@@ -1089,15 +1142,15 @@ function weekdayNumber(token) {
1089
1142
  function monthNumber(token) {
1090
1143
  return +token;
1091
1144
  }
1092
- function applyYear(description, ir, opts) {
1093
- const yearField = ir.pattern.year;
1145
+ function applyYear(description, schedule, opts) {
1146
+ const yearField = schedule.pattern.year;
1094
1147
  if (yearField === "*") {
1095
1148
  return description;
1096
1149
  }
1097
1150
  if (yearField.indexOf("/") !== -1) {
1098
1151
  return description + " " + stepYears(yearField, opts);
1099
1152
  }
1100
- if (foldedYear(ir) && ir.pattern.date !== "*") {
1153
+ if (foldedYear(schedule) && schedule.pattern.date !== "*") {
1101
1154
  return description;
1102
1155
  }
1103
1156
  if (yearField.indexOf(",") !== -1) {
@@ -1117,8 +1170,8 @@ function stepYears(yearField, opts) {
1117
1170
  }
1118
1171
  return phrase;
1119
1172
  }
1120
- function foldedYear(ir) {
1121
- const yearField = ir.pattern.year;
1173
+ function foldedYear(schedule) {
1174
+ const yearField = schedule.pattern.year;
1122
1175
  if (yearField === "*" || yearField.indexOf("/") !== -1 || yearField.indexOf("-") !== -1 || yearField.indexOf(",") !== -1) {
1123
1176
  return "";
1124
1177
  }
@@ -1135,9 +1188,6 @@ function segmentWords(segments) {
1135
1188
  return [segment.value];
1136
1189
  });
1137
1190
  }
1138
- function isOpenStep(field) {
1139
- return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
1140
- }
1141
1191
  function wordList(fires) {
1142
1192
  return fires.map(function digit(value) {
1143
1193
  return "" + value;