cronli5 0.8.3 → 0.8.6
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 +59 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +23 -5
- package/dist/cronli5.js +23 -5
- package/dist/lang/de.cjs +49 -3
- package/dist/lang/de.js +49 -3
- package/dist/lang/en.cjs +23 -5
- package/dist/lang/en.js +23 -5
- 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 +64 -10
- 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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {orderWeekdaysForDisplay} from '../../core/weekday.js';
|
|
11
11
|
import {isOpenStep} from '../../core/shapes.js';
|
|
12
12
|
import {maxClockTimes} from '../../core/specs.js';
|
|
13
|
-
import {clockDigits, numeral
|
|
13
|
+
import {clockDigits, numeral} from '../../core/format.js';
|
|
14
14
|
import type {Cronli5Options} from '../../types.js';
|
|
15
15
|
import type {
|
|
16
16
|
HourTimesPlan, Schedule, Language, NormalizedOptions, PlanNode, Segment
|
|
@@ -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
|
|
|
@@ -1156,23 +1210,23 @@ function minuteConfinement(schedule: Schedule,
|
|
|
1156
1210
|
}
|
|
1157
1211
|
|
|
1158
1212
|
// A minute single/range/list under the seconds lead. The minute reads as a
|
|
1159
|
-
//
|
|
1213
|
+
// plain integer confinement, never "N minutes past the hour" (that is
|
|
1160
1214
|
// the minute-lead clock-point form).
|
|
1161
1215
|
const segments = segmentsOf(schedule, 'minute');
|
|
1162
1216
|
|
|
1163
1217
|
if (schedule.shapes.minute === 'single') {
|
|
1164
|
-
return ' during minute
|
|
1218
|
+
return ' during minute ' + Number(minute);
|
|
1165
1219
|
}
|
|
1166
1220
|
|
|
1167
1221
|
if (schedule.shapes.minute === 'range') {
|
|
1168
1222
|
const bounds = minute.split('-');
|
|
1169
1223
|
|
|
1170
|
-
return ' during minutes
|
|
1171
|
-
|
|
1224
|
+
return ' during minutes ' + Number(bounds[0]) + through(opts) +
|
|
1225
|
+
Number(bounds[1]);
|
|
1172
1226
|
}
|
|
1173
1227
|
|
|
1174
|
-
const values = segmentWords(segments, opts).map(function
|
|
1175
|
-
return
|
|
1228
|
+
const values = segmentWords(segments, opts).map(function plain(word) {
|
|
1229
|
+
return String(Number(word));
|
|
1176
1230
|
});
|
|
1177
1231
|
|
|
1178
1232
|
return ' during minutes ' + joinList(values, opts);
|