cronli5 0.1.4 → 0.1.5

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.
package/dist/lang/fi.js CHANGED
@@ -24,12 +24,28 @@ var weekdayNumbers = {
24
24
  FRI: 5,
25
25
  SAT: 6
26
26
  };
27
+ var maxClockTimes = 6;
27
28
 
28
29
  // src/core/util.ts
29
30
  function isNonNegativeInteger(value) {
30
31
  const digits = /^\d+$/;
31
32
  return digits.test(value);
32
33
  }
34
+ function arithmeticStep(values) {
35
+ if (values.length < 5) {
36
+ return null;
37
+ }
38
+ const interval = values[1] - values[0];
39
+ if (interval < 2) {
40
+ return null;
41
+ }
42
+ for (let i = 2; i < values.length; i += 1) {
43
+ if (values[i] - values[i - 1] !== interval) {
44
+ return null;
45
+ }
46
+ }
47
+ return { start: values[0], interval, last: values[values.length - 1] };
48
+ }
33
49
  function toFieldNumber(token, numberMap) {
34
50
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
35
51
  }
@@ -165,12 +181,14 @@ var units = {
165
181
  mark: "joka tunti",
166
182
  anchor: "jokaisen tunnin",
167
183
  ela: "minuutista",
184
+ ill: "minuuttiin",
168
185
  gen: "minuutin"
169
186
  },
170
187
  second: {
171
188
  mark: "joka minuutti",
172
189
  anchor: "jokaisen minuutin",
173
190
  ela: "sekunnista",
191
+ ill: "sekuntiin",
174
192
  gen: "sekunnin"
175
193
  }
176
194
  };
@@ -226,29 +244,50 @@ function renderSecondsWithinMinute(ir, plan, opts) {
226
244
  }
227
245
  return secondsLeadClause(ir, opts) + ", " + atMarks(minuteField, units.minute, true) + trailingQualifier(ir, opts);
228
246
  }
247
+ function composeSecondsOverMinuteStep(ir, freq, opts) {
248
+ const seg = stepSegment(ir.analyses.segments.minute);
249
+ const stepPhrase = stepCycle60(seg, units.minute, opts);
250
+ if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
251
+ const bareHours = kloFromTimes(ir, freq.hours.times, opts);
252
+ return hoursFirstMinutes(bareHours, ir, opts) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
253
+ }
254
+ let hourClause = "";
255
+ if (freq.hours.kind === "during") {
256
+ hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
257
+ } else if (freq.hours.kind === "window") {
258
+ hourClause = " " + hourWindow(freq.hours, opts);
259
+ } else if (freq.hours.kind === "step") {
260
+ hourClause = " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
261
+ }
262
+ return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
263
+ }
264
+ function composeHourCadence(ir, plan, opts) {
265
+ const clockRest = plan.rest.kind === "clockTimes" || plan.rest.kind === "compactClockTimes";
266
+ return clockRest && ir.shapes.minute === "single" ? hourCadence(ir, +ir.pattern.minute, opts) : null;
267
+ }
229
268
  function renderComposeSeconds(ir, plan, opts) {
269
+ const cadence = composeHourCadence(ir, plan, opts);
270
+ if (cadence !== null) {
271
+ return cadence;
272
+ }
230
273
  if (plan.rest.kind === "minuteFrequency" && ir.pattern.second !== "*") {
231
- const freq = plan.rest;
232
- const seg = stepSegment(ir.analyses.segments.minute);
233
- const stepPhrase = stepCycle60(seg, units.minute, opts);
234
- let hourClause = "";
235
- if (freq.hours.kind === "during" && minuteStepIsAnchored(seg)) {
236
- const bareHours = kloFromTimes(ir, freq.hours.times, opts);
237
- return hoursFirstMinutes(bareHours, ir) + ", " + secondsLeadClause(ir, opts) + trailingQualifier(ir, opts);
238
- } else if (freq.hours.kind === "during" && !minuteStepIsAnchored(seg)) {
239
- hourClause = " " + hourWindowsFromTimes(ir, freq.hours.times, opts);
240
- } else if (freq.hours.kind === "window") {
241
- hourClause = " " + hourWindow(freq.hours, opts);
242
- } else if (freq.hours.kind === "step") {
243
- hourClause = " " + everyNthHour(stepSegment(ir.analyses.segments.hour), opts);
244
- }
245
- return stepPhrase + ", " + secondsLeadClause(ir, opts) + hourClause + trailingQualifier(ir, opts);
274
+ return composeSecondsOverMinuteStep(ir, plan.rest, opts);
246
275
  }
247
276
  if (plan.rest.kind === "clockTimes" && plan.rest.times.every((time) => +time.minute === 0)) {
248
277
  return composeMinuteZero(ir, plan.rest, opts);
249
278
  }
279
+ if (isEveryOtherMinuteSeconds(ir, plan)) {
280
+ return secondsLeadClause(ir, opts) + " joka toisena minuuttina";
281
+ }
250
282
  return secondsLeadClause(ir, opts) + ", " + render(ir, plan.rest, opts);
251
283
  }
