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/de.cjs CHANGED
@@ -42,11 +42,7 @@ var weekdayNumbers = {
42
42
  };
43
43
  var maxClockTimes = 6;
44
44
 
45
- // src/core/util.ts
46
- function isNonNegativeInteger(value) {
47
- const digits = /^\d+$/;
48
- return digits.test(value);
49
- }
45
+ // src/core/cadence.ts
50
46
  function arithmeticStep(values) {
51
47
  if (values.length < 5) {
52
48
  return null;
@@ -62,6 +58,56 @@ function arithmeticStep(values) {
62
58
  }
63
59
  return { start: values[0], interval, last: values[values.length - 1] };
64
60
  }
61
+ function segmentsOf(schedule, field) {
62
+ return schedule.analyses.segments[field] ?? [];
63
+ }
64
+ function stepSegment(schedule, field) {
65
+ return segmentsOf(schedule, field)[0];
66
+ }
67
+ function singleValues(segments) {
68
+ const values = [];
69
+ for (const segment of segments) {
70
+ if (segment.kind !== "single") {
71
+ return null;
72
+ }
73
+ values.push(+segment.value);
74
+ }
75
+ return values;
76
+ }
77
+ function offsetCleanStride(stride) {
78
+ return stride.start < stride.interval && 24 % stride.interval === 0;
79
+ }
80
+ function renderStride(spec, parts) {
81
+ const { start, interval, cycle } = spec;
82
+ const tiles = cycle % interval === 0;
83
+ if (start === 0 && tiles) {
84
+ return parts.bare();
85
+ }
86
+ if (start < interval && tiles) {
87
+ return parts.offset();
88
+ }
89
+ return parts.bounded();
90
+ }
91
+ function hourListStride(values) {
92
+ if (values.length < 2) {
93
+ return null;
94
+ }
95
+ const interval = values[1] - values[0];
96
+ if (interval < 2) {
97
+ return null;
98
+ }
99
+ for (let i = 2; i < values.length; i += 1) {
100
+ if (values[i] - values[i - 1] !== interval) {
101
+ return null;
102
+ }
103
+ }
104
+ if (values[0] !== 0 && values.length < 5) {
105
+ return null;
106
+ }
107
+ return { interval, last: values[values.length - 1], start: values[0] };
108
+ }
109
+
110
+ // src/core/weekday.ts
65
111
  function weekdayDisplayKey(value) {
66
112
  return value === 0 ? 7 : value;
67
113
  }
@@ -82,10 +128,21 @@ function orderWeekdaysForDisplay(segments) {
82
128
  return pair[0];
83
129
  });
84
130
  }
131
+
132
+ // src/core/util.ts
133
+ function isNonNegativeInteger(value) {
134
+ const digits = /^\d+$/;
135
+ return digits.test(value);
136
+ }
85
137
  function toFieldNumber(token, numberMap) {
86
138
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
87
139
  }
88
140
 
141
+ // src/core/shapes.ts
142
+ function isOpenStep(field) {
143
+ return field.indexOf("/") !== -1 && field.indexOf("-") === -1 && field.indexOf(",") === -1;
144
+ }
145
+
89
146
  // src/lang/de/dialects.ts
