cronli5 0.8.2 → 0.8.3

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.
@@ -144,6 +144,16 @@ const nthWeekdayMasculine =
144
144
  const nthWeekdayFeminine =
145
145
  [null, 'primeira', 'segunda', 'terceira', 'quarta', 'quinta'];
146
146
 
147
+ // Portuguese ordinals (masculine) for a stepped-minute cadence under a seconds
148
+ // lead ("a cada segundo no sexto minuto"). The interval-2 step keeps its own
149
+ // idiom and never reaches here, so the colliding "segundo" is unused; a lookup
150
+ // miss falls back to the cardinal-with-"no" form, which still confines.
151
+ const stepOrdinals: Record<number, string> = {
152
+ 3: 'terceiro', 4: 'quarto', 5: 'quinto', 6: 'sexto', 7: 'sétimo',
153
+ 8: 'oitavo', 9: 'nono', 10: 'décimo', 12: 'décimo segundo',
154
+ 15: 'décimo quinto', 20: 'vigésimo', 30: 'trigésimo'
155
+ };
156
+
147
157
  // --- Contractions (the principal es->pt divergence). ---
148
158
  //
149
159
  // Portuguese fuses a preposition with the following article wherever es emitted
@@ -413,6 +423,67 @@ function isPinnedMinuteSeconds(
413
423
  schedule.shapes.second === 'step');
414
424
  }
415
425
 