284
+ function isEveryOtherMinuteSeconds(ir, plan) {
285
+ if (plan.rest.kind !== "minuteFrequency" || ir.pattern.second !== "*" || ir.shapes.hour !== "wildcard") {
286
+ return false;
287
+ }
288
+ const seg = stepSegment(ir.analyses.segments.minute);
289
+ return seg.startToken === "*" && seg.interval === 2;
290
+ }
252
291
  function composeMinuteZero(ir, rest, opts) {
253
292
  const clocks = rest.times.map(function clock(time) {
254
293
  return clockDigits(
@@ -277,7 +316,7 @@ function secondsLeadClause(ir, opts) {
277
316
  if (shape === "single") {
278
317
  return atMarks(secondField, units.second, marked);
279
318
  }
280
- return atMarks(
319
+ return strideFromSegments(ir.analyses.segments.second, units.second, opts) ?? atMarks(
281
320
  joinList(segmentWords(ir.analyses.segments.second)),
282
321
  units.second,
283
322
  marked
@@ -290,20 +329,20 @@ function renderSingleMinute(ir, plan, opts) {
290
329
  return atMarks(ir.pattern.minute, units.minute, true) + trailingQualifier(ir, opts);
291
330
  }
292
331
  function renderRangeOfMinutes(ir, plan, opts) {
293
- return minutesList(ir) + trailingQualifier(ir, opts);
332
+ return minutesList(ir, opts) + trailingQualifier(ir, opts);
294
333
  }
295
334
  function renderMultipleMinutes(ir, plan, opts) {
296
- return minutesList(ir) + trailingQualifier(ir, opts);
335
+ return minutesList(ir, opts) + trailingQualifier(ir, opts);
297
336
  }
298
- function minutesList(ir) {
299
- return atMarks(
337
+ function minutesList(ir, opts) {
338
+ return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
300
339
  joinList(segmentWords(ir.analyses.segments.minute)),
301
340
  units.minute,
302
341
  true
303
342
  );
304
343
  }
305
- function bareMinutes(ir) {
306
- return atMarks(
344
+ function bareMinutes(ir, opts) {
345
+ return strideFromSegments(ir.analyses.segments.minute, units.minute, opts) ?? atMarks(
307
346
  joinList(segmentWords(ir.analyses.segments.minute)),
308
347
  units.minute,
309
348
  false
@@ -334,7 +373,11 @@ function hoursAreRangeIsolated(segments) {
334
373
  }
335
374
  return hasRange && hasSingle;
336
375
  }
337
- function hoursFirstMinutes(hoursStr, ir) {
376
+ function hoursFirstMinutes(hoursStr, ir, opts) {
377
+ const stride = strideFromSegments(ir.analyses.segments.minute, units.minute, opts);
378
+ if (stride) {
379
+ return hoursStr + " aina " + stride;
380
+ }
338
381
  return hoursStr + " aina minuuttien " + joinList(segmentWords(ir.analyses.segments.minute)) + " kohdalla";
339
382
  }
340
383
  function hourSegmentTimesWithSeka(ir, minute, second, opts) {
@@ -357,7 +400,7 @@ function renderMinuteFrequency(ir, plan, opts) {
357
400
  if (plan.hours.kind === "during") {
358
401
  if (minuteStepIsAnchored(seg)) {
359
402
  const bareHours = kloFromTimes(ir, plan.hours.times, opts);
360
- return hoursFirstMinutes(bareHours, ir) + trailingQualifier(ir, opts);
403
+ return hoursFirstMinutes(bareHours, ir, opts) + trailingQualifier(ir, opts);
361
404
  }
362
405
  return stepCycle60(seg, units.minute, opts) + " " + hourWindowsFromTimes(ir, plan.hours.times, opts) + trailingQualifier(ir, opts);
363
406
  }
@@ -384,10 +427,10 @@ function renderMinutesAcrossHours(ir, plan, opts) {
384
427
  return "joka minuutti " + hourWindowsFromTimes(ir, plan.times, opts) + trailingQualifier(ir, opts);
385
428
  }
386
429
  if (hoursAreRangeIsolated(ir.analyses.segments.hour)) {
387
- return bareMinutes(ir) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
430
+ return bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
388
431
  }
389
432
  const hoursStr = kloFromTimes(ir, plan.times, opts);
390
- return hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
433
+ return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
391
434
  }
392
435
  function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
393
436
  const segment = stepSegment(ir.analyses.segments.hour);
@@ -396,9 +439,9 @@ function renderMinuteSpanAcrossHourStep(ir, plan, opts) {
396
439
  }
397
440
  if (segment.startToken.indexOf("-") !== -1) {
398
441
  const hoursStr = kloList(segment.fires, opts);
399
- return hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
442
+ return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
400
443
  }
401
- return bareMinutes(ir) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
444
+ return bareMinutes(ir, opts) + hourStepTail(segment, opts) + trailingQualifier(ir, opts);
402
445
  }
403
446
  function plainHourStep(segment) {
404
447
  return segment.startToken === "*" && 24 % segment.interval === 0;
@@ -456,7 +499,7 @@ function renderHourRange(ir, plan, opts) {
456
499
  return "joka minuutti " + window + trailingQualifier(ir, opts);
457
500
  }
458
501
  if (plan.minuteForm === "range") {
459
- return hoursFirstMinutes(window, ir) + trailingQualifier(ir, opts);
502
+ return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
460
503
  }
461
504
  if (ir.pattern.minute === "0") {
462
505
  return "joka tunti " + window + trailingQualifier(ir, opts);
@@ -468,7 +511,7 @@ function renderHourRange(ir, plan, opts) {
468
511
  opts
469
512
  ) + trailingQualifier(ir, opts);
470
513
  }
471
- return hoursFirstMinutes(window, ir) + trailingQualifier(ir, opts);
514
+ return hoursFirstMinutes(window, ir, opts) + trailingQualifier(ir, opts);
472
515
  }
473
516
  function renderHourStep(ir, plan, opts) {
474
517
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
@@ -484,6 +527,12 @@ function hourWindow(window, opts) {
484
527
  );
485
528
  }
486
529
  function renderClockTimes(ir, plan, opts) {
530
+ if (ir.shapes.minute === "single") {
531
+ const cadence = hourCadence(ir, +ir.pattern.minute, opts);
532
+ if (cadence !== null) {
533
+ return cadence;
534
+ }
535
+ }
487
536
  if (plan.times.length === 1) {
488
537
  const time = plan.times[0];
489
538
  return leadingQualifier(ir, opts) + timeWord(time.hour, time.minute, time.second, opts);
@@ -494,6 +543,12 @@ function renderClockTimes(ir, plan, opts) {
494
543
  return leadingQualifier(ir, opts) + "klo " + joinList(digits);
495
544
  }
496
545
  function renderCompactClockTimes(ir, plan, opts) {
546
+ if (plan.fold) {
547
+ const cadence = hourCadence(ir, plan.minute, opts);
548
+ if (cadence !== null) {
549
+ return cadence;
550
+ }
551
+ }
497
552
  const hourSegs = ir.analyses.segments.hour;
498
553
  if (hoursAreRangeIsolated(hourSegs)) {
499
554
  if (plan.fold) {
@@ -504,14 +559,14 @@ function renderCompactClockTimes(ir, plan, opts) {
504
559
  opts
505
560
  );
506
561
  }
507
- const phrase2 = bareMinutes(ir) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
562
+ const phrase2 = bareMinutes(ir, opts) + " " + hourSegmentTimesWithSeka(ir, 0, null, opts) + trailingQualifier(ir, opts);
508
563
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase2 : phrase2;
509
564
  }
510
565
  if (plan.fold) {
511
566
  return leadingQualifier(ir, opts) + hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
512
567
  }
513
568
  const hoursStr = hourSegmentTimes(ir, 0, null, opts);
514
- const phrase = hoursFirstMinutes(hoursStr, ir) + trailingQualifier(ir, opts);
569
+ const phrase = hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
515
570
  return ir.analyses.clockSecond ? secondsLeadClause(ir, opts) + ", " + phrase : phrase;
516
571
  }
517
572
  var renderers = {
@@ -534,20 +589,48 @@ var renderers = {
534
589
  singleMinute: renderSingleMinute,
535
590
  standaloneSeconds: renderStandaloneSeconds
536
591
  };
592
+ function renderStride(stride, opts) {
593
+ const { interval, start, last, cycle, unit } = stride;
594
+ const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
595
+ const tiles = cycle % interval === 0;
596
+ if (start === 0 && tiles) {
597
+ return cadence;
598
+ }
599
+ if (start < interval && tiles) {
600
+ return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
601
+ }
602
+ return cadence + " " + unit.ela + " " + start + " " + unit.ill + " " + last;
603
+ }
604
+ function strideFromSegments(segments, unit, opts) {
605
+ const values = singleValues(segments);
606
+ const step = values && arithmeticStep(values);
607
+ return step ? renderStride({ ...step, cycle: 60, unit }, opts) : null;
608
+ }
609
+ function singleValues(segments) {
610
+ const values = [];
611
+ for (const segment of segments) {
612
+ if (segment.kind !== "single") {
613
+ return null;
614
+ }
615
+ values.push(+segment.value);
616
+ }
617
+ return values;
618
+ }
537
619
  function stepCycle60(segment, unit, opts) {
538
620
  if (segment.startToken.indexOf("-") !== -1) {
539
621
  return atMarks(joinList(wordList(segment.fires)), unit, true);
540
622
  }
541
623
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
542
- const interval = segment.interval;
543
- const cadence = genitive(interval, opts) + " " + unit.gen + " v\xE4lein";
544
- if (start !== 0) {
545
- if (segment.fires.length <= 3) {
546
- return atMarks(joinList(wordList(segment.fires)), unit, true);
547
- }
548
- return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
624
+ if (start !== 0 && segment.fires.length <= 3) {
625
+ return atMarks(joinList(wordList(segment.fires)), unit, true);
549
626
  }
550
- return cadence;
627
+ return renderStride({
628
+ interval: segment.interval,
629
+ start,
630
+ last: segment.fires[segment.fires.length - 1],
631
+ cycle: 60,
632
+ unit
633
+ }, opts);
551
634
  }
552
635
  function stepHours(segment, opts) {
553
636
  if (segment.startToken.indexOf("-") !== -1) {
@@ -564,6 +647,71 @@ function stepHours(segment, opts) {
564
647
  }
565
648
  return cadence + " klo " + hourElatives[start] + " alkaen";
566
649
  }
650
+ function hourStrideCadence(stride, opts) {
651
+ const { start, interval, last } = stride;
652
+ const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
653
+ const tiles = 24 % interval === 0;
654
+ if (start === 0 && tiles) {
655
+ return cadence;
656
+ }
657
+ if (start < interval && tiles) {
658
+ return cadence + " klo " + hourElatives[start] + " alkaen";
659
+ }
660
+ return cadence + " " + kloRange({ hour: start, minute: 0 }, { hour: last, minute: 0 }, opts);
661
+ }
662
+ function hourStride(ir) {
663
+ const segments = ir.analyses.segments.hour;
664
+ if (!segments) {
665
+ return null;
666
+ }
667
+ if (segments.length === 1 && segments[0].kind === "step") {
668
+ const segment = segments[0];
669
+ const start = segment.startToken === "*" ? 0 : +segment.startToken.split("-")[0];
670
+ return { interval: segment.interval, last: segment.fires[segment.fires.length - 1], start };
671
+ }
672
+ const values = singleValues(segments);
673
+ const step = values && arithmeticStep(values);
674
+ return step || null;
675
+ }
676
+ function subMinuteSecond(ir) {
677
+ return ir.pattern.second === "*" || ir.shapes.second === "step";
678
+ }
679
+ function hourCadenceLead(ir, minute, opts) {
680
+ if (minute === 0) {
681
+ if (subMinuteSecond(ir)) {
682
+ return secondsLeadClause(ir, opts) + " minuutin ajan";
683
+ }
684
+ return secondsLeadClause(ir, opts);
685
+ }
686
+ const minutePhrase = atMarks(String(minute), units.minute, false);
687
+ if (ir.pattern.second === "0") {
688
+ return minutePhrase;
689
+ }
690
+ return secondsLeadClause(ir, opts) + ", " + minutePhrase;
691
+ }
692
+ function hourCadence(ir, minute, opts) {
693
+ const stride = hourStride(ir);
694
+ if (!stride) {
695
+ return null;
696
+ }
697
+ const fires = (stride.last - stride.start) / stride.interval + 1;
698
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
699
+ return null;
700
+ }
701
+ const segment = ir.analyses.segments.hour[0];
702
+ const confined = minute === 0 && subMinuteSecond(ir) && ir.analyses.segments.hour.length === 1 && segment.kind === "step" && cleanHourStride(segment);
703
+ if (confined) {
704
+ return secondsLeadClause(ir, opts) + " minuutin ajan " + everyNthHour(segment, opts) + trailingQualifier(ir, opts);
705
+ }
706
+ return hourCadenceLead(ir, minute, opts) + ", " + hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
707
+ }
708
+ function cleanHourStride(segment) {
709
+ if (segment.startToken.indexOf("-") !== -1) {
710
+ return false;
711
+ }
712
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
713
+ return 24 % segment.interval === 0 && start < segment.interval;
714
+ }
567
715
  function kloList(hours, opts) {
568
716
  if (hours.length === 1) {
569
717
  return timeWord(hours[0], 0, null, opts);
package/dist/lang/zh.cjs CHANGED
@@ -29,6 +29,21 @@ function isNonNegativeInteger(value) {
29
29
  const digits = /^\d+$/;
30
30
  return digits.test(value);
31
31
  }
32
+ function arithmeticStep(values) {
33
+ if (values.length < 5) {
34
+ return null;
35
+ }
36
+ const interval = values[1] - values[0];
37
+ if (interval < 2) {
38
+ return null;
39
+ }
40
+ for (let i = 2; i < values.length; i += 1) {
41
+ if (values[i] - values[i - 1] !== interval) {
42
+ return null;
43
+ }
44
+ }
45
+ return { start: values[0], interval, last: values[values.length - 1] };
46
+ }
32
47
  function toFieldNumber(token, numberMap) {
33
48
  return isNonNegativeInteger(token) ? +token : numberMap[token.toUpperCase()];
34
49
  }
@@ -57,6 +72,7 @@ var monthNumbers = {
57
72
  NOV: 11,
58
73
  DEC: 12
59
74
  };
75
+ var maxClockTimes = 6;
60
76
 
61
77
  // src/lang/zh/dialects.ts
62
78
  var zh = { variant: "Hans" };
@@ -87,6 +103,46 @@ function stepSegment(ir, field) {
87
103
  function cadence(interval, unit) {
88
104
  return interval === 1 ? "\u6BCF" + unit : "\u6BCF" + interval + unit;
89
105
  }
106
+ function renderStride(stride) {
107
+ const { interval, start, last, cycle, unit, mark, anchor } = stride;
108
+ const tiles = cycle % interval === 0;
109
+ if (start === 0 && tiles) {
110
+ return cadence(interval, unit);
111
+ }
112
+ const lead = anchor + "\u4ECE" + start + mark + "\u8D77" + cadence(interval, unit);
113
+ return start < interval && tiles ? lead : lead + "\uFF0C\u81F3" + last + mark;
114
+ }
115
+ function singleValues(segments) {
116
+ const values = [];
117
+ for (const segment of segments) {
118
+ if (segment.kind !== "single") {
119
+ return null;
120
+ }
121
+ values.push(+segment.value);
122
+ }
123
+ return values;
124
+ }
125
+ function strideFromSegments(segments, unit, mark, anchor) {
126
+ const values = singleValues(segments);
127
+ const step = values && arithmeticStep(values);
128
+ return step ? renderStride({ ...step, cycle: 60, unit, mark, anchor }) : null;
129
+ }
130
+ function stepClause(segment, unit, mark, anchor) {
131
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
132
+ const short = start !== 0 && segment.fires.length <= 3;
133
+ if (segment.startToken.indexOf("-") !== -1 || short) {
134
+ return anchor + segment.fires.join("\u3001") + mark;
135
+ }
136
+ return renderStride({
137
+ interval: segment.interval,
138
+ start,
139
+ last: segment.fires[segment.fires.length - 1],
140
+ cycle: 60,
141
+ unit,
142
+ mark,
143
+ anchor
144
+ });
145
+ }
90
146
  function dayPeriod(hour) {
91
147
  if (hour < 6) {
92
148
  return "\u51CC\u6668";
@@ -183,8 +239,15 @@ function renderEveryMinute() {
183
239
  function renderEveryHour() {
184
240
  return "\u6BCF\u5C0F\u65F6";
185
241
  }
242
+ function minuteHourClause(ir) {
243
+ const segments = fieldSegments(ir, "minute");
244
+ if (ir.shapes.minute === "step") {
245
+ return stepClause(stepSegment(ir, "minute"), "\u5206\u949F", "\u5206", "\u6BCF\u5C0F\u65F6");
246
+ }
247
+ return strideFromSegments(segments, "\u5206\u949F", "\u5206", "\u6BCF\u5C0F\u65F6") ?? "\u6BCF\u5C0F\u65F6" + valueList(segments, "\u5206");
248
+ }
186
249
  function renderMinutePast(ir) {
187
- return "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206");
250
+ return minuteHourClause(ir);
188
251
  }
189
252
  function hourList(ir) {
190
253
  return joinAnd(hourFires(ir).map(hourWord));
@@ -198,7 +261,7 @@ function hourFrame(ir) {
198
261
  }
199
262
  function renderMinuteFrequency(ir, plan) {
200
263
  const minuteStep = stepSegment(ir, "minute");
201
- const base = minuteStep.startToken === "*" ? cadence(minuteStep.interval, UNITS.minute) : renderMinutePast(ir);
264
+ const base = stepClause(minuteStep, UNITS.minute, "\u5206", "\u6BCF\u5C0F\u65F6");
202
265
  const { hours } = plan;
203
266
  if (hours.kind === "step") {
204
267
  const hourStep = stepSegment(ir, "hour");
@@ -227,12 +290,15 @@ function renderMinutesAcrossHours(ir, plan) {
227
290
  if (form === "wildcard") {
228
291
  return "\u5728" + hourList(ir) + "\uFF0C\u6BCF\u5206\u949F";
229
292
  }
230
- return hourList(ir) + "\uFF0C\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206") + "\uFF0C\u6BCF\u5206\u949F";
293
+ return hourList(ir) + "\uFF0C" + minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
231
294
  }
232
295
  function renderMinuteSpanAcrossHourStep(ir, plan) {
233
296
  const hourStep = stepSegment(ir, "hour");
234
297
  const { form } = plan;
235
- const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206") + "\uFF0C\u6BCF\u5206\u949F";
298
+ if (form === "list") {
299
+ return renderMinutePast(ir) + "\uFF0C\u5728" + hourList(ir);
300
+ }
301
+ const minuteTail = form === "wildcard" ? "\u6BCF\u5206\u949F" : minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
236
302
  if (hourStep.startToken !== "*") {
237
303
  return form === "wildcard" ? "\u5728" + hourList(ir) + "\uFF0C" + minuteTail : hourList(ir) + "\uFF0C" + minuteTail;
238
304
  }
@@ -243,15 +309,27 @@ function renderMinuteSpanAcrossHourStep(ir, plan) {
243
309
  return form === "wildcard" ? cad + "\u5185\uFF0C" + minuteTail : cad + "\uFF0C" + minuteTail;
244
310
  }
245
311
  function renderClockTimes(ir, plan, opts) {
312
+ if (ir.shapes.minute === "single") {
313
+ const cad = hourCadence(ir);
314
+ if (cad !== null) {
315
+ return cad;
316
+ }
317
+ }
246
318
  const { times } = plan;
247
319
  return joinAnd(times.map((t) => clockTime(t.hour, t.minute, t.second, opts)));
248
320
  }
249
321
  function renderCompactClockTimes(ir, plan) {
322
+ if (ir.shapes.minute === "single") {
323
+ const cad = hourCadence(ir);
324
+ if (cad !== null) {
325
+ return cad;
326
+ }
327
+ }
250
328
  const { minute } = plan;
251
329
  const secs = fieldSegments(ir, "second");
252
330
  const tail = secs.length && ir.pattern.second !== "0" ? "\uFF0C\u7B2C" + valueText(secs) + "\u79D2" : "";
253
331
  if (minute > 0) {
254
- return "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206") + "\uFF0C\u5728" + hourList(ir) + tail;
332
+ return minuteHourClause(ir) + "\uFF0C\u5728" + hourList(ir) + tail;
255
333
  }
256
334
  return hourList(ir) + tail;
257
335
  }
@@ -259,7 +337,7 @@ function renderHourRange(ir, plan) {
259
337
  const range = plan;
260
338
  if (range.minuteForm === "lead") {
261
339
  const minuteSegs = fieldSegments(ir, "minute");
262
- const past = minuteSegs.length && ir.pattern.minute !== "0" ? "\u6BCF\u5C0F\u65F6" + valueList(minuteSegs, "\u5206") : "\u6BCF\u5C0F\u65F6";
340
+ const past = minuteSegs.length && ir.pattern.minute !== "0" ? minuteHourClause(ir) : "\u6BCF\u5C0F\u65F6";
263
341
  return "\u5728" + hourWord(range.from) + "\u81F3" + hourWord(range.to) + "\u4E4B\u95F4\uFF0C" + past;
264
342
  }
265
343
  if (range.minuteForm === "range") {
@@ -277,8 +355,52 @@ function renderHourStep(ir) {
277
355
  }
278
356
  return cadence(segment.interval, UNITS.hour);
279
357
  }
358
+ function hourStride(ir) {
359
+ const segments = fieldSegments(ir, "hour");
360
+ if (segments.length === 1 && segments[0].kind === "step") {
361
+ const segment = segments[0];
362
+ const start = segment.startToken === "*" ? 0 : +segment.startToken;
363
+ if (start !== 0 || 24 % segment.interval !== 0) {
364
+ return null;
365
+ }
366
+ return {
367
+ interval: segment.interval,
368
+ last: segment.fires[segment.fires.length - 1]
369
+ };
370
+ }
371
+ const values = singleValues(segments);
372
+ const step = values && arithmeticStep(values);
373
+ if (!step || step.start !== 0 || 24 % step.interval !== 0) {
374
+ return null;
375
+ }
376
+ return { interval: step.interval, last: step.last };
377
+ }
378
+ function hourCadence(ir) {
379
+ const stride = hourStride(ir);
380
+ if (!stride) {
381
+ return null;
382
+ }
383
+ const fires = stride.last / stride.interval + 1;
384
+ if (ir.pattern.second === "0" && fires <= maxClockTimes) {
385
+ return null;
386
+ }
387
+ const prefix = cadence(stride.interval, UNITS.hour);
388
+ const minute = +ir.pattern.minute;
389
+ const subMinute = ir.pattern.second === "*" || ir.shapes.second === "step";
390
+ if (minute === 0 && subMinute) {
391
+ return stride.interval === 2 ? "\u5728\u5076\u6570\u5C0F\u65F60\u5206" + secondTail(ir) : null;
392
+ }
393
+ if (minute === 0) {
394
+ return prefix + "0\u5206" + secondTail(ir);
395
+ }
396
+ return ir.pattern.second === "0" ? prefix + minute + "\u5206" : prefix + minute + "\u5206" + secondTail(ir);
397
+ }
398
+ function secondTail(ir) {
399
+ const sec = secondClause(ir);
400
+ return sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
401
+ }
280
402
  function renderRangeOfMinutes(ir) {
281
- return "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206") + "\uFF0C\u6BCF\u5206\u949F";
403
+ return minuteHourClause(ir) + "\uFF0C\u6BCF\u5206\u949F";
282
404
  }
283
405
  function renderStandaloneSeconds(ir) {
284
406
  const segs = fieldSegments(ir, "second");
@@ -286,7 +408,7 @@ function renderStandaloneSeconds(ir) {
286
408
  if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
287
409
  return cadence(first.interval, UNITS.second);
288
410
  }
289
- return "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
411
+ return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u6BCF\u5206\u949F\u7B2C" + valueText(segs) + "\u79D2";
290
412
  }
291
413
  function renderSecondPastMinute(ir) {
292
414
  return "\u6BCF\u5206\u949F\u7B2C" + valueText(fieldSegments(ir, "second")) + "\u79D2";
@@ -309,7 +431,7 @@ function secondClause(ir) {
309
431
  if (segs.length === 1 && first.kind === "step" && first.startToken === "*") {
310
432
  return cadence(first.interval, UNITS.second);
311
433
  }
312
- return "\u7B2C" + valueText(segs) + "\u79D2";
434
+ return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u7B2C" + valueText(segs) + "\u79D2";
313
435
  }
314
436
  function minuteClause(ir) {
315
437
  if (ir.pattern.minute === "*") {
@@ -326,13 +448,15 @@ function isHourCadence(ir) {
326
448
  function composeSecondsOnHour(ir, plan, opts) {
327
449
  const sec = secondClause(ir);
328
450
  const { rest } = plan;
329
- if ((rest.kind === "clockTimes" || rest.kind === "compactClockTimes") && ir.pattern.minute === "0") {
330
- const clocks = hourFires(ir).map(function clock(hour) {
331
- return hourWord(hour) + "0\u5206";
332
- });
333
- const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
334
- const core = joinAnd(clocks) + tail;
335
- return isDaily(ir) ? "\u6BCF\u5929" + core : core;
451
+ const composedClock = rest.kind === "clockTimes" || rest.kind === "compactClockTimes";
452
+ if (composedClock) {
453
+ const hourCad = hourCadence(ir);
454
+ if (hourCad !== null) {
455
+ return hourCad;
456
+ }
457
+ }
458
+ if (composedClock && ir.pattern.minute === "0") {
459
+ return composeMinuteZeroClocks(ir, sec);
336
460
  }
337
461
  const restText = render(ir, rest, opts);
338
462
  if (rest.kind === "clockTimes" || rest.kind === "compactClockTimes") {
@@ -345,6 +469,15 @@ function composeSecondsOnHour(ir, plan, opts) {
345
469
  }
346
470
  return restText + sec;
347
471
  }
472
+ function composeMinuteZeroClocks(ir, sec) {
473
+ const clocks = hourFires(ir).map(function clock(hour) {
474
+ return hour === 12 ? "\u6B63\u5348" : hourWord(hour) + "0\u5206";
475
+ });
476
+ const nested = strideFromSegments(fieldSegments(ir, "second"), "\u79D2", "\u79D2", "");
477
+ const tail = sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + (nested ?? sec);
478
+ const core = joinAnd(clocks) + tail;
479
+ return isDaily(ir) ? "\u6BCF\u5929" + core : core;
480
+ }
348
481
  function composeSecondsCadence(ir) {
349
482
  const sec = secondClause(ir);
350
483
  const tail = minuteClause(ir) + sec;
@@ -355,15 +488,23 @@ function composeSecondsCadence(ir) {
355
488
  return hourWord(hourFires(ir)[0]) + "\u7684" + tail;
356
489
  }
357
490
  if (ir.shapes.hour === "wildcard") {
491
+ if (ir.shapes.minute === "step" && sec === "\u6BCF\u79D2") {
492
+ const minuteStep = stepSegment(ir, "minute");
493
+ if (minuteStep.startToken === "*" && minuteStep.interval === 2) {
494
+ return "\u6BCF\u5076\u6570\u5206\u949F\u7684\u6BCF\u4E00\u79D2";
495
+ }
496
+ }
358
497
  return sec + "\uFF0C" + minuteClause(ir);
359
498
  }
360
499
  return hourFrame(ir) + tail;
361
500
  }
362
501
  function composeSecondsListed(ir) {
363
502
  const sec = secondClause(ir);
364
- const minutes = "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206");
503
+ const minutes = minuteHourClause(ir);
365
504
  if (ir.shapes.hour === "single" && sec === "\u6BCF\u79D2") {
366
- return hourWord(hourFires(ir)[0]) + valueList(fieldSegments(ir, "minute"), "\u5206") + "\u7684\u6BCF\u4E00\u79D2";
505
+ const minuteSegs = fieldSegments(ir, "minute");
506
+ const minuteCad = strideFromSegments(minuteSegs, "\u5206\u949F", "\u5206", "") ?? valueList(minuteSegs, "\u5206");
507
+ return hourWord(hourFires(ir)[0]) + minuteCad + "\u7684\u6BCF\u4E00\u79D2";
367
508
  }
368
509
  if (ir.shapes.hour === "wildcard") {
369
510
  return minutes + "\uFF0C" + sec;
@@ -580,11 +721,16 @@ function composeCadence(ir, core) {
580
721
  function composeWindow(ir, core) {
581
722
  return qualifier(ir) + core;
582
723
  }
724
+ function hourCadenceApplies(ir) {
725
+ return ir.shapes.minute === "single" && hourCadence(ir) !== null;
726
+ }
583
727
  function describe(ir, opts) {
584
728
  const { kind } = ir.plan;
585
729
  const core = render(ir, ir.plan, opts);
586
730
  let composed = core;
587
- if (kind === "clockTimes" || kind === "compactClockTimes" && ir.pattern.minute === "0") {
731
+ if (hourCadenceApplies(ir)) {
732
+ composed = composeCadence(ir, core);
733
+ } else if (kind === "clockTimes" || kind === "compactClockTimes" && ir.pattern.minute === "0") {
588
734
  composed = composePoint(ir, core);
589
735
  } else if (kind === "hourStep" || kind === "minuteFrequency" || kind === "minuteSpanAcrossHourStep" || kind === "compactClockTimes") {
590
736
  composed = composeCadence(ir, core);