cronli5 0.8.3 → 0.8.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/CHANGELOG.md +47 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +19 -1
- package/dist/cronli5.js +19 -1
- package/dist/lang/de.cjs +49 -3
- package/dist/lang/de.js +49 -3
- package/dist/lang/en.cjs +19 -1
- package/dist/lang/en.js +19 -1
- package/dist/lang/es.cjs +68 -5
- package/dist/lang/es.js +68 -5
- package/dist/lang/fi.cjs +30 -0
- package/dist/lang/fi.js +30 -0
- package/dist/lang/fr.cjs +63 -25
- package/dist/lang/fr.js +63 -25
- package/dist/lang/pt.cjs +64 -25
- package/dist/lang/pt.js +64 -25
- package/dist/lang/zh.cjs +13 -3
- package/dist/lang/zh.js +13 -3
- package/package.json +1 -1
- package/src/lang/de/index.ts +119 -7
- package/src/lang/en/index.ts +57 -3
- package/src/lang/es/index.ts +158 -12
- package/src/lang/fi/index.ts +77 -0
- package/src/lang/fr/index.ts +132 -33
- package/src/lang/pt/index.ts +131 -36
- package/src/lang/zh/index.ts +35 -9
package/dist/lang/pt.js
CHANGED
|
@@ -206,20 +206,6 @@ function weekdayFeminine(number) {
|
|
|
206
206
|
}
|
|
207
207
|
var nthWeekdayMasculine = [null, "primeiro", "segundo", "terceiro", "quarto", "quinto"];
|
|
208
208
|
var nthWeekdayFeminine = [null, "primeira", "segunda", "terceira", "quarta", "quinta"];
|
|
209
|
-
var stepOrdinals = {
|
|
210
|
-
3: "terceiro",
|
|
211
|
-
4: "quarto",
|
|
212
|
-
5: "quinto",
|
|
213
|
-
6: "sexto",
|
|
214
|
-
7: "s\xE9timo",
|
|
215
|
-
8: "oitavo",
|
|
216
|
-
9: "nono",
|
|
217
|
-
10: "d\xE9cimo",
|
|
218
|
-
12: "d\xE9cimo segundo",
|
|
219
|
-
15: "d\xE9cimo quinto",
|
|
220
|
-
20: "vig\xE9simo",
|
|
221
|
-
30: "trig\xE9simo"
|
|
222
|
-
};
|
|
223
209
|
function noonMidnightArticle(phrase) {
|
|
224
210
|
if (phrase === "meio-dia") {
|
|
225
211
|
return "o";
|
|
@@ -325,6 +311,9 @@ function renderSecondsWithinMinute(schedule, plan, opts) {
|
|
|
325
311
|
if (plan.singleSecond) {
|
|
326
312
|
return "no minuto " + minuteField + " e no segundo " + schedule.pattern.second + " de cada hora" + trailingQualifier(schedule, opts);
|
|
327
313
|
}
|
|
314
|
+
if (secondsConfinesMinute(schedule)) {
|
|
315
|
+
return secondsBareLead(schedule) + " " + confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
316
|
+
}
|
|
328
317
|
return secondsLeadClause(schedule, opts) + ", no minuto " + minuteField + " de cada hora" + trailingQualifier(schedule, opts);
|
|
329
318
|
}
|
|
330
319
|
function secondsListAtClock(schedule, rest, opts) {
|
|
@@ -358,19 +347,68 @@ function minuteStride(schedule) {
|
|
|
358
347
|
const values = singleValues(segmentsOf(schedule, "minute"));
|
|
359
348
|
return values && arithmeticStep(values);
|
|
360
349
|
}
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
const head = ordinal ? "no " + ordinal + " minuto" : "a cada " + numero(stride.interval, opts) + " minutos";
|
|
364
|
-
const tail = renderStride({ ...stride, cycle: 60 }, {
|
|
365
|
-
bare: () => "",
|
|
366
|
-
offset: () => " a partir do minuto " + stride.start,
|
|
367
|
-
bounded: () => " do minuto " + stride.start + " ao " + stride.last
|
|
368
|
-
});
|
|
369
|
-
return secondsLeadClause(schedule, opts) + " " + head + tail + " de cada hora" + trailingQualifier(schedule, opts);
|
|
350
|
+
function steppedMinuteConfinement(schedule, plan, lead, opts) {
|
|
351
|
+
return lead + ", " + render(schedule, plan.rest, opts) + trailingQualifier(schedule, opts);
|
|
370
352
|
}
|
|
371
353
|
function isSteppedMinuteSeconds(schedule, plan) {
|
|
372
354
|
return (plan.rest.kind === "minuteFrequency" || plan.rest.kind === "multipleMinutes") && (schedule.shapes.second === "wildcard" || schedule.shapes.second === "step") && schedule.shapes.hour === "wildcard" && schedule.pattern.minute !== "*/2" && minuteStride(schedule) !== null;
|
|
373
355
|
}
|
|
356
|
+
function secondsBareLead(schedule) {
|
|
357
|
+
const secondField = schedule.pattern.second;
|
|
358
|
+
const shape = schedule.shapes.second;
|
|
359
|
+
if (shape === "range") {
|
|
360
|
+
const bounds = secondField.split("-");
|
|
361
|
+
return "a cada segundo do " + bounds[0] + " ao " + bounds[1];
|
|
362
|
+
}
|
|
363
|
+
if (shape === "single") {
|
|
364
|
+
return "no segundo " + secondField;
|
|
365
|
+
}
|
|
366
|
+
return "nos segundos " + joinList(segmentWords(segmentsOf(schedule, "second")));
|
|
367
|
+
}
|
|
368
|
+
function confinedMinutePhrase(schedule) {
|
|
369
|
+
if (schedule.shapes.minute === "range") {
|
|
370
|
+
const range = minuteRangeLead(schedule.pattern.minute).replace(/^a /u, "");
|
|
371
|
+
return "de " + range + " de cada hora";
|
|
372
|
+
}
|
|
373
|
+
if (schedule.shapes.minute === "list") {
|
|
374
|
+
return "dos minutos " + joinList(segmentWords(segmentsOf(schedule, "minute"))) + " de cada hora";
|
|
375
|
+
}
|
|
376
|
+
return "do minuto " + schedule.pattern.minute + " de cada hora";
|
|
377
|
+
}
|
|
378
|
+
function secondsConfinesMinute(schedule) {
|
|
379
|
+
const { second, minute, hour } = schedule.shapes;
|
|
380
|
+
if (second === "list") {
|
|
381
|
+
const values = singleValues(segmentsOf(schedule, "second"));
|
|
382
|
+
if (values && arithmeticStep(values)) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const clockPoint = second === "single" || second === "range" || second === "list";
|
|
387
|
+
return clockPoint && minute !== "wildcard" && hour === "wildcard" && !(second === "single" && minute === "single");
|
|
388
|
+
}
|
|
389
|
+
function minuteConfinementRender(plan, schedule, opts) {
|
|
390
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
391
|
+
return steppedMinuteConfinement(
|
|
392
|
+
schedule,
|
|
393
|
+
plan,
|
|
394
|
+
secondsLeadClause(schedule, opts),
|
|
395
|
+
opts
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const minuteRest = plan.rest.kind === "minuteFrequency" || plan.rest.kind === "multipleMinutes" || plan.rest.kind === "rangeOfMinutes";
|
|
399
|
+
if (minuteRest && secondsConfinesMinute(schedule)) {
|
|
400
|
+
if (minuteStride(schedule) && schedule.pattern.minute !== "*/2") {
|
|
401
|
+
return steppedMinuteConfinement(
|
|
402
|
+
schedule,
|
|
403
|
+
plan,
|
|
404
|
+
secondsBareLead(schedule),
|
|
405
|
+
opts
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return secondsBareLead(schedule) + " " + confinedMinutePhrase(schedule) + trailingQualifier(schedule, opts);
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
374
412
|
function renderComposeSeconds(schedule, plan, opts) {
|
|
375
413
|
const hourCad = composeHourCadence(schedule, plan, opts);
|
|
376
414
|
if (hourCad !== null) {
|
|
@@ -389,8 +427,9 @@ function renderComposeSeconds(schedule, plan, opts) {
|
|
|
389
427
|
const cadence = "a cada " + numero(stepSegment(schedule, "second").interval, opts) + " segundos do minuto " + schedule.pattern.minute;
|
|
390
428
|
return dayFrame + ", " + window + ", " + cadence;
|
|
391
429
|
}
|
|
392
|
-
|
|
393
|
-
|
|
430
|
+
const confined = minuteConfinementRender(plan, schedule, opts);
|
|
431
|
+
if (confined !== null) {
|
|
432
|
+
return confined;
|
|
394
433
|
}
|
|
395
434
|
if (isEveryOtherMinuteSeconds(schedule, plan)) {
|
|
396
435
|
const rest = render(schedule, plan.rest, opts).replace(/^a /u, "");
|
package/dist/lang/zh.cjs
CHANGED
|
@@ -573,8 +573,8 @@ function secondClause(schedule) {
|
|
|
573
573
|
return "\u6BCF\u79D2";
|
|
574
574
|
}
|
|
575
575
|
const first = segs[0];
|
|
576
|
-
if (segs.length === 1 && first.kind === "step"
|
|
577
|
-
return
|
|
576
|
+
if (segs.length === 1 && first.kind === "step") {
|
|
577
|
+
return stepClause(first, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F");
|
|
578
578
|
}
|
|
579
579
|
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u7B2C" + valueText(segs) + "\u79D2";
|
|
580
580
|
}
|
|
@@ -670,6 +670,9 @@ function composeSecondsCadence(schedule) {
|
|
|
670
670
|
return "\u6BCF\u5076\u6570\u5206\u949F\u7684\u6BCF\u4E00\u79D2";
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
|
+
if (!secondIsCadence(schedule) && !secondIsStride(schedule)) {
|
|
674
|
+
return minuteClause(schedule) + "\u7684" + sec;
|
|
675
|
+
}
|
|
673
676
|
return sec + "\uFF0C" + minuteClause(schedule);
|
|
674
677
|
}
|
|
675
678
|
return hourFrame(schedule) + tail;
|
|
@@ -683,7 +686,7 @@ function composeSecondsListed(schedule) {
|
|
|
683
686
|
return hourWord(hourFires(schedule)[0]) + minuteCad + "\u7684\u6BCF\u4E00\u79D2";
|
|
684
687
|
}
|
|
685
688
|
if (schedule.shapes.hour === "wildcard") {
|
|
686
|
-
return
|
|
689
|
+
return secondIsStride(schedule) ? minutes + "\uFF0C" + sec : minutes + confinedSecondTail(sec);
|
|
687
690
|
}
|
|
688
691
|
const hourCad = unevenHourCadence(schedule);
|
|
689
692
|
if (hourCad !== null) {
|
|
@@ -701,6 +704,13 @@ function isMinuteStride(schedule) {
|
|
|
701
704
|
function secondIsCadence(schedule) {
|
|
702
705
|
return schedule.pattern.second === "*" || schedule.shapes.second === "step";
|
|
703
706
|
}
|
|
707
|
+
function secondIsStride(schedule) {
|
|
708
|
+
if (schedule.shapes.second !== "list") {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
const values = singleValues(segmentsOf(schedule, "second"));
|
|
712
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
713
|
+
}
|
|
704
714
|
function confinedSecondTail(sec) {
|
|
705
715
|
return sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
|
|
706
716
|
}
|
package/dist/lang/zh.js
CHANGED
|
@@ -547,8 +547,8 @@ function secondClause(schedule) {
|
|
|
547
547
|
return "\u6BCF\u79D2";
|
|
548
548
|
}
|
|
549
549
|
const first = segs[0];
|
|
550
|
-
if (segs.length === 1 && first.kind === "step"
|
|
551
|
-
return
|
|
550
|
+
if (segs.length === 1 && first.kind === "step") {
|
|
551
|
+
return stepClause(first, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F");
|
|
552
552
|
}
|
|
553
553
|
return strideFromSegments(segs, "\u79D2", "\u79D2", "\u6BCF\u5206\u949F") ?? "\u7B2C" + valueText(segs) + "\u79D2";
|
|
554
554
|
}
|
|
@@ -644,6 +644,9 @@ function composeSecondsCadence(schedule) {
|
|
|
644
644
|
return "\u6BCF\u5076\u6570\u5206\u949F\u7684\u6BCF\u4E00\u79D2";
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
|
+
if (!secondIsCadence(schedule) && !secondIsStride(schedule)) {
|
|
648
|
+
return minuteClause(schedule) + "\u7684" + sec;
|
|
649
|
+
}
|
|
647
650
|
return sec + "\uFF0C" + minuteClause(schedule);
|
|
648
651
|
}
|
|
649
652
|
return hourFrame(schedule) + tail;
|
|
@@ -657,7 +660,7 @@ function composeSecondsListed(schedule) {
|
|
|
657
660
|
return hourWord(hourFires(schedule)[0]) + minuteCad + "\u7684\u6BCF\u4E00\u79D2";
|
|
658
661
|
}
|
|
659
662
|
if (schedule.shapes.hour === "wildcard") {
|
|
660
|
-
return
|
|
663
|
+
return secondIsStride(schedule) ? minutes + "\uFF0C" + sec : minutes + confinedSecondTail(sec);
|
|
661
664
|
}
|
|
662
665
|
const hourCad = unevenHourCadence(schedule);
|
|
663
666
|
if (hourCad !== null) {
|
|
@@ -675,6 +678,13 @@ function isMinuteStride(schedule) {
|
|
|
675
678
|
function secondIsCadence(schedule) {
|
|
676
679
|
return schedule.pattern.second === "*" || schedule.shapes.second === "step";
|
|
677
680
|
}
|
|
681
|
+
function secondIsStride(schedule) {
|
|
682
|
+
if (schedule.shapes.second !== "list") {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
const values = singleValues(segmentsOf(schedule, "second"));
|
|
686
|
+
return values !== null && arithmeticStep(values) !== null;
|
|
687
|
+
}
|
|
678
688
|
function confinedSecondTail(sec) {
|
|
679
689
|
return sec === "\u6BCF\u79D2" ? "\u7684\u6BCF\u4E00\u79D2" : "\u7684" + sec;
|
|
680
690
|
}
|
package/package.json
CHANGED
package/src/lang/de/index.ts
CHANGED
|
@@ -668,7 +668,17 @@ function renderSecondsWithinMinute(
|
|
|
668
668
|
schedule.pattern.second + ' jeder Stunde';
|
|
669
669
|
}
|
|
670
670
|
|
|
671
|
-
|
|
671
|
+
// A second LIST or RANGE under a single minute confines that minute in the
|
|
672
|
+
// genitive ("in den Sekunden 5 und 10 der Minute 30 jeder Stunde"), never the
|
|
673
|
+
// comma juxtaposition; a STEP second is a cadence and keeps its own lead.
|
|
674
|
+
if (secondsConfinesMinute(schedule)) {
|
|
675
|
+
return secondsLead(schedule) + ' ' + confinedMinutePhrase(schedule);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// A cadence/stepped second leads straight into the locative "in Minute …"
|
|
679
|
+
// with NO comma ("alle 15 Sekunden in Minute 30 jeder Stunde"); the locative
|
|
680
|
+
// binds the two specs, matching the no-comma list/single confinement.
|
|
681
|
+
return secondsLead(schedule) + ' in Minute ' + schedule.pattern.minute +
|
|
672
682
|
' jeder Stunde';
|
|
673
683
|
}
|
|
674
684
|
|
|
@@ -786,6 +796,96 @@ function isSteppedMinuteSeconds(
|
|
|
786
796
|
minuteStride(schedule) !== null;
|
|
787
797
|
}
|
|
788
798
|
|
|
799
|
+
// The CONFINED-minute phrase in the genitive that a clock-point second attaches
|
|
800
|
+
// to ("jeder sechsten Minute ab Minute 4 jeder Stunde", "der Minuten 0, 15 und
|
|
801
|
+
// 30 jeder Stunde", "der Minute 30 jeder Stunde"). A stepped minute reuses the
|
|
802
|
+
// ordinal cadence; a list, range, or single names the minute(s) in the genitive
|
|
803
|
+
// — so the seconds clause's bare lead never stacks a redundant "jeder Minute".
|
|
804
|
+
function confinedMinutePhrase(schedule: Schedule): string {
|
|
805
|
+
const stride = minuteStride(schedule);
|
|
806
|
+
|
|
807
|
+
if (stride && schedule.pattern.minute !== '*/2') {
|
|
808
|
+
const ordinal = minuteStepOrdinals[stride.interval];
|
|
809
|
+
const head = ordinal ?
|
|
810
|
+
'jeder ' + ordinal + ' Minute' :
|
|
811
|
+
'alle ' + stride.interval + ' Minuten';
|
|
812
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
813
|
+
bare: () => '',
|
|
814
|
+
offset: () => ' ab Minute ' + stride.start,
|
|
815
|
+
bounded: () => ' von Minute ' + stride.start + ' bis ' + stride.last
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
return head + tail + ' jeder Stunde';
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const genitive = schedule.shapes.minute === 'single' ?
|
|
822
|
+
'der Minute ' + schedule.pattern.minute :
|
|
823
|
+
'der Minuten ' + joinList(fieldValues(schedule, 'minute'));
|
|
824
|
+
|
|
825
|
+
return genitive + ' jeder Stunde';
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// The minute-confinement rendering for a compose-seconds plan, or null when the
|
|
829
|
+
// plan is not one. A CADENCE second over a stepped minute uses the ordinal
|
|
830
|
+
// cadence form; a CLOCK-POINT second (list/range/single) over any restricted
|
|
831
|
+
// minute uses the genitive form. Both bind the second beneath the minute
|
|
832
|
+
// instead of juxtaposing the two behind a comma. Folded into one helper so
|
|
833
|
+
// `renderComposeSeconds` carries a single branch.
|
|
834
|
+
function minuteConfinementRender(
|
|
835
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>, schedule: Schedule
|
|
836
|
+
): string | null {
|
|
837
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
838
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const minuteRest = plan.rest.kind === 'minuteFrequency' ||
|
|
842
|
+
plan.rest.kind === 'multipleMinutes' ||
|
|
843
|
+
plan.rest.kind === 'rangeOfMinutes';
|
|
844
|
+
|
|
845
|
+
if (minuteRest && secondsConfinesMinute(schedule)) {
|
|
846
|
+
return secondsLead(schedule) + ' ' + confinedMinutePhrase(schedule);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Whether a clock-point second (list, range, or single) sits under a restricted
|
|
853
|
+
// minute and a wildcard hour — the shape that must CONFINE the minute in the
|
|
854
|
+
// genitive rather than juxtapose it behind a comma (two independent schedules).
|
|
855
|
+
// A second LIST the core enumerated from a step (`3/2`) is really a stride
|
|
856
|
+
// cadence and stays out. The single-second + single-minute pair folds into one
|
|
857
|
+
// coherent clock point ("in Minute 5 und Sekunde 30 jeder Stunde") and is
|
|
858
|
+
// excluded.
|
|
859
|
+
function secondsConfinesMinute(schedule: Schedule): boolean {
|
|
860
|
+
const {second, minute, hour} = schedule.shapes;
|
|
861
|
+
|
|
862
|
+
if (second === 'list') {
|
|
863
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
864
|
+
|
|
865
|
+
if (values && arithmeticStep(values)) {
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
871
|
+
second === 'list';
|
|
872
|
+
|
|
873
|
+
return clockPoint && minute !== 'wildcard' && hour === 'wildcard' &&
|
|
874
|
+
!(second === 'single' && minute === 'single');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Whether a compose-seconds plan is a cadence/stepped second under a minute
|
|
878
|
+
// LIST or SINGLE and a wildcard hour — the shape that leads into the locative
|
|
879
|
+
// "in …" minute phrase with no comma. A restricted/cadence hour keeps the
|
|
880
|
+
// comma, so it does not qualify.
|
|
881
|
+
function isLocativeMinuteConfinement(
|
|
882
|
+
schedule: Schedule,
|
|
883
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
884
|
+
): boolean {
|
|
885
|
+
return (plan.rest.kind === 'multipleMinutes' ||
|
|
886
|
+
plan.rest.kind === 'singleMinute') && schedule.shapes.hour === 'wildcard';
|
|
887
|
+
}
|
|
888
|
+
|
|
789
889
|
function renderComposeSeconds(
|
|
790
890
|
schedule: Schedule,
|
|
791
891
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -818,12 +918,15 @@ function renderComposeSeconds(
|
|
|
818
918
|
clockMinuteGenitive(plan.rest.times, opts.style.sep);
|
|
819
919
|
}
|
|
820
920
|
|
|
821
|
-
// A
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
921
|
+
// A second confines the minute restriction (open hour), never the comma
|
|
922
|
+
// juxtaposition that reads as two independent cadences: a CADENCE second over
|
|
923
|
+
// a stepped minute uses the ordinal-cadence form ("jede Sekunde in jeder
|
|
924
|
+
// sechsten Minute …"); a CLOCK-POINT second uses the genitive form ("in den
|
|
925
|
+
// Sekunden 5, 10 und 15 jeder sechsten Minute …").
|
|
926
|
+
const confined = minuteConfinementRender(plan, schedule);
|
|
927
|
+
|
|
928
|
+
if (confined !== null) {
|
|
929
|
+
return confined;
|
|
827
930
|
}
|
|
828
931
|
|
|
829
932
|
// A wildcard second under a minute */2 with a wildcard hour binds in the
|
|
@@ -832,6 +935,15 @@ function renderComposeSeconds(
|
|
|
832
935
|
return secondsLead(schedule) + ' jeder zweiten Minute';
|
|
833
936
|
}
|
|
834
937
|
|
|
938
|
+
// A cadence/stepped second under a minute LIST or SINGLE and a wildcard hour
|
|
939
|
+
// leads straight into the locative minute phrase with NO comma ("jede Sekunde
|
|
940
|
+
// in den Minuten 0, 15 und 30 jeder Stunde"). The locative "in" already binds
|
|
941
|
+
// the two specs; the comma read as two independent specifications and is
|
|
942
|
+
// inconsistent with the no-comma stepped-minute and list-tier confinements.
|
|
943
|
+
if (isLocativeMinuteConfinement(schedule, plan)) {
|
|
944
|
+
return secondsLead(schedule) + ' ' + render(schedule, plan.rest, opts);
|
|
945
|
+
}
|
|
946
|
+
|
|
835
947
|
// A compact clock-time rest folds a meaningful SINGLE second into its own
|
|
836
948
|
// leading clause, so the composer must not prepend a second lead that would
|
|
837
949
|
// double it. A wildcard or stepped second is not folded there (no
|
package/src/lang/en/index.ts
CHANGED
|
@@ -1102,15 +1102,69 @@ function isCadenceField(token: string): boolean {
|
|
|
1102
1102
|
token.startsWith('*/') && token.indexOf('-') === -1;
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
+
// Whether the second field leads the confinement frame as a clean cadence. A
|
|
1106
|
+
// wildcard ("every second") and a clean `*/n` step both lead via
|
|
1107
|
+
// `isCadenceField`; an OPEN OFFSET step (`m/n`) is the SAME cadence, only named
|
|
1108
|
+
// from its offset ("every six seconds from five seconds past the minute"), so
|
|
1109
|
+
// it leads the SAME confinement rather than juxtaposing the minute restriction
|
|
1110
|
+
// behind a comma — whether the offset is clean from the top (`0/n`) or not
|
|
1111
|
+
// (`5/n`). A bounded step (`a-b/n`, a windowed set) is not an open cadence and
|
|
1112
|
+
// keeps its existing form.
|
|
1113
|
+
function secondLeadsCadence(schedule: Schedule): boolean {
|
|
1114
|
+
if (isCadenceField(schedule.pattern.second)) {
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (schedule.shapes.second !== 'step') {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Reached only under a stepped second the `isCadenceField` guard did not
|
|
1123
|
+
// already admit, so its `*/n` clean-cadence forms are gone and the remaining
|
|
1124
|
+
// open form is the offset step `m/n` (`0/n` or non-zero). A bounded step
|
|
1125
|
+
// `a-b/n` is a windowed set, not a cadence, and stays out.
|
|
1126
|
+
return isOpenStep(schedule.pattern.second);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Whether the second leads the confinement frame as a CLOCK-POINT clause (a
|
|
1130
|
+
// list, range, or single second), as opposed to a cadence. A clock-point second
|
|
1131
|
+
// under a minute restriction confines that restriction exactly as the cadence
|
|
1132
|
+
// does ("at 5, 10, and 15 seconds past the minute during every sixth minute …")
|
|
1133
|
+
// rather than juxtaposing it behind a comma, which reads as two independent
|
|
1134
|
+
// schedules. The single-second + single-minute pair is excluded: it folds into
|
|
1135
|
+
// one coherent clock point ("30 minutes and 15 seconds past the hour"), not a
|
|
1136
|
+
// juxtaposition, so it keeps that fold. The confinement only applies where the
|
|
1137
|
+
// minute is the restriction and the hour is open; `confinementEligible` gates
|
|
1138
|
+
// the rest (a restricted hour folds into a clock time, left to that renderer).
|
|
1139
|
+
function secondLeadsClockPoint(schedule: Schedule): boolean {
|
|
1140
|
+
// Only a MEANINGFUL second leads a clause: the two seconds-bearing plans the
|
|
1141
|
+
// core chooses for a real second. A 5-field pattern (or an explicit `0`
|
|
1142
|
+
// second) carries no seconds clause — its plan is the minute's own — so it is
|
|
1143
|
+
// not confined here, which would otherwise prepend "at zero seconds …".
|
|
1144
|
+
if (schedule.plan.kind !== 'composeSeconds' &&
|
|
1145
|
+
schedule.plan.kind !== 'secondsWithinMinute') {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const {second, minute, hour} = schedule.shapes;
|
|
1150
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
1151
|
+
second === 'list';
|
|
1152
|
+
const minuteRestricted = minute !== 'wildcard';
|
|
1153
|
+
|
|
1154
|
+
return clockPoint && minuteRestricted && hour === 'wildcard' &&
|
|
1155
|
+
!(second === 'single' && minute === 'single');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1105
1158
|
// The leading cadence and whether the second is the leading field, or null when
|
|
1106
1159
|
// the pattern has no cadence lead (the finest restricted field is a clock-point
|
|
1107
|
-
// single/range/list). The seconds lead when restricted as a cadence
|
|
1108
|
-
//
|
|
1160
|
+
// single/range/list). The seconds lead when restricted as a cadence, or as a
|
|
1161
|
+
// clock-point clause that confines a minute restriction; otherwise the minute
|
|
1162
|
+
// leads when the second is a plain :00 and the minute is a cadence.
|
|
1109
1163
|
function leadingCadence(schedule: Schedule, opts: NormalizedOptions):
|
|
1110
1164
|
{text: string; secondLead: boolean} | null {
|
|
1111
1165
|
const {second, minute} = schedule.pattern;
|
|
1112
1166
|
|
|
1113
|
-
if (
|
|
1167
|
+
if (secondLeadsCadence(schedule) || secondLeadsClockPoint(schedule)) {
|
|
1114
1168
|
return {secondLead: true, text: secondsClause(schedule, 'minute', opts)};
|
|
1115
1169
|
}
|
|
1116
1170
|
|
package/src/lang/es/index.ts
CHANGED
|
@@ -215,7 +215,19 @@ function renderSecondsWithinMinute(
|
|
|
215
215
|
trailingQualifier(schedule, opts);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
// A second LIST or RANGE under a single minute confines that minute with the
|
|
219
|
+
// genitive "de" ("en los segundos 5 y 10 del minuto 30 de cada hora"), never
|
|
220
|
+
// the comma juxtaposition that reads as two independent schedules. A STEP
|
|
221
|
+
// second is a cadence ("cada 15 segundos") and keeps its own lead.
|
|
222
|
+
if (secondsConfinesMinute(schedule)) {
|
|
223
|
+
return secondsBareLead(schedule) + ' ' +
|
|
224
|
+
confinedMinutePhrase(schedule, opts) + trailingQualifier(schedule, opts);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A cadence/stepped second leads straight into the locative "en el minuto …"
|
|
228
|
+
// with NO comma ("cada 15 segundos en el minuto 30 de cada hora"); the
|
|
229
|
+
// locative binds the two specs, matching the no-comma list/single form.
|
|
230
|
+
return secondsLeadClause(schedule, opts) + ' en el minuto ' + minuteField +
|
|
219
231
|
' de cada hora' + trailingQualifier(schedule, opts);
|
|
220
232
|
}
|
|
221
233
|
|
|
@@ -345,6 +357,136 @@ function isSteppedMinuteSeconds(
|
|
|
345
357
|
minuteStride(schedule) !== null;
|
|
346
358
|
}
|
|
347
359
|
|
|
360
|
+
// The leading seconds words for a clock-point second, WITHOUT the trailing "de
|
|
361
|
+
// cada minuto" anchor: a confined second attaches to the CONFINED minute ("de
|
|
362
|
+
// cada sexto minuto…"), so the generic minute anchor would be redundant. The
|
|
363
|
+
// list/range/single forms mirror `secondsClause` minus that anchor.
|
|
364
|
+
function secondsBareLead(schedule: Schedule): string {
|
|
365
|
+
const secondField = schedule.pattern.second;
|
|
366
|
+
const shape = schedule.shapes.second;
|
|
367
|
+
|
|
368
|
+
if (shape === 'range') {
|
|
369
|
+
const bounds = secondField.split('-');
|
|
370
|
+
|
|
371
|
+
return 'cada segundo del ' + bounds[0] + ' al ' + bounds[1];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (shape === 'single') {
|
|
375
|
+
return 'en el segundo ' + secondField;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return 'en los segundos ' +
|
|
379
|
+
joinList(segmentWords(segmentsOf(schedule, 'second')));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// The CONFINED-minute genitive phrase a clock-point second attaches to, with
|
|
383
|
+
// its leading connector folded in ("de cada sexto minuto a partir del minuto 4
|
|
384
|
+
// de cada hora", "de los minutos 0, 15 y 30 de cada hora", "del minuto 30 de
|
|
385
|
+
// cada hora"). A stepped minute reuses the ordinal cadence form; a list, range,
|
|
386
|
+
// or single names the minute(s) directly. This is the seconds clause's anchor,
|
|
387
|
+
// so the generic "de cada minuto" is never stacked alongside it.
|
|
388
|
+
function confinedMinutePhrase(schedule: Schedule, opts: Opts): string {
|
|
389
|
+
const stride = minuteStride(schedule);
|
|
390
|
+
|
|
391
|
+
if (stride && schedule.pattern.minute !== '*/2') {
|
|
392
|
+
const ordinal = stepOrdinals[stride.interval];
|
|
393
|
+
const head = ordinal ?
|
|
394
|
+
'cada ' + ordinal + ' minuto' :
|
|
395
|
+
'cada ' + numero(stride.interval, opts) + ' minutos';
|
|
396
|
+
const tail = chooseStride({...stride, cycle: 60}, {
|
|
397
|
+
bare: () => '',
|
|
398
|
+
offset: () => ' a partir del minuto ' + stride.start,
|
|
399
|
+
bounded: () => ' del minuto ' + stride.start + ' al ' + stride.last
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
return 'de ' + head + tail + ' de cada hora';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (schedule.shapes.minute === 'range') {
|
|
406
|
+
return 'de ' + minuteRangeLead(schedule.pattern.minute) + ' de cada hora';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (schedule.shapes.minute === 'list') {
|
|
410
|
+
return 'de los minutos ' +
|
|
411
|
+
joinList(segmentWords(segmentsOf(schedule, 'minute'))) + ' de cada hora';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// A single pinned minute: "del minuto 30 de cada hora".
|
|
415
|
+
return 'del minuto ' + schedule.pattern.minute + ' de cada hora';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Whether a clock-point second (list, range, or single) sits under a restricted
|
|
419
|
+
// minute and a wildcard hour — the shape that must CONFINE the minute with the
|
|
420
|
+
// genitive "de" rather than juxtapose it behind a comma (two independent
|
|
421
|
+
// schedules). The single-second + single-minute pair folds into one coherent
|
|
422
|
+
// clock point ("en el minuto 5 y el segundo 30 de cada hora") and is excluded.
|
|
423
|
+
// The minute-confinement rendering for a compose-seconds plan, or null when the
|
|
424
|
+
// plan is not one. A CADENCE second over a stepped minute uses the ordinal
|
|
425
|
+
// cadence form; a CLOCK-POINT second (list/range/single) over any restricted
|
|
426
|
+
// minute uses the genitive form anchored to the confined minute. Both bind the
|
|
427
|
+
// second beneath the minute instead of juxtaposing the two behind a comma.
|
|
428
|
+
// Folded into one helper so `renderComposeSeconds` carries a single branch.
|
|
429
|
+
function minuteConfinementRender(
|
|
430
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
431
|
+
schedule: Schedule, opts: Opts
|
|
432
|
+
): string | null {
|
|
433
|
+
if (isSteppedMinuteSeconds(schedule, plan)) {
|
|
434
|
+
return minuteStepConfinement(schedule, minuteStride(schedule)!, opts);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const minuteRest = plan.rest.kind === 'minuteFrequency' ||
|
|
438
|
+
plan.rest.kind === 'multipleMinutes' ||
|
|
439
|
+
plan.rest.kind === 'rangeOfMinutes';
|
|
440
|
+
|
|
441
|
+
if (minuteRest && secondsConfinesMinute(schedule)) {
|
|
442
|
+
return secondsBareLead(schedule) + ' ' +
|
|
443
|
+
confinedMinutePhrase(schedule, opts) + trailingQualifier(schedule, opts);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function secondsConfinesMinute(schedule: Schedule): boolean {
|
|
450
|
+
const {second, minute, hour} = schedule.shapes;
|
|
451
|
+
|
|
452
|
+
// A second LIST the core enumerated from a step (`*/15` → 0,15,30,45; `3/2` →
|
|
453
|
+
// 3,5,…) is really a stride CADENCE, spoken "cada N segundos" and confined by
|
|
454
|
+
// the cadence path, not a clock-point clause; exclude it here.
|
|
455
|
+
if (second === 'list') {
|
|
456
|
+
const values = singleValues(segmentsOf(schedule, 'second'));
|
|
457
|
+
|
|
458
|
+
if (values && arithmeticStep(values)) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const clockPoint = second === 'single' || second === 'range' ||
|
|
464
|
+
second === 'list';
|
|
465
|
+
|
|
466
|
+
return clockPoint && minute !== 'wildcard' && hour === 'wildcard' &&
|
|
467
|
+
!(second === 'single' && minute === 'single');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// The seconds lead plus its connector for a generic compose-seconds fallback:
|
|
471
|
+
// empty when the rest already owns the second (a compact clock time folding a
|
|
472
|
+
// meaningful single second); a bare space when the rest is a locative "en …"
|
|
473
|
+
// minute LIST/SINGLE under a wildcard hour (the locative binds the two specs,
|
|
474
|
+
// so no comma); otherwise the comma that sets the seconds clause apart.
|
|
475
|
+
function composeConnector(
|
|
476
|
+
schedule: Schedule,
|
|
477
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
478
|
+
opts: Opts
|
|
479
|
+
): string {
|
|
480
|
+
if (plan.rest.kind === 'compactClockTimes' && schedule.analyses.clockSecond) {
|
|
481
|
+
return '';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const locative = (plan.rest.kind === 'multipleMinutes' ||
|
|
485
|
+
plan.rest.kind === 'singleMinute') && schedule.shapes.hour === 'wildcard';
|
|
486
|
+
|
|
487
|
+
return secondsLeadClause(schedule, opts) + (locative ? ' ' : ', ');
|
|
488
|
+
}
|
|
489
|
+
|
|
348
490
|
function renderComposeSeconds(
|
|
349
491
|
schedule: Schedule,
|
|
350
492
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -388,12 +530,15 @@ function renderComposeSeconds(
|
|
|
388
530
|
return dayFrame + ', ' + window + ', ' + cadence;
|
|
389
531
|
}
|
|
390
532
|
|
|
391
|
-
// A
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
533
|
+
// A second confines the minute restriction (open hour), never the comma
|
|
534
|
+
// juxtaposition that reads as two independent cadences: a CADENCE second over
|
|
535
|
+
// a stepped minute uses the ordinal-cadence form ("cada segundo en cada sexto
|
|
536
|
+
// minuto …"); a CLOCK-POINT second uses the genitive form anchored to the
|
|
537
|
+
// confined minute ("en los segundos 5, 10 y 15 de cada sexto minuto …").
|
|
538
|
+
const confined = minuteConfinementRender(plan, schedule, opts);
|
|
539
|
+
|
|
540
|
+
if (confined !== null) {
|
|
541
|
+
return confined;
|
|
397
542
|
}
|
|
398
543
|
|
|
399
544
|
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
|
@@ -408,11 +553,12 @@ function renderComposeSeconds(
|
|
|
408
553
|
|
|
409
554
|
// A compact clock-time rest folds a meaningful SINGLE second into its own
|
|
410
555
|
// leading clause, so the composer must not prepend a second lead that would
|
|
411
|
-
// double it. A
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
556
|
+
// double it (empty connector). A cadence/stepped second under a minute
|
|
557
|
+
// LIST or SINGLE and a wildcard hour leads straight into the locative "en …"
|
|
558
|
+
// minute phrase with NO comma ("cada segundo en los minutos 0, 15 y 30 de
|
|
559
|
+
// cada hora") — the locative binds the two specs, matching the no-comma
|
|
560
|
+
// stepped-minute and list-tier confinements. Every other rest takes a comma.
|
|
561
|
+
const lead = composeConnector(schedule, plan, opts);
|
|
416
562
|
|
|
417
563
|
return lead + render(schedule, plan.rest, opts);
|
|
418
564
|
}
|