426
+ // The minute field's step stride for the confinement frame, or null when the
427
+ // minute is not a stepped cadence. A `step`-shaped field reads its segment; a
428
+ // `list`-shaped field the core enumerated from a uneven step (`2/7` → 2,9,…,58)
429
+ // recovers the progression from its values.
430
+ function minuteStride(
431
+ schedule: Schedule
432
+ ): {start: number; interval: number; last: number} | null {
433
+ if (schedule.shapes.minute === 'step') {
434
+ const segment = stepSegment(schedule, 'minute');
435
+ const start = segment.startToken === '*' ? 0 : +segment.startToken;
436
+
437
+ return {interval: segment.interval, last:
438
+ segment.fires[segment.fires.length - 1], start};
439
+ }
440
+
441
+ const values = singleValues(segmentsOf(schedule, 'minute'));
442
+
443
+ return values && arithmeticStep(values);
444
+ }
445
+
446
+ // A stepped minute under a wildcard/stepped second and wildcard hour: bind the
447
+ // second cadence to the minute cadence as a CONFINEMENT ("a cada segundo no
448
+ // sexto minuto a partir do minuto 4 de cada hora"), never the comma
449
+ // juxtaposition that reads as two independent cadences. The cadence is ORDINAL
450
+ // ("no sexto minuto") — the cardinal "a cada seis minutos" is what fuels the
451
+ // misread — and the start/bound mirror the standalone minute cadence.
452
+ function minuteStepConfinement(
453
+ schedule: Schedule,
454
+ stride: {start: number; interval: number; last: number},
455
+ opts: Opts
456
+ ): string {
457
+ const ordinal = stepOrdinals[stride.interval];
458
+ const head = ordinal ?
459
+ 'no ' + ordinal + ' minuto' :
460
+ 'a cada ' + numero(stride.interval, opts) + ' minutos';
461
+
462
+ const tail = chooseStride({...stride, cycle: 60}, {
463
+ bare: () => '',
464
+ offset: () => ' a partir do minuto ' + stride.start,
465
+ bounded: () => ' do minuto ' + stride.start + ' ao ' + stride.last
466
+ });
467
+
468
+ return secondsLeadClause(schedule, opts) + ' ' + head + tail +
469
+ ' de cada hora' + trailingQualifier(schedule, opts);
470
+ }
471
+
472
+ // Whether a stepped minute fills a wildcard hour under a wildcard/stepped
473
+ // second — the shape the confinement frame above handles.
474
+ function isSteppedMinuteSeconds(
475
+ schedule: Schedule,
476
+ plan: Extract<PlanNode, {kind: 'composeSeconds'}>
477
+ ): boolean {
478
+ return (plan.rest.kind === 'minuteFrequency' ||
479
+ plan.rest.kind === 'multipleMinutes') &&
480
+ (schedule.shapes.second === 'wildcard' ||
481
+ schedule.shapes.second === 'step') &&
482
+ schedule.shapes.hour === 'wildcard' &&
483
+ schedule.pattern.minute !== '*/2' &&
484
+ minuteStride(schedule) !== null;
485
+ }
486
+
416
487
  function renderComposeSeconds(
417
488
  schedule: Schedule,
418
489
  plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
@@ -456,6 +527,14 @@ function renderComposeSeconds(
456
527
  return dayFrame + ', ' + window + ', ' + cadence;
457
528
  }
458
529
 
530
+ // A stepped minute under a wildcard/stepped second + wildcard hour confines
531
+ // the second cadence to the ordinal minute cadence ("a cada segundo no sexto
532
+ // minuto a partir do minuto 4 de cada hora"), never the comma juxtaposition
533
+ // that reads as two independent cadences.
534
+ if (isSteppedMinuteSeconds(schedule, plan)) {
535
+ return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
536
+ }
537
+
459
538
  // A wildcard second under a minute */2 with a wildcard hour juxtaposes two
460
539
  // cadences that read as contradictory ("a cada segundo, a cada dois
461
540
  // minutos"). Bind them with the genitive "de" ("a cada segundo de cada dois
@@ -909,10 +909,14 @@ function composeSecondsOnHour(
909
909
  return '每天' + restText + secTail;
910
910
  }
911
911
 
912
- // A stated minute (e.g. minute 0 under a sub-minute second) takes the same
913
- // "" connector the listed-minute path uses.
912
+ // A stated single minute (minute 0 under an open hour) confines the second
913
+ // beneath it with "" when the second is a cadence ("每小时0分的每一秒"), the
914
+ // same fusion the other pinned-minute paths use; the bare comma ("每小时0分,
915
+ // 每秒") reads as two independent cadences. A single/list/range second is a
916
+ // clock-point, not a cadence, so it keeps the "," connector.
914
917
  if (rest.kind === 'singleMinute') {
915
- return restText + ',' + sec;
918
+ return secondIsCadence(schedule) ?
919
+ restText + confinedSecondTail(sec) : restText + ',' + sec;
916
920
  }
917
921
 
918
922
  return restText + secTail;
@@ -1069,7 +1073,13 @@ function composeSecondsListed(schedule: Schedule): string {
1069
1073
  }
1070
1074
 
1071
1075
  if (schedule.shapes.hour === 'wildcard') {
1072
- return minutes + ',' + sec;
1076
+ // A wildcard or stepped second is a cadence; the bare comma ("每小时30分,
1077
+ // 每秒") reads as two independent cadences, so fuse the second beneath the
1078
+ // stated minute(s) with "的" ("每小时30分的每一秒"), the same confinement the
1079
+ // minute-stride and pinned-clock paths use. A single/list/range second is a
1080
+ // clock-point, not a cadence, so it keeps the comma.
1081
+ return secondIsCadence(schedule) ?
1082
+ minutes + confinedSecondTail(sec) : minutes + ',' + sec;
1073
1083
  }
1074
1084
 
1075
1085
  const hourCad = unevenHourCadence(schedule);
@@ -1084,6 +1094,54 @@ function composeSecondsListed(schedule: Schedule): string {
1084
1094
  return hourFrame(schedule) + minutes + ',' + sec;
1085
1095
  }
1086
1096
 
1097
+ // Whether the minute field is a stepped cadence (a clean `*/n`, an offset
1098
+ // `m/n`, or a uneven step the core enumerated to an arithmetic fire list). The
1099
+ // shape the seconds-wildcard confinement below fuses with "的".
1100
+ function isMinuteStride(schedule: Schedule): boolean {
1101
+ if (schedule.shapes.minute === 'step') {
1102
+ return true;
1103
+ }
1104
+
1105
+ const values = singleValues(segmentsOf(schedule, 'minute'));
1106
+
1107
+ return values !== null && arithmeticStep(values) !== null;
1108
+ }
1109
+
1110
+ // Whether the second is a CADENCE (wildcard "每秒" or a clean step "每N秒") rather
1111
+ // than a clock-point (a single/list/range). A cadence second under a stated
1112
+ // minute fuses beneath it with "的"; a clock-point keeps the "," connector.
1113
+ function secondIsCadence(schedule: Schedule): boolean {
1114
+ return schedule.pattern.second === '*' || schedule.shapes.second === 'step';
1115
+ }
1116
+
1117
+ // The "的"-fused second tail for a clause that already states its minute(s):
1118
+ // "的每一秒" for a wildcard second, else "的" + the second's own cadence clause.
1119
+ // The fusion binds the second beneath the minute rather than leaving a bare
1120
+ // trailing "每秒" that reads as a second, independent cadence.
1121
+ function confinedSecondTail(sec: string): string {
1122
+ return sec === '每秒' ? '的每一秒' : '的' + sec;
1123
+ }
1124
+
1125
+ // Whether a compose-seconds plan is a stepped minute under a cadence second and
1126
+ // wildcard hour — the shape the "的"-fused confinement below handles, kept
1127
+ // distinct from the */2 even-minutes idiom and the composed-clock paths.
1128
+ function isSteppedMinuteSeconds(
1129
+ schedule: Schedule, composedClock: boolean
1130
+ ): boolean {
1131
+ return !composedClock && schedule.shapes.hour === 'wildcard' &&
1132
+ secondIsCadence(schedule) && schedule.pattern.minute !== '*/2' &&
1133
+ isMinuteStride(schedule);
1134
+ }
1135
+
1136
+ // A stepped minute under a cadence second and wildcard hour: fuse the minute
1137
+ // cadence and the second cadence with "的" ("每小时从4分起每6分钟的每一秒"), never
1138
+ // the comma juxtaposition ("…每6分钟,每秒") that reads as two independent
1139
+ // cadences. The minute clause carries the offset/bound ("从4分起" / ",至58分").
1140
+ function minuteStrideConfinement(schedule: Schedule): string {
1141
+ return minuteHourClause(schedule) +
1142
+ confinedSecondTail(secondClause(schedule));
1143
+ }
1144
+
1087
1145
  // Seconds composed with the minute/hour structure, dispatched on the minute.
1088
1146
  // A single minute over a composed clock-time rest (the core already joined the
1089
1147
  // lone hour and minute into "N点M分") keeps that composition, attaching the
@@ -1096,6 +1154,13 @@ function renderComposeSeconds(
1096
1154
  const composedClock =
1097
1155
  rest.kind === 'clockTimes' || rest.kind === 'compactClockTimes';
1098
1156
 
1157
+ // A stepped minute under a cadence second and wildcard hour confines the
1158
+ // second beneath the minute cadence with "的", never the comma that reads as
1159
+ // two independent cadences. The */2 step keeps its own "每偶数分钟" idiom.
1160
+ if (isSteppedMinuteSeconds(schedule, composedClock)) {
1161
+ return minuteStrideConfinement(schedule);
1162
+ }
1163
+
1099
1164
  if (schedule.pattern.minute === '0' ||
1100
1165
  composedClock && schedule.shapes.minute === 'single') {
1101
1166
  return composeSecondsOnHour(schedule, plan, opts);