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.
- package/CHANGELOG.md +15 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +50 -4
- package/dist/cronli5.js +50 -4
- package/dist/lang/de.cjs +39 -0
- package/dist/lang/de.js +39 -0
- package/dist/lang/en.cjs +50 -4
- package/dist/lang/en.js +50 -4
- package/dist/lang/es.cjs +39 -0
- package/dist/lang/es.js +39 -0
- package/dist/lang/fi.cjs +40 -0
- package/dist/lang/fi.js +40 -0
- package/dist/lang/fr.cjs +39 -0
- package/dist/lang/fr.js +39 -0
- package/dist/lang/pt.cjs +39 -0
- package/dist/lang/pt.js +39 -0
- package/dist/lang/zh.cjs +24 -2
- package/dist/lang/zh.js +24 -2
- package/package.json +1 -1
- package/src/lang/de/index.ts +77 -0
- package/src/lang/en/index.ts +97 -21
- package/src/lang/es/index.ts +84 -0
- package/src/lang/fi/index.ts +86 -0
- package/src/lang/fr/index.ts +79 -0
- package/src/lang/pt/index.ts +79 -0
- package/src/lang/zh/index.ts +69 -4
package/package.json
CHANGED
package/src/lang/de/index.ts
CHANGED
|
@@ -223,6 +223,16 @@ const stepOrdinals: {[interval: number]: string} = {
|
|
|
223
223
|
12: 'zwölften'
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
// Dative ordinals for "in jeder N-ten Minute" — the step intervals a minute
|
|
227
|
+
// cadence can take. The interval-2 step keeps its own "jeder zweiten Minute"
|
|
228
|
+
// idiom and never reaches the confinement helper; a lookup miss falls back to
|
|
229
|
+
// the cardinal "alle N Minuten" form, which still confines.
|
|
230
|
+
const minuteStepOrdinals: {[interval: number]: string} = {
|
|
231
|
+
3: 'dritten', 4: 'vierten', 5: 'fünften', 6: 'sechsten', 7: 'siebten',
|
|
232
|
+
8: 'achten', 9: 'neunten', 10: 'zehnten', 12: 'zwölften',
|
|
233
|
+
15: 'fünfzehnten', 20: 'zwanzigsten', 30: 'dreißigsten'
|
|
234
|
+
};
|
|
235
|
+
|
|
226
236
|
// Confine a cadence to a clean hour stride: "in jeder zweiten Stunde", with
|
|
227
237
|
// the start named when it is not midnight ("…ab 1 Uhr" for an odd stride).
|
|
228
238
|
function everyNthHour(segment: StepSegment): string {
|
|
@@ -717,6 +727,65 @@ function isEveryOtherMinuteSeconds(
|
|
|
717
727
|
return minuteStep.startToken === '*' && minuteStep.interval === 2;
|
|
718
728
|
}
|
|
719
729
|
|
|
730
|
+
// The minute field's step stride for the confinement frame, or null when the
|
|
731
|
+
// minute is not a stepped cadence. A `step`-shaped field reads its segment; a
|
|
732
|
+
// `list`-shaped field the core enumerated from a uneven step (`2/7` → 2,9,…,58)
|
|
733
|
+
// recovers the progression from its values.
|
|
734
|
+
function minuteStride(
|
|
735
|
+
schedule: Schedule
|
|
736
|
+
): {start: number; interval: number; last: number} | null {
|
|
737
|
+
if (schedule.shapes.minute === 'step') {
|
|
738
|
+
const segment = stepSegment(schedule, 'minute');
|
|
739
|
+
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
740
|
+
|
|
741
|
+
return {interval: segment.interval, last:
|
|
742
|
+
segment.fires[segment.fires.length - 1], start};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const values = singleValues(segmentsOf(schedule, 'minute'));
|
|
746
|
+
|
|
747
|
+
return values && arithmeticStep(values);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// A stepped minute under a wildcard/stepped second and wildcard hour: bind the
|
|
751
|
+
// second cadence to the minute cadence as a CONFINEMENT ("jede Sekunde in jeder
|
|
752
|
+
// sechsten Minute ab Minute 4 jeder Stunde"), never the comma juxtaposition
|
|
753
|
+
// that reads as two independent cadences. The cadence is ORDINAL ("in jeder
|
|
754
|
+
// sechsten Minute") — the cardinal "alle 6 Minuten" is what fuels the misread —
|
|
755
|
+
// and the start/bound mirror the standalone minute cadence.
|
|
756
|
+
function minuteStepConfinement(
|
|
757
|
+
schedule: Schedule,
|
|
758
|
+
stride: {start: number; interval: number; last: number}
|
|
759
|
+
): string {
|
|
760
|
+
const ordinal = minuteStepOrdinals[stride.interval];
|
|
761
|
+
const head = ordinal ?
|
|
762
|
+
'in jeder ' + ordinal + ' Minute' :
|
|
763
|
+
'alle ' + stride.interval + ' Minuten';
|
|
764
|
+
|
|
765
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
766
|
+
bare: () => '',
|
|
767
|
+
offset: () => ' ab Minute ' + stride.start,
|
|
768
|
+
bounded: () => ' von Minute ' + stride.start + ' bis ' + stride.last
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
return secondsLead(schedule) + ' ' + head + tail + ' jeder Stunde';
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Whether a stepped minute fills a wildcard hour under a wildcard/stepped
|
|
775
|
+
// second — the shape the confinement frame above handles.
|
|
776
|
+
function isSteppedMinuteSeconds(
|
|
777
|
+
schedule: Schedule,
|
|
778
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
779
|
+
): boolean {
|
|
780
|
+
return (plan.rest.kind === 'minuteFrequency' ||
|
|
781
|
+
plan.rest.kind === 'multipleMinutes') &&
|
|
782
|
+
(schedule.shapes.second === 'wildcard' ||
|
|
783
|
+
schedule.shapes.second === 'step') &&
|
|
784
|
+
schedule.shapes.hour === 'wildcard' &&
|
|
785
|
+
schedule.pattern.minute !== '*/2' &&
|
|
786
|
+
minuteStride(schedule) !== null;
|
|
787
|
+
}
|
|
788
|
+
|
|
720
789
|
function renderComposeSeconds(
|
|
721
790
|
schedule: Schedule,
|
|
722
791
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -749,6 +818,14 @@ function renderComposeSeconds(
|
|
|
749
818
|
clockMinuteGenitive(plan.rest.times, opts.style.sep);
|
|
750
819
|
}
|
|
751
820
|
|
|
821
|
+
// A stepped minute under a wildcard/stepped second + wildcard hour confines
|
|
822
|
+
// the second cadence to the ordinal minute cadence ("jede Sekunde in jeder
|
|
823
|
+
// sechsten Minute ab Minute 4 jeder Stunde"), never the comma juxtaposition
|
|
824
|
+
// that reads as two independent cadences.
|
|
825
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
826
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!);
|
|
827
|
+
}
|
|
828
|
+
|
|
752
829
|
// A wildcard second under a minute */2 with a wildcard hour binds in the
|
|
753
830
|
// genitive ("jede Sekunde jeder zweiten Minute").
|
|
754
831
|
if (isEveryOtherMinuteSeconds(schedule, plan)) {
|
package/src/lang/en/index.ts
CHANGED
|
@@ -677,6 +677,67 @@ const stepOrdinals: Record<number, string> = {
|
|
|
677
677
|
2: 'other', 3: 'third', 4: 'fourth', 6: 'sixth', 8: 'eighth', 12: 'twelfth'
|
|
678
678
|
};
|
|
679
679
|
|
|
680
|
+
// Spelled ordinals for "every Nth minute" — the step intervals a minute
|
|
681
|
+
// cadence can take (2 reads idiomatically as "other"). A lookup miss falls back
|
|
682
|
+
// to the suffixed numeric ordinal, so an unusually large interval still reads.
|
|
683
|
+
const spelledOrdinals: Record<number, string> = {
|
|
684
|
+
2: 'other', 3: 'third', 4: 'fourth', 5: 'fifth', 6: 'sixth', 7: 'seventh',
|
|
685
|
+
8: 'eighth', 9: 'ninth', 10: 'tenth', 11: 'eleventh', 12: 'twelfth',
|
|
686
|
+
15: 'fifteenth', 20: 'twentieth', 30: 'thirtieth'
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// The ordinal word for a cadence interval ("sixth", "seventh"), spelled where
|
|
690
|
+
// known and suffixed-numeric ("13th") otherwise.
|
|
691
|
+
function ordinalWord(interval: number): string {
|
|
692
|
+
return spelledOrdinals[interval] ?? getOrdinal(interval);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// A stepped minute under a seconds lead reads as a CONFINEMENT of that cadence,
|
|
696
|
+
// not a juxtaposed clause (a comma there reads as two independent cadences) nor
|
|
697
|
+
// a wall of enumerated minutes: "during every Nth minute" plus the step's
|
|
698
|
+
// offset/bound. The cadence is ORDINAL ("every sixth minute"); the cardinal
|
|
699
|
+
// ("every six minutes") is the form that reads as a separate cadence. The
|
|
700
|
+
// offset/bound mirrors the standalone minute cadence: a clean stride from the
|
|
701
|
+
// top names no offset, an offset-clean stride names only its start ("from four
|
|
702
|
+
// minutes past the hour"), and an uneven one pins both endpoints ("from 2
|
|
703
|
+
// through 58 minutes past the hour").
|
|
704
|
+
function minuteStrideConfinement(stride: {start: number; interval: number;
|
|
705
|
+
last: number}, opts: NormalizedOptions): string {
|
|
706
|
+
const base = ' during every ' + ordinalWord(stride.interval) + ' minute';
|
|
707
|
+
|
|
708
|
+
return chooseStride({...stride, cycle: 60}, {
|
|
709
|
+
bare: () => base,
|
|
710
|
+
offset: () => base + ' from ' + getNumber(stride.start, opts) + ' ' +
|
|
711
|
+
pluralize(stride.start, 'minute') + ' past the hour',
|
|
712
|
+
bounded: () => {
|
|
713
|
+
const num = seriesNumber();
|
|
714
|
+
|
|
715
|
+
return base + ' from ' + num(stride.start) + through(opts) +
|
|
716
|
+
num(stride.last) + ' ' + pluralize(stride.last, 'minute') +
|
|
717
|
+
' past the hour';
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// The minute field's step stride for the confinement frame, or null when the
|
|
723
|
+
// minute is not a stepped cadence. A `step`-shaped field (`*/6`) reads its
|
|
724
|
+
// segment directly; a `list`-shaped field the core enumerated from an uneven
|
|
725
|
+
// step (`2/7` → 2,9,…,58) recovers the progression from its values.
|
|
726
|
+
function minuteStride(schedule: Schedule):
|
|
727
|
+
{start: number; interval: number; last: number} | null {
|
|
728
|
+
if (schedule.shapes.minute === 'step') {
|
|
729
|
+
const segment = stepSegment(schedule, 'minute');
|
|
730
|
+
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
731
|
+
|
|
732
|
+
return {interval: segment.interval, last:
|
|
733
|
+
segment.fires[segment.fires.length - 1], start};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const values = singleValues(segmentsOf(schedule, 'minute'));
|
|
737
|
+
|
|
738
|
+
return values && arithmeticStep(values);
|
|
739
|
+
}
|
|
740
|
+
|
|
680
741
|
// Confine a cadence to a clean hour stride: "during every other hour", with
|
|
681
742
|
// the start named when it is not midnight ("…from 1 a.m." for an odd stride).
|
|
682
743
|
function everyNthHour(segment: StepSegment, opts: NormalizedOptions): string {
|
|
@@ -1078,12 +1139,22 @@ function minuteConfinement(schedule: Schedule,
|
|
|
1078
1139
|
return '';
|
|
1079
1140
|
}
|
|
1080
1141
|
|
|
1081
|
-
if (
|
|
1082
|
-
// The
|
|
1083
|
-
// minute steps
|
|
1142
|
+
if (minute === '*/2') {
|
|
1143
|
+
// The `*/2` clean step reads idiomatically as "every other minute" with no
|
|
1144
|
+
// offset; other minute steps take the ordinal stride-cadence below.
|
|
1084
1145
|
return ' of every other minute';
|
|
1085
1146
|
}
|
|
1086
1147
|
|
|
1148
|
+
// A stepped minute (a clean `*/n`, an offset `m/n`, or a uneven step the core
|
|
1149
|
+
// enumerated to an arithmetic list) confines as "during every Nth minute"
|
|
1150
|
+
// plus the step's offset/bound — the ordinal cadence, not the cardinal that
|
|
1151
|
+
// reads as a separate cadence, nor a wall of enumerated ":NN" minutes.
|
|
1152
|
+
const stride = minuteStride(schedule);
|
|
1153
|
+
|
|
1154
|
+
if (stride) {
|
|
1155
|
+
return minuteStrideConfinement(stride, opts);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1087
1158
|
// A minute single/range/list under the seconds lead. The minute reads as a
|
|
1088
1159
|
// ":NN" clock-minute confinement, never "N minutes past the hour" (that is
|
|
1089
1160
|
// the minute-lead clock-point form).
|
|
@@ -1119,10 +1190,11 @@ function hourConfinement(schedule: Schedule, opts: NormalizedOptions): string {
|
|
|
1119
1190
|
|
|
1120
1191
|
if (hour === '*') {
|
|
1121
1192
|
// A pinned minute confinement ("during minute :00") repeats across every
|
|
1122
|
-
// hour, so the hour is named as the unit of recurrence; a
|
|
1123
|
-
// ("of every other minute"
|
|
1193
|
+
// hour, so the hour is named as the unit of recurrence; a minute cadence
|
|
1194
|
+
// ("of every other minute", "during every sixth minute …") or an absent
|
|
1195
|
+
// minute already implies all hours, so the hour is not restated.
|
|
1124
1196
|
const minutePinned = schedule.pattern.minute !== '*' &&
|
|
1125
|
-
!isCadenceField(schedule.pattern.minute);
|
|
1197
|
+
!isCadenceField(schedule.pattern.minute) && !minuteStride(schedule);
|
|
1126
1198
|
|
|
1127
1199
|
return minutePinned ? ' of every hour' : '';
|
|
1128
1200
|
}
|
|
@@ -1216,24 +1288,28 @@ function confinementEligible(schedule: Schedule,
|
|
|
1216
1288
|
}
|
|
1217
1289
|
|
|
1218
1290
|
if (lead.secondLead) {
|
|
1219
|
-
// A minute STEP
|
|
1220
|
-
//
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
// the
|
|
1224
|
-
//
|
|
1225
|
-
// renderer rather than this confinement frame, which closes on the top of
|
|
1226
|
-
// the next hour.
|
|
1291
|
+
// A minute STEP confines as an ordinal cadence ("during every sixth minute
|
|
1292
|
+
// from four minutes past the hour"), but only where it fills the coarser
|
|
1293
|
+
// field: under a WILDCARD hour the step repeats every hour, so the cadence
|
|
1294
|
+
// is the whole confinement. A single hour or a contiguous range closes on
|
|
1295
|
+
// the minute's real last fire, which the windowing renderer already speaks,
|
|
1296
|
+
// so those defer. The `*/2` step keeps its "of every other minute" idiom.
|
|
1227
1297
|
if (minuteStep) {
|
|
1228
|
-
return minute === '*/2'
|
|
1298
|
+
return minute === '*/2' ?
|
|
1299
|
+
schedule.shapes.hour !== 'range' :
|
|
1300
|
+
schedule.pattern.hour === '*';
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// A minute list that is really an arithmetic stride confines as that same
|
|
1304
|
+
// ordinal cadence when it fills a wildcard hour; under a restricted hour it
|
|
1305
|
+
// keeps its existing cadence form. A short explicit minute list crossed
|
|
1306
|
+
// with a discrete hour LIST is a wall of distinct clock times ("9:00 a.m.,
|
|
1307
|
+
// 9:25 a.m., …"), not a single minute confinement, so it stays enumerated.
|
|
1308
|
+
if (isMinuteStride(schedule)) {
|
|
1309
|
+
return schedule.pattern.hour === '*';
|
|
1229
1310
|
}
|
|
1230
1311
|
|
|
1231
|
-
|
|
1232
|
-
// explicit minute list crossed with a discrete hour LIST is a wall of
|
|
1233
|
-
// distinct clock times ("9:00 a.m., 9:25 a.m., …"), not a single minute
|
|
1234
|
-
// confinement. Both stay with the enumerating renderer.
|
|
1235
|
-
if (isMinuteStride(schedule) ||
|
|
1236
|
-
schedule.shapes.minute === 'list' && schedule.shapes.hour === 'list') {
|
|
1312
|
+
if (schedule.shapes.minute === 'list' && schedule.shapes.hour === 'list') {
|
|
1237
1313
|
return false;
|
|
1238
1314
|
}
|
|
1239
1315
|
|
package/src/lang/es/index.ts
CHANGED
|
@@ -115,6 +115,17 @@ const weekdayNames = [
|
|
|
115
115
|
const nthWeekdayNames =
|
|
116
116
|
[null, 'primer', 'segundo', 'tercer', 'cuarto', 'quinto'];
|
|
117
117
|
|
|
118
|
+
// Spanish ordinals (masculine) for a stepped-minute cadence under a seconds
|
|
119
|
+
// lead ("cada sexto minuto"). The interval-2 step never reaches here — it keeps
|
|
120
|
+
// its own "de cada dos minutos" idiom — so the colliding "segundo" is unused.
|
|
121
|
+
// A lookup miss falls back to the cardinal-with-"cada" form, which still
|
|
122
|
+
// confines (see `minuteStepOrdinal`).
|
|
123
|
+
const stepOrdinals: Record<number, string> = {
|
|
124
|
+
3: 'tercer', 4: 'cuarto', 5: 'quinto', 6: 'sexto', 7: 'séptimo',
|
|
125
|
+
8: 'octavo', 9: 'noveno', 10: 'décimo', 12: 'duodécimo', 15: 'decimoquinto',
|
|
126
|
+
20: 'vigésimo', 30: 'trigésimo'
|
|
127
|
+
};
|
|
128
|
+
|
|
118
129
|
// Normalize raw user options.
|
|
119
130
|
function normalizeOptions(options?: Cronli5Options): Opts {
|
|
120
131
|
options = options || {};
|
|
@@ -269,6 +280,71 @@ function isPinnedMinuteSeconds(
|
|
|
269
280
|
schedule.shapes.second === 'step');
|
|
270
281
|
}
|
|
271
282
|
|
|
283
|
+
// The minute field's step stride for the confinement frame, or null when the
|
|
284
|
+
// minute is not a stepped cadence. A `step`-shaped field (`*/6`) reads its
|
|
285
|
+
// segment; a `list`-shaped field the core enumerated from a uneven step (`2/7`
|
|
286
|
+
// → 2,9,…,58) recovers the progression from its values.
|
|
287
|
+
function minuteStride(
|
|
288
|
+
schedule: Schedule
|
|
289
|
+
): {start: number; interval: number; last: number} | null {
|
|
290
|
+
if (schedule.shapes.minute === 'step') {
|
|
291
|
+
const segment = stepSegment(schedule, 'minute');
|
|
292
|
+
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
293
|
+
|
|
294
|
+
return {interval: segment.interval, last:
|
|
295
|
+
segment.fires[segment.fires.length - 1], start};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const values = singleValues(segmentsOf(schedule, 'minute'));
|
|
299
|
+
|
|
300
|
+
return values && arithmeticStep(values);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// A stepped minute under a wildcard second and wildcard hour: bind the second
|
|
304
|
+
// cadence to the minute cadence as a CONFINEMENT ("cada segundo en cada sexto
|
|
305
|
+
// minuto a partir del minuto 4 de cada hora"), never the comma juxtaposition
|
|
306
|
+
// that reads as two independent cadences. The cadence is ORDINAL ("cada sexto
|
|
307
|
+
// minuto") — the cardinal "cada seis minutos" is what fuels the misread — and
|
|
308
|
+
// the start/bound mirror the standalone minute cadence: a clean step from the
|
|
309
|
+
// top names no offset, an offset-clean stride names only its start, and a
|
|
310
|
+
// uneven one pins both endpoints ("del minuto 2 al 58"). An interval the
|
|
311
|
+
// ordinal table does not cover keeps the cardinal "cada N" after "en", which
|
|
312
|
+
// still confines.
|
|
313
|
+
function minuteStepConfinement(
|
|
314
|
+
schedule: Schedule,
|
|
315
|
+
stride: {start: number; interval: number; last: number},
|
|
316
|
+
opts: Opts
|
|
317
|
+
): string {
|
|
318
|
+
const ordinal = stepOrdinals[stride.interval];
|
|
319
|
+
const head = ordinal ?
|
|
320
|
+
'cada ' + ordinal + ' minuto' :
|
|
321
|
+
'cada ' + numero(stride.interval, opts) + ' minutos';
|
|
322
|
+
|
|
323
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
324
|
+
bare: () => '',
|
|
325
|
+
offset: () => ' a partir del minuto ' + stride.start,
|
|
326
|
+
bounded: () => ' del minuto ' + stride.start + ' al ' + stride.last
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return secondsLeadClause(schedule, opts) + ' en ' + head + tail +
|
|
330
|
+
' de cada hora' + trailingQualifier(schedule, opts);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Whether a stepped minute fills a wildcard hour under a wildcard second — the
|
|
334
|
+
// shape the confinement frame above handles.
|
|
335
|
+
function isSteppedMinuteSeconds(
|
|
336
|
+
schedule: Schedule,
|
|
337
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
338
|
+
): boolean {
|
|
339
|
+
return (plan.rest.kind === 'minuteFrequency' ||
|
|
340
|
+
plan.rest.kind === 'multipleMinutes') &&
|
|
341
|
+
(schedule.shapes.second === 'wildcard' ||
|
|
342
|
+
schedule.shapes.second === 'step') &&
|
|
343
|
+
schedule.shapes.hour === 'wildcard' &&
|
|
344
|
+
schedule.pattern.minute !== '*/2' &&
|
|
345
|
+
minuteStride(schedule) !== null;
|
|
346
|
+
}
|
|
347
|
+
|
|
272
348
|
function renderComposeSeconds(
|
|
273
349
|
schedule: Schedule,
|
|
274
350
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -312,6 +388,14 @@ function renderComposeSeconds(
|
|
|
312
388
|
return dayFrame + ', ' + window + ', ' + cadence;
|
|
313
389
|
}
|
|
314
390
|
|
|
391
|
+
// A stepped minute under a wildcard second + wildcard hour confines the
|
|
392
|
+
// second cadence to the ordinal minute cadence ("cada segundo en cada sexto
|
|
393
|
+
// minuto a partir del minuto 4 de cada hora"), never the comma juxtaposition
|
|
394
|
+
// that reads as two independent cadences.
|
|
395
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
396
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
|
|
397
|
+
}
|
|
398
|
+
|
|
315
399
|
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
|
316
400
|
// cadences that read as contradictory ("cada segundo, cada dos minutos").
|
|
317
401
|
// Bind them with the genitive "de" ("cada segundo de cada dos minutos"),
|
package/src/lang/fi/index.ts
CHANGED
|
@@ -123,6 +123,17 @@ const nthWeekdayNames: (string | null)[] = [
|
|
|
123
123
|
'viidentenä'
|
|
124
124
|
];
|
|
125
125
|
|
|
126
|
+
// Essive ordinals for "joka N:ntenä minuuttina" — the step intervals a minute
|
|
127
|
+
// cadence can take. The interval-2 step keeps its own "joka toisena minuuttina"
|
|
128
|
+
// idiom and never reaches the confinement helper; a lookup miss falls back to
|
|
129
|
+
// the genitive "N minuutin välein" cadence, which still confines.
|
|
130
|
+
const minuteStepOrdinals: {[interval: number]: string} = {
|
|
131
|
+
3: 'kolmantena', 4: 'neljäntenä', 5: 'viidentenä', 6: 'kuudentena',
|
|
132
|
+
7: 'seitsemäntenä', 8: 'kahdeksantena', 9: 'yhdeksäntenä',
|
|
133
|
+
10: 'kymmenentenä', 12: 'kahdentenatoista', 15: 'viidentenätoista',
|
|
134
|
+
20: 'kahdentenakymmenentenä', 30: 'kolmantenakymmenentenä'
|
|
135
|
+
};
|
|
136
|
+
|
|
126
137
|
// Weekdays as stored inflected forms (SUN..SAT): distributive -isin,
|
|
127
138
|
// elative, illative, and essive. Consonant gradation (keskiviikko →
|
|
128
139
|
// keskiviikosta) makes stem+suffix logic wrong; store the forms.
|
|
@@ -404,6 +415,71 @@ function composeHourCadence(
|
|
|
404
415
|
hourRangeCadence(schedule, minute, opts);
|
|
405
416
|
}
|
|
406
417
|
|
|
418
|
+
// The minute field's step stride for the confinement frame, or null when the
|
|
419
|
+
// minute is not a stepped cadence. A `step`-shaped field reads its segment; a
|
|
420
|
+
// `list`-shaped field the core enumerated from a uneven step (`2/7` → 2,9,…,58)
|
|
421
|
+
// recovers the progression from its values.
|
|
422
|
+
function minuteStride(
|
|
423
|
+
schedule: Schedule
|
|
424
|
+
): {start: number; interval: number; last: number} | null {
|
|
425
|
+
if (schedule.shapes.minute === 'step') {
|
|
426
|
+
const segment = stepSegment(schedule, 'minute');
|
|
427
|
+
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
428
|
+
|
|
429
|
+
return {interval: segment.interval, last:
|
|
430
|
+
segment.fires[segment.fires.length - 1], start};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const values = singleValues(segmentsOf(schedule, 'minute'));
|
|
434
|
+
|
|
435
|
+
return values && arithmeticStep(values);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// A stepped minute under a wildcard/stepped second and wildcard hour: bind the
|
|
439
|
+
// second cadence to the minute cadence as a CONFINEMENT ("joka sekunti joka
|
|
440
|
+
// kuudentena minuuttina jokaisen tunnin minuutista 4 alkaen"), never the comma
|
|
441
|
+
// juxtaposition that reads as two independent cadences. The cadence is ORDINAL
|
|
442
|
+
// ("joka kuudentena minuuttina") — the cardinal "kuuden minuutin välein" is
|
|
443
|
+
// what fuels the misread — and the start/bound mirror the standalone minute
|
|
444
|
+
// cadence: an offset-clean stride names only its start, a uneven one pins both
|
|
445
|
+
// endpoints ("minuutista 2 minuuttiin 58").
|
|
446
|
+
function minuteStepConfinement(
|
|
447
|
+
schedule: Schedule,
|
|
448
|
+
stride: {start: number; interval: number; last: number},
|
|
449
|
+
opts: NormalizedOptions
|
|
450
|
+
): string {
|
|
451
|
+
const ordinalForm = minuteStepOrdinals[stride.interval];
|
|
452
|
+
const minute = units.minute;
|
|
453
|
+
const head = ordinalForm ?
|
|
454
|
+
' joka ' + ordinalForm + ' minuuttina' :
|
|
455
|
+
' ' + genitive(stride.interval, opts) + ' ' + minute.gen + ' välein';
|
|
456
|
+
|
|
457
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
458
|
+
bare: () => '',
|
|
459
|
+
offset: () => ' ' + minute.anchor + ' ' + minute.ela + ' ' +
|
|
460
|
+
stride.start + ' alkaen',
|
|
461
|
+
bounded: () => ' ' + minute.ela + ' ' + stride.start + ' ' +
|
|
462
|
+
minute.ill + ' ' + stride.last
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return secondsLeadClause(schedule, opts) + head + tail +
|
|
466
|
+
trailingQualifier(schedule, opts);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Whether a stepped minute fills a wildcard hour under a wildcard/stepped
|
|
470
|
+
// second — the shape the confinement frame above handles.
|
|
471
|
+
function isSteppedMinuteSeconds(
|
|
472
|
+
schedule: Schedule,
|
|
473
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
474
|
+
): boolean {
|
|
475
|
+
return (plan.rest.kind === 'minuteFrequency' ||
|
|
476
|
+
plan.rest.kind === 'multipleMinutes') &&
|
|
477
|
+
(schedule.pattern.second === '*' || schedule.shapes.second === 'step') &&
|
|
478
|
+
schedule.shapes.hour === 'wildcard' &&
|
|
479
|
+
schedule.pattern.minute !== '*/2' &&
|
|
480
|
+
minuteStride(schedule) !== null;
|
|
481
|
+
}
|
|
482
|
+
|
|
407
483
|
function renderComposeSeconds(
|
|
408
484
|
schedule: Schedule,
|
|
409
485
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -419,6 +495,16 @@ function renderComposeSeconds(
|
|
|
419
495
|
return cadence;
|
|
420
496
|
}
|
|
421
497
|
|
|
498
|
+
// A stepped minute under a wildcard/stepped second + wildcard hour confines
|
|
499
|
+
// the second cadence to the ordinal minute cadence ("joka sekunti joka
|
|
500
|
+
// kuudentena minuuttina jokaisen tunnin minuutista 4 alkaen"), never the
|
|
501
|
+
// comma juxtaposition that reads as two independent cadences. Checked before
|
|
502
|
+
// the general minute-step compose path, which keeps the comma form under a
|
|
503
|
+
// restricted hour.
|
|
504
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
505
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
|
|
506
|
+
}
|
|
507
|
+
|
|
422
508
|
// When the rest is a minute-step cadence, the step leads and the second
|
|
423
509
|
// anchor follows after a comma (the comma marks the granularity boundary
|
|
424
510
|
// between the two levels, not a flat list).
|
package/src/lang/fr/index.ts
CHANGED
|
@@ -127,6 +127,16 @@ const weekdayNames = [
|
|
|
127
127
|
const nthWeekdayMasculine =
|
|
128
128
|
[null, 'premier', 'deuxième', 'troisième', 'quatrième', 'cinquième'];
|
|
129
129
|
|
|
130
|
+
// French ordinals (gender-neutral "-ième") for a stepped-minute cadence under a
|
|
131
|
+
// seconds lead ("à la sixième minute"). The interval-2 step keeps its own
|
|
132
|
+
// idiom and never reaches here; a lookup miss falls back to the cardinal-with-
|
|
133
|
+
// preposition form, which still confines (see `minuteStepConfinement`).
|
|
134
|
+
const stepOrdinals: Record<number, string> = {
|
|
135
|
+
3: 'troisième', 4: 'quatrième', 5: 'cinquième', 6: 'sixième',
|
|
136
|
+
7: 'septième', 8: 'huitième', 9: 'neuvième', 10: 'dixième',
|
|
137
|
+
12: 'douzième', 15: 'quinzième', 20: 'vingtième', 30: 'trentième'
|
|
138
|
+
};
|
|
139
|
+
|
|
130
140
|
// Normalize raw user options.
|
|
131
141
|
function normalizeOptions(options?: Cronli5Options): Opts {
|
|
132
142
|
options = options || {};
|
|
@@ -279,6 +289,67 @@ function isPinnedMinuteSeconds(
|
|
|
279
289
|
schedule.shapes.second === 'step');
|
|
280
290
|
}
|
|
281
291
|
|
|
292
|
+
// The minute field's step stride for the confinement frame, or null when the
|
|
293
|
+
// minute is not a stepped cadence. A `step`-shaped field reads its segment; a
|
|
294
|
+
// `list`-shaped field the core enumerated from a uneven step (`2/7` → 2,9,…,58)
|
|
295
|
+
// recovers the progression from its values.
|
|
296
|
+
function minuteStride(
|
|
297
|
+
schedule: Schedule
|
|
298
|
+
): {start: number; interval: number; last: number} | null {
|
|
299
|
+
if (schedule.shapes.minute === 'step') {
|
|
300
|
+
const segment = stepSegment(schedule, 'minute');
|
|
301
|
+
const start = segment.startToken === '*' ? 0 : +segment.startToken;
|
|
302
|
+
|
|
303
|
+
return {interval: segment.interval, last:
|
|
304
|
+
segment.fires[segment.fires.length - 1], start};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const values = singleValues(segmentsOf(schedule, 'minute'));
|
|
308
|
+
|
|
309
|
+
return values && arithmeticStep(values);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// A stepped minute under a wildcard/stepped second and wildcard hour: bind the
|
|
313
|
+
// second cadence to the minute cadence as a CONFINEMENT ("chaque seconde à la
|
|
314
|
+
// sixième minute à partir de la minute 4 de chaque heure"), never the comma
|
|
315
|
+
// juxtaposition that reads as two independent cadences. The cadence is ORDINAL
|
|
316
|
+
// ("à la sixième minute") — the cardinal "toutes les six minutes" is what fuels
|
|
317
|
+
// the misread — and the start/bound mirror the standalone minute cadence.
|
|
318
|
+
function minuteStepConfinement(
|
|
319
|
+
schedule: Schedule,
|
|
320
|
+
stride: {start: number; interval: number; last: number},
|
|
321
|
+
opts: Opts
|
|
322
|
+
): string {
|
|
323
|
+
const ordinal = stepOrdinals[stride.interval];
|
|
324
|
+
const head = ordinal ?
|
|
325
|
+
'à la ' + ordinal + ' minute' :
|
|
326
|
+
'à la minute toutes les ' + numero(stride.interval, opts);
|
|
327
|
+
|
|
328
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
329
|
+
bare: () => '',
|
|
330
|
+
offset: () => ' à partir de la minute ' + stride.start,
|
|
331
|
+
bounded: () => ' de la minute ' + stride.start + ' à ' + stride.last
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return secondsLeadClause(schedule, opts) + ' ' + head + tail +
|
|
335
|
+
' de chaque heure' + trailingQualifier(schedule, opts);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Whether a stepped minute fills a wildcard hour under a wildcard/stepped
|
|
339
|
+
// second — the shape the confinement frame above handles.
|
|
340
|
+
function isSteppedMinuteSeconds(
|
|
341
|
+
schedule: Schedule,
|
|
342
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
343
|
+
): boolean {
|
|
344
|
+
return (plan.rest.kind === 'minuteFrequency' ||
|
|
345
|
+
plan.rest.kind === 'multipleMinutes') &&
|
|
346
|
+
(schedule.shapes.second === 'wildcard' ||
|
|
347
|
+
schedule.shapes.second === 'step') &&
|
|
348
|
+
schedule.shapes.hour === 'wildcard' &&
|
|
349
|
+
schedule.pattern.minute !== '*/2' &&
|
|
350
|
+
minuteStride(schedule) !== null;
|
|
351
|
+
}
|
|
352
|
+
|
|
282
353
|
function renderComposeSeconds(
|
|
283
354
|
schedule: Schedule,
|
|
284
355
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -322,6 +393,14 @@ function renderComposeSeconds(
|
|
|
322
393
|
return dayFrame + ', ' + window + ', ' + cadence;
|
|
323
394
|
}
|
|
324
395
|
|
|
396
|
+
// A stepped minute under a wildcard/stepped second + wildcard hour confines
|
|
397
|
+
// the second cadence to the ordinal minute cadence ("chaque seconde à la
|
|
398
|
+
// sixième minute à partir de la minute 4 de chaque heure"), never the comma
|
|
399
|
+
// juxtaposition that reads as two independent cadences.
|
|
400
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
401
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
|
|
402
|
+
}
|
|
403
|
+
|
|
325
404
|
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
|
326
405
|
// cadences that read as contradictory ("chaque seconde, toutes les deux
|
|
327
406
|
// minutes"). Bind them with the genitive "de" ("chaque seconde de chaque
|