90
147
  var months = [
91
148
  null,
@@ -145,24 +202,18 @@ function everyN(interval, unit) {
145
202
  function withAnchor(clause, anchor) {
146
203
  return anchor ? clause + " " + anchor : clause;
147
204
  }
148
- function stepSegment(segments) {
149
- return segments[0];
150
- }
151
205
  function cleanStep(segment, cycle) {
152
206
  return (segment.startToken === "*" || +segment.startToken === 0) && cycle % segment.interval === 0;
153
207
  }
154
- function renderStride(stride) {
208
+ function renderStride2(stride) {
155
209
  const { interval, start, last, cycle, unit, anchor } = stride;
156
210
  const cadence = everyN(interval, unit);
157
- const tiles = cycle % interval === 0;
158
- if (start === 0 && tiles) {
159
- return cadence;
160
- }
161
211
  const tail = anchor ? " " + anchor : "";
162
- if (start < interval && tiles) {
163
- return cadence + " ab " + unit.singular + " " + start + tail;
164
- }
165
- return cadence + " von " + unit.singular + " " + start + " bis " + last + tail;
212
+ return renderStride({ start, interval, cycle }, {
213
+ bare: () => cadence,
214
+ offset: () => cadence + " ab " + unit.singular + " " + start + tail,
215
+ bounded: () => cadence + " von " + unit.singular + " " + start + " bis " + last + tail
216
+ });
166
217
  }
167
218
  function stepClause(segment, unit, anchor) {
168
219
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
@@ -173,7 +224,7 @@ function stepClause(segment, unit, anchor) {
173
224
  anchor
174
225
  );
175
226
  }
176
- return renderStride({
227
+ return renderStride2({
177
228
  interval: segment.interval,
178
229
  start,
179
230
  last: segment.fires[segment.fires.length - 1],
@@ -182,20 +233,10 @@ function stepClause(segment, unit, anchor) {
182
233
  anchor
183
234
  });
184
235
  }
185
- function singleValues(segments) {
186
- const values = [];
187
- for (const segment of segments) {
188
- if (segment.kind !== "single") {
189
- return null;
190
- }
191
- values.push(+segment.value);
192
- }
193
- return values;
194
- }
195
236
  function strideFromSegments(segments, unit, anchor) {
196
237
  const values = singleValues(segments);
197
238
  const step = values && arithmeticStep(values);
198
- return step ? renderStride({ ...step, cycle: 60, unit, anchor }) : null;
239
+ return step ? renderStride2({ ...step, cycle: 60, unit, anchor }) : null;
199
240
  }
200
241
  var weekdayNames = [
201
242
  "sonntags",
@@ -206,9 +247,6 @@ var weekdayNames = [
206
247
  "freitags",
207
248
  "samstags"
208
249
  ];
209
- function fieldSegments(ir, field) {
210
- return ir.analyses.segments[field];
211
- }
212
250
  function flattenSteps(segments) {
213
251
  return segments.flatMap(function flat(segment) {
214
252
  return segment.kind === "step" ? segment.fires.map(function single(value) {
@@ -231,8 +269,8 @@ function weekdayName(token) {
231
269
  function weekdayRange(bounds) {
232
270
  return weekdayName(bounds[0]) + " bis " + weekdayName(bounds[1]);
233
271
  }
234
- function weekdayQualifier(ir) {
235
- const segments = orderWeekdaysForDisplay(fieldSegments(ir, "weekday"));
272
+ function weekdayQualifier(schedule) {
273
+ const segments = orderWeekdaysForDisplay(segmentsOf(schedule, "weekday"));
236
274
  if (segments.length === 1 && segments[0].kind === "range") {
237
275
  return weekdayRange(segments[0].bounds);
238
276
  }
@@ -295,26 +333,39 @@ function quartzDate(field) {
295
333
  }
296
334
  return null;
297
335
  }
336
+ function oddEvenDay(dateField) {
337
+ if (!isOpenStep(dateField)) {
338
+ return null;
339
+ }
340
+ const [start, step] = dateField.split("/");
341
+ if (+step !== 2) {
342
+ return null;
343
+ }
344
+ if (start === "*" || start === "1") {
345
+ return "an jedem ungeraden Tag des Monats";
346
+ }
347
+ return start === "2" ? "an jedem geraden Tag des Monats" : null;
348
+ }
298
349
  function monthName(token, months2) {
299
350
  return months2[+token];
300
351
  }
301
352
  function monthRange(bounds, months2) {
302
353
  return "von " + monthName(bounds[0], months2) + " bis " + monthName(bounds[1], months2);
303
354
  }
304
- function monthNamesList(ir, months2) {
305
- return joinList(flattenSteps(fieldSegments(ir, "month")).map(function name(segment) {
355
+ function monthNamesList(schedule, months2) {
356
+ return joinList(flattenSteps(segmentsOf(schedule, "month")).map(function name(segment) {
306
357
  return segment.kind === "range" ? monthRange(segment.bounds, months2) : monthName(segment.value, months2);
307
358
  }));
308
359
  }
309
- function monthClause(ir, months2) {
310
- const segments = flattenSteps(fieldSegments(ir, "month"));
360
+ function monthClause(schedule, months2) {
361
+ const segments = flattenSteps(segmentsOf(schedule, "month"));
311
362
  if (segments.length === 1 && segments[0].kind === "range") {
312
363
  return monthRange(segments[0].bounds, months2);
313
364
  }
314
- return "im " + monthNamesList(ir, months2);
365
+ return "im " + monthNamesList(schedule, months2);
315
366
  }
316
- function monthScope(ir, months2) {
317
- return ir.pattern.month === "*" ? "" : " " + monthClause(ir, months2);
367
+ function monthScope(schedule, months2) {
368
+ return schedule.pattern.month === "*" ? "" : " " + monthClause(schedule, months2);
318
369
  }
319
370
  function ordinalDay(value) {
320
371
  return value + ".";
@@ -322,8 +373,8 @@ function ordinalDay(value) {
322
373
  function dateRange(bounds) {
323
374
  return "vom " + ordinalDay(bounds[0]) + " bis zum " + ordinalDay(bounds[1]);
324
375
  }
325
- function dateClauseBare(ir) {
326
- const segments = flattenSteps(fieldSegments(ir, "date"));
376
+ function dateClauseBare(schedule) {
377
+ const segments = flattenSteps(segmentsOf(schedule, "date"));
327
378
  if (segments.length === 1 && segments[0].kind === "range") {
328
379
  return dateRange(segments[0].bounds);
329
380
  }
@@ -336,13 +387,13 @@ function dateClauseBare(ir) {
336
387
  return segment.kind === "range" ? dateRange(segment.bounds) : "am " + ordinalDay(segment.value);
337
388
  }));
338
389
  }
339
- function datePhrase(ir, months2) {
340
- const clause = dateClauseBare(ir);
341
- if (ir.pattern.month === "*") {
390
+ function datePhrase(schedule, months2) {
391
+ const clause = dateClauseBare(schedule);
392
+ if (schedule.pattern.month === "*") {
342
393
  return clause;
343
394
  }
344
- const monthRanged = flattenSteps(fieldSegments(ir, "month")).some((segment) => segment.kind === "range");
345
- return monthRanged ? clause + ", " + monthClause(ir, months2) : clause + " " + monthNamesList(ir, months2);
395
+ const monthRanged = flattenSteps(segmentsOf(schedule, "month")).some((segment) => segment.kind === "range");
396
+ return monthRanged ? clause + ", " + monthClause(schedule, months2) : clause + " " + monthNamesList(schedule, months2);
346
397
  }
347
398
  function bareTime(time, sep) {
348
399
  if (time.second) {
@@ -361,32 +412,35 @@ function timesPhrase(times, sep) {
361
412
  function hourWindow(from, to, last, sep) {
362
413
  return "von " + bareTime({ hour: from, minute: 0 }, sep) + " bis " + bareTime({ hour: to, minute: last }, sep) + " Uhr";
363
414
  }
364
- function fieldValues(ir, field) {
365
- return flattenSteps(fieldSegments(ir, field)).map(function value(segment) {
415
+ function fieldValues(schedule, field) {
416
+ return flattenSteps(segmentsOf(schedule, field)).map(function value(segment) {
366
417
  return segment.kind === "range" ? segment.bounds[0] + " bis " + segment.bounds[1] : String(segment.value);
367
418
  });
368
419
  }
369
- function countedPhrase(ir, field, singular, plural) {
370
- if (ir.shapes[field] === "single") {
371
- return "in " + singular + " " + ir.pattern[field];
420
+ function countedPhrase(schedule, field, singular, plural) {
421
+ if (schedule.shapes[field] === "single") {
422
+ return "in " + singular + " " + schedule.pattern[field];
372
423
  }
373
- return "in den " + plural + " " + joinList(fieldValues(ir, field));
424
+ return "in den " + plural + " " + joinList(fieldValues(schedule, field));
374
425
  }
375
- function minuteAnchor(ir) {
376
- return ir.pattern.minute === "*" ? "jeder Minute" : "";
426
+ function minuteAnchor(schedule) {
427
+ return schedule.pattern.minute === "*" ? "jeder Minute" : "";
377
428
  }
378
- function secondsLead(ir) {
379
- return secondsClause(ir, minuteAnchor(ir));
429
+ function secondsLead(schedule) {
430
+ return secondsClause(schedule, minuteAnchor(schedule));
380
431
  }
381
- function secondsClause(ir, anchor) {
382
- if (ir.pattern.second === "*") {
432
+ function secondsClause(schedule, anchor) {
433
+ if (schedule.pattern.second === "*") {
383
434
  return "jede Sekunde";
384
435
  }
385
- const segments = ir.analyses.segments.second;
386
- if (ir.shapes.second === "step") {
387
- return stepClause(stepSegment(segments), UNITS.second, anchor);
436
+ const segments = schedule.analyses.segments.second;
437
+ if (schedule.shapes.second === "step") {
438
+ return stepClause(stepSegment(schedule, "second"), UNITS.second, anchor);
388
439
  }
389
- return strideFromSegments(segments, UNITS.second, anchor) ?? withAnchor(countedPhrase(ir, "second", "Sekunde", "Sekunden"), anchor);
440
+ return strideFromSegments(segments, UNITS.second, anchor) ?? withAnchor(
441
+ countedPhrase(schedule, "second", "Sekunde", "Sekunden"),
442
+ anchor
443
+ );
390
444
  }
391
445
  function spanTime(hour, minute, sep) {
392
446
  return hour + sep + pad(minute);
@@ -394,8 +448,8 @@ function spanTime(hour, minute, sep) {
394
448
  function atHours(hours) {
395
449
  return "um " + joinList(hours.map(String)) + " Uhr";
396
450
  }
397
- function hourFires(ir) {
398
- return flattenSteps(fieldSegments(ir, "hour")).map(function fire(segment) {
451
+ function hourFires(schedule) {
452
+ return flattenSteps(segmentsOf(schedule, "hour")).map(function fire(segment) {
399
453
  return segment.kind === "range" ? +segment.bounds[0] : +segment.value;
400
454
  });
401
455
  }
@@ -405,8 +459,8 @@ function partTime(hour, minute, second, sep) {
405
459
  }
406
460
  return minute === 0 ? String(hour) : hour + sep + pad(minute);
407
461
  }
408
- function hourSegmentParts(ir, minute, second, sep) {
409
- return fieldSegments(ir, "hour").map(function part(segment) {
462
+ function hourSegmentParts(schedule, minute, second, sep) {
463
+ return segmentsOf(schedule, "hour").map(function part(segment) {
410
464
  if (segment.kind === "range") {
411
465
  return "von " + partTime(+segment.bounds[0], minute, second, sep) + " bis " + partTime(+segment.bounds[1], minute, second, sep) + " Uhr";
412
466
  }
@@ -418,13 +472,13 @@ function hourSegmentParts(ir, minute, second, sep) {
418
472
  return "um " + partTime(+segment.value, minute, second, sep) + " Uhr";
419
473
  });
420
474
  }
421
- function duringWindows(ir, times, sep) {
475
+ function duringWindows(schedule, times, sep) {
422
476
  if (times.kind === "fires") {
423
477
  return times.fires.map(function each(hour) {
424
478
  return hourWindow(hour, hour, 59, sep);
425
479
  });
426
480
  }
427
- return fieldSegments(ir, "hour").flatMap(function part(segment) {
481
+ return segmentsOf(schedule, "hour").flatMap(function part(segment) {
428
482
  if (segment.kind === "range") {
429
483
  return [hourWindow(+segment.bounds[0], +segment.bounds[1], 59, sep)];
430
484
  }
@@ -436,8 +490,8 @@ function duringWindows(ir, times, sep) {
436
490
  return [hourWindow(+segment.value, +segment.value, 59, sep)];
437
491
  });
438
492
  }
439
- function duringHours(ir, times, sep) {
440
- const windows = duringWindows(ir, times, sep);
493
+ function duringHours(schedule, times, sep) {
494
+ const windows = duringWindows(schedule, times, sep);
441
495
  if (windows.length <= 3 || times.kind !== "fires") {
442
496
  return joinList(windows);
443
497
  }
@@ -452,24 +506,24 @@ function renderEveryMinute() {
452
506
  function renderEveryHour() {
453
507
  return everyUnit(UNITS.hour);
454
508
  }
455
- function renderSeconds(ir) {
456
- return secondsLead(ir);
509
+ function renderSeconds(schedule) {
510
+ return secondsLead(schedule);
457
511
  }
458
- function minutePastClause(ir) {
512
+ function minutePastClause(schedule) {
459
513
  return strideFromSegments(
460
- fieldSegments(ir, "minute"),
514
+ segmentsOf(schedule, "minute"),
461
515
  UNITS.minute,
462
516
  "jeder Stunde"
463
- ) ?? countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde";
517
+ ) ?? countedPhrase(schedule, "minute", "Minute", "Minuten") + " jeder Stunde";
464
518
  }
465
- function renderMinutePast(ir) {
466
- return minutePastClause(ir);
519
+ function renderMinutePast(schedule) {
520
+ return minutePastClause(schedule);
467
521
  }
468
- function renderSecondsWithinMinute(ir, plan) {
522
+ function renderSecondsWithinMinute(schedule, plan) {
469
523
  if (plan.singleSecond) {
470
- return "in Minute " + ir.pattern.minute + " und Sekunde " + ir.pattern.second + " jeder Stunde";
524
+ return "in Minute " + schedule.pattern.minute + " und Sekunde " + schedule.pattern.second + " jeder Stunde";
471
525
  }
472
- return secondsLead(ir) + ", in Minute " + ir.pattern.minute + " jeder Stunde";
526
+ return secondsLead(schedule) + ", in Minute " + schedule.pattern.minute + " jeder Stunde";
473
527
  }
474
528
  function wholeHour(hour) {
475
529
  if (hour === 0) {
@@ -480,39 +534,39 @@ function wholeHour(hour) {
480
534
  }
481
535
  return "der " + hour + "-Uhr-Stunde";
482
536
  }
483
- function renderMinuteSpanInHour(ir, plan, opts) {
484
- if (ir.pattern.minute === "*") {
537
+ function renderMinuteSpanInHour(schedule, plan, opts) {
538
+ if (schedule.pattern.minute === "*") {
485
539
  return "jede Minute " + wholeHour(plan.hour);
486
540
  }
487
541
  const sep = opts.style.sep;
488
542
  return "jede Minute von " + spanTime(plan.hour, plan.span[0], sep) + " bis " + spanTime(plan.hour, plan.span[1], sep) + " Uhr";
489
543
  }
490
- function isEveryOtherMinuteSeconds(ir, plan) {
491
- if (plan.rest.kind !== "minuteFrequency" || ir.shapes.second !== "wildcard" || ir.shapes.hour !== "wildcard") {
544
+ function isEveryOtherMinuteSeconds(schedule, plan) {
545
+ if (plan.rest.kind !== "minuteFrequency" || schedule.shapes.second !== "wildcard" || schedule.shapes.hour !== "wildcard") {
492
546
  return false;
493
547
  }
494
- const minuteStep = stepSegment(ir.analyses.segments.minute);
548
+ const minuteStep = stepSegment(schedule, "minute");
495
549
  return minuteStep.startToken === "*" && minuteStep.interval === 2;
496
550
  }
497
- function renderComposeSeconds(ir, plan, opts) {
498
- if ((plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes") && ir.shapes.minute === "single") {
499
- const minute = +ir.pattern.minute;
500
- const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
551
+ function renderComposeSeconds(schedule, plan, opts) {
552
+ if ((plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes") && schedule.shapes.minute === "single") {
553
+ const minute = +schedule.pattern.minute;
554
+ const cadence = hourCadence(schedule, minute) ?? hourRangeCadence(schedule, minute);
501
555
  if (cadence !== null) {
502
556
  return cadence;
503
557
  }
504
558
  }
505
- if (composeMinuteZero(ir, plan)) {
506
- return secondsLead(ir) + " " + clockMinuteGenitive(plan.rest.times, opts.style.sep);
559
+ if (composeMinuteZero(schedule, plan)) {
560
+ return secondsLead(schedule) + " " + clockMinuteGenitive(plan.rest.times, opts.style.sep);
507
561
  }
508
- if (isEveryOtherMinuteSeconds(ir, plan)) {
509
- return secondsLead(ir) + " jeder zweiten Minute";
562
+ if (isEveryOtherMinuteSeconds(schedule, plan)) {
563
+ return secondsLead(schedule) + " jeder zweiten Minute";
510
564
  }
511
- const restOwnsLead = plan.rest.kind === "compactClockTimes" && ir.analyses.clockSecond;
512
- const lead = restOwnsLead ? "" : secondsLead(ir) + ", ";
513
- return lead + render(ir, plan.rest, opts);
565
+ const restOwnsLead = plan.rest.kind === "compactClockTimes" && schedule.analyses.clockSecond;
566
+ const lead = restOwnsLead ? "" : secondsLead(schedule) + ", ";
567
+ return lead + render(schedule, plan.rest, opts);
514
568
  }
515
- function composeMinuteZero(ir, plan) {
569
+ function composeMinuteZero(schedule, plan) {
516
570
  return plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0);
517
571
  }
518
572
  function clockMinuteGenitive(times, sep) {
@@ -521,44 +575,49 @@ function clockMinuteGenitive(times, sep) {
521
575
  });
522
576
  return clocks.length === 1 ? "der Minute " + clocks[0] : "der Minuten " + joinList(clocks);
523
577
  }
524
- function renderMinutesAcrossHours(ir, plan, opts) {
578
+ function renderMinutesAcrossHours(schedule, plan, opts) {
525
579
  const sep = opts.style.sep;
526
- const cadence = unevenHourCadence(ir);
580
+ const cadence = unevenHourCadence(schedule);
527
581
  if (plan.form === "wildcard") {
528
- return cadence ? "jede Minute, " + cadence : "jede Minute " + duringHours(ir, plan.times, sep);
582
+ return cadence ? "jede Minute, " + cadence : "jede Minute " + duringHours(schedule, plan.times, sep);
529
583
  }
530
- const minuteLead = strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten");
584
+ const minuteLead = strideFromSegments(segmentsOf(schedule, "minute"), UNITS.minute, "") ?? countedPhrase(schedule, "minute", "Minute", "Minuten");
531
585
  if (cadence !== null) {
532
586
  return minuteLead + ", " + cadence;
533
587
  }
534
- const hours = plan.times.kind === "fires" ? atHours(plan.times.fires) : joinList(hourSegmentParts(ir, 0, 0, sep));
588
+ const hours = plan.times.kind === "fires" ? atHours(plan.times.fires) : joinList(hourSegmentParts(schedule, 0, 0, sep));
535
589
  return minuteLead + ", " + hours;
536
590
  }
537
- function renderMinuteSpanAcrossHourStep(ir, plan) {
538
- const cadence = unevenHourCadence(ir);
591
+ function renderMinuteSpanAcrossHourStep(schedule, plan) {
592
+ const cadence = unevenHourCadence(schedule);
539
593
  if (plan.form === "wildcard") {
540
- return "jede Minute " + everyNthHour(stepSegment(ir.analyses.segments.hour));
594
+ return "jede Minute " + everyNthHour(stepSegment(schedule, "hour"));
541
595
  }
542
- const segment = stepSegment(ir.analyses.segments.hour);
596
+ const segment = stepSegment(schedule, "hour");
543
597
  const hours = cadence ?? (confinedHourStride(segment) ? everyNthHour(segment) : atHours(segment.fires));
544
- return (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
598
+ return (strideFromSegments(segmentsOf(schedule, "minute"), UNITS.minute, "") ?? countedPhrase(schedule, "minute", "Minute", "Minuten")) + ", " + hours;
545
599
  }
546
- function renderCompactClockTimes(ir, plan, opts) {
600
+ function renderCompactClockTimes(schedule, plan, opts) {
547
601
  const sep = opts.style.sep;
548
602
  if (plan.fold) {
549
- const cadence = hourCadence(ir, plan.minute) ?? hourRangeCadence(ir, plan.minute);
603
+ const cadence = hourCadence(schedule, plan.minute) ?? hourRangeCadence(schedule, plan.minute);
550
604
  if (cadence !== null) {
551
605
  return cadence;
552
606
  }
553
- const hourly = fieldSegments(ir, "hour").some((segment) => segment.kind === "range");
554
- return (hourly ? "st\xFCndlich " : "t\xE4glich ") + joinList(hourSegmentParts(ir, plan.minute, ir.analyses.clockSecond, sep));
607
+ const hourly = segmentsOf(schedule, "hour").some((segment) => segment.kind === "range");
608
+ return (hourly ? "st\xFCndlich " : "t\xE4glich ") + joinList(hourSegmentParts(
609
+ schedule,
610
+ plan.minute,
611
+ schedule.analyses.clockSecond,
612
+ sep
613
+ ));
555
614
  }
556
- const hours = unevenHourCadence(ir) ?? (fieldSegments(ir, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(ir, 0, 0, sep)) : atHours(hourFires(ir)));
557
- const lead = ir.analyses.clockSecond ? countedPhrase(ir, "second", "Sekunde", "Sekunden") + ", " : "";
558
- return lead + (strideFromSegments(fieldSegments(ir, "minute"), UNITS.minute, "") ?? countedPhrase(ir, "minute", "Minute", "Minuten")) + ", " + hours;
615
+ const hours = unevenHourCadence(schedule) ?? (segmentsOf(schedule, "hour").some((segment) => segment.kind === "range") ? joinList(hourSegmentParts(schedule, 0, 0, sep)) : atHours(hourFires(schedule)));
616
+ const lead = schedule.analyses.clockSecond ? countedPhrase(schedule, "second", "Sekunde", "Sekunden") + ", " : "";
617
+ return lead + (strideFromSegments(segmentsOf(schedule, "minute"), UNITS.minute, "") ?? countedPhrase(schedule, "minute", "Minute", "Minuten")) + ", " + hours;
559
618
  }
560
- function renderMinuteFrequency(ir, plan, opts) {
561
- const segment = stepSegment(ir.analyses.segments.minute);
619
+ function renderMinuteFrequency(schedule, plan, opts) {
620
+ const segment = stepSegment(schedule, "minute");
562
621
  const sep = opts.style.sep;
563
622
  const clean = cleanStep(segment, 60);
564
623
  if (plan.hours.kind === "window") {
@@ -578,68 +637,44 @@ function renderMinuteFrequency(ir, plan, opts) {
578
637
  }
579
638
  const base = stepClause(segment, UNITS.minute, "jeder Stunde");
580
639
  if (plan.hours.kind === "during") {
581
- const cadence = unevenHourCadence(ir);
582
- return cadence ? base + ", " + cadence : base + " " + duringHours(ir, plan.hours.times, sep);
640
+ const cadence = unevenHourCadence(schedule);
641
+ return cadence ? base + ", " + cadence : base + " " + duringHours(schedule, plan.hours.times, sep);
583
642
  }
584
643
  if (plan.hours.kind === "step") {
585
- return base + " " + everyNthHour(stepSegment(ir.analyses.segments.hour));
644
+ return base + " " + everyNthHour(stepSegment(schedule, "hour"));
586
645
  }
587
646
  return base;
588
647
  }
589
- function hourStepPhrase(ir) {
590
- const cadence = unevenHourCadence(ir);
648
+ function hourStepPhrase(schedule) {
649
+ const cadence = unevenHourCadence(schedule);
591
650
  if (cadence !== null) {
592
651
  return cadence;
593
652
  }
594
- const segment = stepSegment(ir.analyses.segments.hour);
653
+ const segment = stepSegment(schedule, "hour");
595
654
  if (cleanStep(segment, 24)) {
596
655
  return everyN(segment.interval, UNITS.hour);
597
656
  }
598
- const stride = openOffsetCleanStride(ir, segment);
657
+ const stride = openOffsetCleanStride(schedule, segment);
599
658
  return stride ? hourStrideCadence(stride) : atHours(segment.fires);
600
659
  }
601
- function openOffsetCleanStride(ir, segment) {
660
+ function openOffsetCleanStride(schedule, segment) {
602
661
  if (segment.startToken.indexOf("-") !== -1) {
603
662
  return null;
604
663
  }
605
- const stride = hourStride(ir);
664
+ const stride = hourStride(schedule);
606
665
  return stride && offsetCleanStride(stride) ? stride : null;
607
666
  }
608
667
  function hourStrideCadence(stride) {
609
668
  const { start, interval, last } = stride;
610
669
  const cadence = everyN(interval, UNITS.hour);
611
- const tiles = 24 % interval === 0;
612
- if (start === 0 && tiles) {
613
- return cadence;
614
- }
615
- if (start < interval && tiles) {
616
- return cadence + " ab " + start + " Uhr";
617
- }
618
- return cadence + " von " + start + " bis " + last + " Uhr";
619
- }
620
- function hourListStride(values) {
621
- if (values.length < 2) {
622
- return null;
623
- }
624
- const interval = values[1] - values[0];
625
- if (interval < 2) {
626
- return null;
627
- }
628
- for (let i = 2; i < values.length; i += 1) {
629
- if (values[i] - values[i - 1] !== interval) {
630
- return null;
631
- }
632
- }
633
- if (values[0] !== 0 && values.length < 5) {
634
- return null;
635
- }
636
- return { interval, last: values[values.length - 1], start: values[0] };
637
- }
638
- function offsetCleanStride(stride) {
639
- return stride.start < stride.interval && 24 % stride.interval === 0;
670
+ return renderStride({ start, interval, cycle: 24 }, {
671
+ bare: () => cadence,
672
+ offset: () => cadence + " ab " + start + " Uhr",
673
+ bounded: () => cadence + " von " + start + " bis " + last + " Uhr"
674
+ });
640
675
  }
641
- function hourStride(ir) {
642
- const segments = fieldSegments(ir, "hour");
676
+ function hourStride(schedule) {
677
+ const segments = segmentsOf(schedule, "hour");
643
678
  if (!segments) {
644
679
  return null;
645
680
  }
@@ -654,89 +689,95 @@ function hourStride(ir) {
654
689
  const values = singleValues(segments);
655
690
  return values && hourListStride(values);
656
691
  }
657
- function unevenHourCadence(ir) {
658
- const stride = hourStride(ir);
692
+ function unevenHourCadence(schedule) {
693
+ const stride = hourStride(schedule);
659
694
  if (!stride || offsetCleanStride(stride)) {
660
695
  return null;
661
696
  }
662
697
  return hourStrideCadence(stride);
663
698
  }
664
- function subMinuteSecond(ir) {
665
- return ir.pattern.second === "*" || ir.shapes.second === "step";
699
+ function subMinuteSecond(schedule) {
700
+ return schedule.pattern.second === "*" || schedule.shapes.second === "step";
666
701
  }
667
- function hourCadenceLead(ir, minute) {
702
+ function hourCadenceLead(schedule, minute) {
668
703
  if (minute === 0) {
669
- if (subMinuteSecond(ir)) {
670
- return withAnchor(secondsClause(ir, minuteAnchor(ir)), "f\xFCr eine Minute");
704
+ if (subMinuteSecond(schedule)) {
705
+ return withAnchor(
706
+ secondsClause(schedule, minuteAnchor(schedule)),
707
+ "f\xFCr eine Minute"
708
+ );
671
709
  }
672
- return secondsClause(ir, "jeder Stunde");
710
+ return secondsClause(schedule, "jeder Stunde");
673
711
  }
674
712
  const minutePhrase = "in Minute " + minute;
675
- if (ir.pattern.second === "0") {
713
+ if (schedule.pattern.second === "0") {
676
714
  return minutePhrase;
677
715
  }
678
- return secondsClause(ir, minuteAnchor(ir)) + ", " + minutePhrase;
716
+ return secondsClause(schedule, minuteAnchor(schedule)) + ", " + minutePhrase;
679
717
  }
680
- function hourCadence(ir, minute) {
681
- const stride = hourStride(ir);
718
+ function hourCadence(schedule, minute) {
719
+ const stride = hourStride(schedule);
682
720
  if (!stride) {
683
721
  return null;
684
722
  }
685
723
  const fires = (stride.last - stride.start) / stride.interval + 1;
686
- if (ir.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
724
+ if (schedule.pattern.second === "0" && fires <= maxClockTimes && offsetCleanStride(stride)) {
687
725
  return null;
688
726
  }
689
- const segment = fieldSegments(ir, "hour")[0];
690
- const confined = minute === 0 && subMinuteSecond(ir) && fieldSegments(ir, "hour").length === 1 && segment.kind === "step" && confinedHourStride(segment);
727
+ const segment = segmentsOf(schedule, "hour")[0];
728
+ const confined = minute === 0 && subMinuteSecond(schedule) && segmentsOf(schedule, "hour").length === 1 && segment.kind === "step" && confinedHourStride(segment);
691
729
  if (confined) {
692
- return withAnchor(secondsClause(ir, minuteAnchor(ir)), "f\xFCr eine Minute") + " " + everyNthHour(segment);
730
+ return withAnchor(
731
+ secondsClause(schedule, minuteAnchor(schedule)),
732
+ "f\xFCr eine Minute"
733
+ ) + " " + everyNthHour(segment);
693
734
  }
694
- if (minute === 0 && ir.pattern.second === "0") {
735
+ if (minute === 0 && schedule.pattern.second === "0") {
695
736
  return hourStrideCadence(stride);
696
737
  }
697
- return hourCadenceLead(ir, minute) + ", " + hourStrideCadence(stride);
738
+ return hourCadenceLead(schedule, minute) + ", " + hourStrideCadence(stride);
698
739
  }
699
- function hourCadenceApplies(ir) {
700
- if (ir.shapes.minute !== "single") {
740
+ function hourCadenceApplies(schedule) {
741
+ if (schedule.shapes.minute !== "single") {
701
742
  return false;
702
743
  }
703
- const minute = +ir.pattern.minute;
704
- return hourCadence(ir, minute) !== null || hourRangeCadence(ir, minute) !== null;
744
+ const minute = +schedule.pattern.minute;
745
+ return hourCadence(schedule, minute) !== null || hourRangeCadence(schedule, minute) !== null;
705
746
  }
706
- function hasHourWindow(ir) {
707
- const segments = fieldSegments(ir, "hour");
747
+ function hasHourWindow(schedule) {
748
+ const segments = segmentsOf(schedule, "hour");
708
749
  return !!segments && segments.some(function range(segment) {
709
750
  return segment.kind === "range";
710
751
  });
711
752
  }
712
- function hourRangeCadence(ir, minute) {
713
- if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === "0") {
753
+ function hourRangeCadence(schedule, minute) {
754
+ if (minute !== 0 || !hasHourWindow(schedule) || schedule.pattern.second === "0") {
714
755
  return null;
715
756
  }
716
- return hourCadenceLead(ir, minute) + ", " + hourRangeWindowTail(ir);
757
+ return hourCadenceLead(schedule, minute) + ", " + hourRangeWindowTail(schedule);
717
758
  }
718
- function hourRangeWindowTail(ir) {
719
- return joinList(hourSegmentParts(ir, 0, 0, ":"));
759
+ function hourRangeWindowTail(schedule) {
760
+ return joinList(hourSegmentParts(schedule, 0, 0, ":"));
720
761
  }
721
- function renderHourRange(ir, plan, opts) {
762
+ function renderHourRange(schedule, plan, opts) {
722
763
  const last = plan.minuteForm === "wildcard" ? plan.boundMinute ?? 0 : 0;
723
764
  const window = hourWindow(plan.from, plan.to, last, opts.style.sep);
724
765
  if (plan.minuteForm === "wildcard") {
725
766
  return "jede Minute " + window;
726
767
  }
727
- if (plan.minuteForm === "lead" && ir.pattern.minute === "0") {
768
+ if (plan.minuteForm === "lead" && schedule.pattern.minute === "0") {
728
769
  return "st\xFCndlich " + window;
729
770
  }
730
771
  return (strideFromSegments(
731
- fieldSegments(ir, "minute"),
772
+ segmentsOf(schedule, "minute"),
732
773
  UNITS.minute,
733
774
  "jeder Stunde"
734
- ) ?? countedPhrase(ir, "minute", "Minute", "Minuten") + " jeder Stunde") + ", " + window;
775
+ ) ?? countedPhrase(schedule, "minute", "Minute", "Minuten") + " jeder Stunde") + ", " + window;
735
776
  }
736
- function renderClockTimes(ir, plan, opts) {
737
- if (ir.shapes.minute === "single") {
738
- const minute = +ir.pattern.minute;
739
- const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
777
+ function renderClockTimes(schedule, plan, opts) {
778
+ if (schedule.shapes.minute === "single") {
779
+ const minute = +schedule.pattern.minute;
780
+ const cadence = hourCadence(schedule, minute) ?? hourRangeCadence(schedule, minute);
740
781
  if (cadence !== null) {
741
782
  return cadence;
742
783
  }
@@ -763,48 +804,75 @@ var renderers = {
763
804
  singleMinute: renderMinutePast,
764
805
  standaloneSeconds: renderSeconds
765
806
  };
766
- function qualifier(ir, months2) {
767
- const { date, month, weekday } = ir.pattern;
768
- if (date !== "*" && weekday !== "*") {
769
- const datePart = quartzDate(date) || dateClauseBare(ir);
770
- const weekdayPart = quartzWeekday(weekday) || weekdayQualifier(ir);
771
- return datePart + " oder " + weekdayPart + monthScope(ir, months2);
807
+ function isDayUnion(schedule) {
808
+ return schedule.pattern.date !== "*" && schedule.pattern.weekday !== "*";
809
+ }
810
+ function dayUnionMonthLead(schedule, months2) {
811
+ return schedule.pattern.month === "*" ? "" : monthClause(schedule, months2) + " ";
812
+ }
813
+ function dayUnionDate(schedule) {
814
+ return quartzDate(schedule.pattern.date) || oddEvenDay(schedule.pattern.date) || dateClauseBare(schedule);
815
+ }
816
+ function dayUnionWeekday(schedule) {
817
+ const weekday = schedule.pattern.weekday;
818
+ const quartz = quartzWeekday(weekday);
819
+ if (quartz) {
820
+ return quartz;
821
+ }
822
+ const segments = segmentsOf(schedule, "weekday");
823
+ if (segments.length === 1 && segments[0].kind === "range" && segments[0].bounds[0] === "1" && segments[0].bounds[1] === "5") {
824
+ return "an einem Wochentag (Mo\u2013Fr)";
825
+ }
826
+ return weekdayQualifier(schedule);
827
+ }
828
+ function dateStepCadence(schedule) {
829
+ const date = schedule.pattern.date;
830
+ if (!isOpenStep(date)) {
831
+ return null;
832
+ }
833
+ const [start, step] = date.split("/");
834
+ return (start === "*" || start === "1") && +step === 2 ? "jeden zweiten Tag des Monats" : null;
835
+ }
836
+ function qualifier(schedule, months2) {
837
+ const { date, month, weekday } = schedule.pattern;
838
+ if (isDayUnion(schedule)) {
839
+ return dayUnionDate(schedule) + " oder " + dayUnionWeekday(schedule);
772
840
  }
773
841
  if (weekday !== "*") {
774
- return (quartzWeekday(weekday) || weekdayQualifier(ir)) + monthScope(ir, months2);
842
+ return (quartzWeekday(weekday) || weekdayQualifier(schedule)) + monthScope(schedule, months2);
775
843
  }
776
844
  if (date !== "*") {
777
- const quartz = quartzDate(date);
778
- return quartz ? quartz + monthScope(ir, months2) : datePhrase(ir, months2);
845
+ const quartz = quartzDate(date) || dateStepCadence(schedule);
846
+ return quartz ? quartz + monthScope(schedule, months2) : datePhrase(schedule, months2);
779
847
  }
780
848
  if (month !== "*") {
781
- return monthClause(ir, months2);
849
+ return monthClause(schedule, months2);
782
850
  }
783
851
  return "";
784
852
  }
785
853
  var LEADING_PLANS = /* @__PURE__ */ new Set(["clockTimes"]);
786
- function leadsQualifier(ir) {
787
- return LEADING_PLANS.has(ir.plan.kind) || isComposeMinuteZero(ir);
854
+ function leadsQualifier(schedule) {
855
+ return LEADING_PLANS.has(schedule.plan.kind) || isComposeMinuteZero(schedule);
788
856
  }
789
- function isComposeMinuteZero(ir) {
790
- return ir.plan.kind === "composeSeconds" && composeMinuteZero(ir, ir.plan);
857
+ function isComposeMinuteZero(schedule) {
858
+ return schedule.plan.kind === "composeSeconds" && composeMinuteZero(schedule, schedule.plan);
791
859
  }
792
- function needsDailyFrame(ir) {
793
- if (hourCadenceApplies(ir)) {
860
+ function needsDailyFrame(schedule) {
861
+ if (hourCadenceApplies(schedule)) {
794
862
  return false;
795
863
  }
796
- if (ir.plan.kind === "clockTimes" || isComposeMinuteZero(ir)) {
864
+ if (schedule.plan.kind === "clockTimes" || isComposeMinuteZero(schedule)) {
797
865
  return true;
798
866
  }
799
- if (ir.plan.kind !== "hourStep") {
867
+ if (schedule.plan.kind !== "hourStep") {
800
868
  return false;
801
869
  }
802
- const segment = stepSegment(ir.analyses.segments.hour);
803
- return !cleanStep(segment, 24) && !openOffsetCleanStride(ir, segment);
870
+ const segment = stepSegment(schedule, "hour");
871
+ return !cleanStep(segment, 24) && !openOffsetCleanStride(schedule, segment);
804
872
  }
805
- function render(ir, plan, opts) {
873
+ function render(schedule, plan, opts) {
806
874
  return renderers[plan.kind](
807
- ir,
875
+ schedule,
808
876
  plan,
809
877
  opts
810
878
  );
@@ -821,8 +889,8 @@ function normalizeOptions(options) {
821
889
  years: !!options.years
822
890
  };
823
891
  }
824
- function applyYear(description, ir) {
825
- const year = ir.pattern.year;
892
+ function applyYear(description, schedule) {
893
+ const year = schedule.pattern.year;
826
894
  if (year === "*") {
827
895
  return description;
828
896
  }
@@ -835,16 +903,19 @@ function applyYear(description, ir) {
835
903
  }
836
904
  return description + " im Jahr " + year;
837
905
  }
838
- function describe(ir, opts) {
839
- const core = render(ir, ir.plan, opts);
840
- const qual = qualifier(ir, opts.style.months);
906
+ function describe(schedule, opts) {
907
+ const core = render(schedule, schedule.plan, opts);
908
+ const qual = qualifier(schedule, opts.style.months);
841
909
  let base = core;
842
910
  if (qual) {
843
- base = leadsQualifier(ir) ? qual + " " + core : core + " " + qual;
844
- } else if (needsDailyFrame(ir)) {
911
+ base = leadsQualifier(schedule) ? qual + " " + core : core + " " + qual;
912
+ } else if (needsDailyFrame(schedule)) {
845
913
  base = "t\xE4glich " + core;
846
914
  }
847
- return applyYear(base, ir);
915
+ if (isDayUnion(schedule)) {
916
+ base = dayUnionMonthLead(schedule, opts.style.months) + base;
917
+ }
918
+ return applyYear(base, schedule);
848
919
  }
849
920
  var de2 = {
850
921
  describe,