cronli5 0.1.5 → 0.1.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 +28 -0
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +117 -20
- package/dist/cronli5.js +117 -20
- package/dist/lang/de.cjs +94 -19
- package/dist/lang/de.js +94 -19
- package/dist/lang/en.cjs +117 -20
- package/dist/lang/en.js +117 -20
- package/dist/lang/es.cjs +89 -14
- package/dist/lang/es.js +89 -14
- package/dist/lang/fi.cjs +107 -19
- package/dist/lang/fi.js +107 -19
- package/dist/lang/zh.cjs +90 -53
- package/dist/lang/zh.js +90 -53
- package/package.json +2 -2
- package/src/lang/de/index.ts +238 -51
- package/src/lang/en/index.ts +280 -46
- package/src/lang/es/index.ts +222 -31
- package/src/lang/fi/index.ts +245 -39
- package/src/lang/zh/index.ts +209 -94
package/src/lang/de/index.ts
CHANGED
|
@@ -670,6 +670,25 @@ function renderMinuteSpanInHour(
|
|
|
670
670
|
|
|
671
671
|
// Seconds composed with the rest: "in den Sekunden 0 und 30 jeder Minute, um
|
|
672
672
|
// 9:05 Uhr".
|
|
673
|
+
// A wildcard second under a minute */2 with a wildcard hour juxtaposes two
|
|
674
|
+
// cadences that read as contradictory ("jede Sekunde, alle 2 Minuten"). Bind
|
|
675
|
+
// them in the genitive ("jede Sekunde jeder zweiten Minute"), mirroring
|
|
676
|
+
// English. Other strides, a restricted hour, and an hour cadence keep the
|
|
677
|
+
// juxtaposed form.
|
|
678
|
+
function isEveryOtherMinuteSeconds(
|
|
679
|
+
ir: IR,
|
|
680
|
+
plan: Extract<PlanNode, {kind: 'composeSeconds'}>
|
|
681
|
+
): boolean {
|
|
682
|
+
if (plan.rest.kind !== 'minuteFrequency' ||
|
|
683
|
+
ir.shapes.second !== 'wildcard' || ir.shapes.hour !== 'wildcard') {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const minuteStep = stepSegment(ir.analyses.segments.minute);
|
|
688
|
+
|
|
689
|
+
return minuteStep.startToken === '*' && minuteStep.interval === 2;
|
|
690
|
+
}
|
|
691
|
+
|
|
673
692
|
function renderComposeSeconds(
|
|
674
693
|
ir: IR,
|
|
675
694
|
plan: Extract<PlanNode, {kind: 'composeSeconds'}>,
|
|
@@ -682,7 +701,8 @@ function renderComposeSeconds(
|
|
|
682
701
|
if ((plan.rest.kind === 'clockTimes' ||
|
|
683
702
|
plan.rest.kind === 'compactClockTimes') &&
|
|
684
703
|
ir.shapes.minute === 'single') {
|
|
685
|
-
const
|
|
704
|
+
const minute = +ir.pattern.minute;
|
|
705
|
+
const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
|
|
686
706
|
|
|
687
707
|
if (cadence !== null) {
|
|
688
708
|
return cadence;
|
|
@@ -699,21 +719,21 @@ function renderComposeSeconds(
|
|
|
699
719
|
clockMinuteGenitive(plan.rest.times, opts.style.sep);
|
|
700
720
|
}
|
|
701
721
|
|
|
702
|
-
// A wildcard second under a minute */2 with a wildcard hour
|
|
703
|
-
//
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
// juxtaposed form.
|
|
707
|
-
if (plan.rest.kind === 'minuteFrequency' &&
|
|
708
|
-
ir.shapes.second === 'wildcard' && ir.shapes.hour === 'wildcard') {
|
|
709
|
-
const minuteStep = stepSegment(ir.analyses.segments.minute);
|
|
710
|
-
|
|
711
|
-
if (minuteStep.startToken === '*' && minuteStep.interval === 2) {
|
|
712
|
-
return secondsLead(ir) + ' jeder zweiten Minute';
|
|
713
|
-
}
|
|
722
|
+
// A wildcard second under a minute */2 with a wildcard hour binds in the
|
|
723
|
+
// genitive ("jede Sekunde jeder zweiten Minute").
|
|
724
|
+
if (isEveryOtherMinuteSeconds(ir, plan)) {
|
|
725
|
+
return secondsLead(ir) + ' jeder zweiten Minute';
|
|
714
726
|
}
|
|
715
727
|
|
|
716
|
-
|
|
728
|
+
// A compact clock-time rest folds a meaningful SINGLE second into its own
|
|
729
|
+
// leading clause, so the composer must not prepend a second lead that would
|
|
730
|
+
// double it. A wildcard or stepped second is not folded there (no
|
|
731
|
+
// clockSecond), so it still leads its own clause here.
|
|
732
|
+
const restOwnsLead = plan.rest.kind === 'compactClockTimes' &&
|
|
733
|
+
ir.analyses.clockSecond;
|
|
734
|
+
const lead = restOwnsLead ? '' : secondsLead(ir) + ', ';
|
|
735
|
+
|
|
736
|
+
return lead + render(ir, plan.rest, opts);
|
|
717
737
|
}
|
|
718
738
|
|
|
719
739
|
// True when a compose-seconds plan is a sub-minute second over a minute-0
|
|
@@ -752,18 +772,30 @@ function renderMinutesAcrossHours(
|
|
|
752
772
|
opts: Opts
|
|
753
773
|
): string {
|
|
754
774
|
const sep = opts.style.sep;
|
|
775
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence,
|
|
776
|
+
// not a wall of hour columns.
|
|
777
|
+
const cadence = unevenHourCadence(ir);
|
|
755
778
|
|
|
756
779
|
// The wildcard form means every minute *during* each hour: render windows.
|
|
757
780
|
if (plan.form === 'wildcard') {
|
|
758
|
-
return
|
|
781
|
+
return cadence ?
|
|
782
|
+
'jede Minute, ' + cadence :
|
|
783
|
+
'jede Minute ' + duringHours(ir, plan.times, sep);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const minuteLead =
|
|
787
|
+
strideFromSegments(fieldSegments(ir, 'minute'), UNITS.minute, '') ??
|
|
788
|
+
countedPhrase(ir, 'minute', 'Minute', 'Minuten');
|
|
789
|
+
|
|
790
|
+
if (cadence !== null) {
|
|
791
|
+
return minuteLead + ', ' + cadence;
|
|
759
792
|
}
|
|
760
793
|
|
|
761
794
|
const hours = plan.times.kind === 'fires' ?
|
|
762
795
|
atHours(plan.times.fires) :
|
|
763
796
|
joinList(hourSegmentParts(ir, 0, 0, sep));
|
|
764
797
|
|
|
765
|
-
return
|
|
766
|
-
countedPhrase(ir, 'minute', 'Minute', 'Minuten')) + ', ' + hours;
|
|
798
|
+
return minuteLead + ', ' + hours;
|
|
767
799
|
}
|
|
768
800
|
|
|
769
801
|
// A minute clause across a stepped hour range. A wildcard minute (a cadence)
|
|
@@ -774,6 +806,12 @@ function renderMinuteSpanAcrossHourStep(
|
|
|
774
806
|
ir: IR,
|
|
775
807
|
plan: Extract<PlanNode, {kind: 'minuteSpanAcrossHourStep'}>
|
|
776
808
|
): string {
|
|
809
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence; an
|
|
810
|
+
// offset-clean stride keeps its "in jeder N-ten Stunde" confinement.
|
|
811
|
+
const cadence = unevenHourCadence(ir);
|
|
812
|
+
|
|
813
|
+
// A wildcard minute over a stepped hour is reached only for a clean stride (a
|
|
814
|
+
// bounded or uneven step routes through minutesAcrossHours instead).
|
|
777
815
|
if (plan.form === 'wildcard') {
|
|
778
816
|
return 'jede Minute ' +
|
|
779
817
|
everyNthHour(stepSegment(ir.analyses.segments.hour));
|
|
@@ -782,11 +820,11 @@ function renderMinuteSpanAcrossHourStep(
|
|
|
782
820
|
// The minute (range or list) leads; the hour trails. A clean stride confines
|
|
783
821
|
// to "in jeder N-ten Stunde" — the same cadence the wildcard form and the
|
|
784
822
|
// minute-step compositions use, never a juxtaposed second frequency. A
|
|
785
|
-
// bounded
|
|
823
|
+
// bounded or uneven stride trails its endpoint-pinning cadence instead.
|
|
786
824
|
const segment = stepSegment(ir.analyses.segments.hour);
|
|
787
|
-
const hours = confinedHourStride(segment) ?
|
|
825
|
+
const hours = cadence ?? (confinedHourStride(segment) ?
|
|
788
826
|
everyNthHour(segment) :
|
|
789
|
-
atHours(segment.fires);
|
|
827
|
+
atHours(segment.fires));
|
|
790
828
|
|
|
791
829
|
return (strideFromSegments(fieldSegments(ir, 'minute'), UNITS.minute, '') ??
|
|
792
830
|
countedPhrase(ir, 'minute', 'Minute', 'Minuten')) + ', ' + hours;
|
|
@@ -805,10 +843,11 @@ function renderCompactClockTimes(
|
|
|
805
843
|
const sep = opts.style.sep;
|
|
806
844
|
|
|
807
845
|
if (plan.fold) {
|
|
808
|
-
// An hour step (or arithmetic-progression hour list) under the
|
|
809
|
-
// pinned minute reads as a cadence, not a wall of clock
|
|
810
|
-
// null for an irregular list
|
|
811
|
-
const cadence = hourCadence(ir, plan.minute)
|
|
846
|
+
// An hour step or range (or arithmetic-progression hour list) under the
|
|
847
|
+
// single pinned minute reads as a cadence or window, not a wall of clock
|
|
848
|
+
// times. (Returns null for an irregular list, which keeps folding below.)
|
|
849
|
+
const cadence = hourCadence(ir, plan.minute) ??
|
|
850
|
+
hourRangeCadence(ir, plan.minute);
|
|
812
851
|
|
|
813
852
|
if (cadence !== null) {
|
|
814
853
|
return cadence;
|
|
@@ -821,11 +860,12 @@ function renderCompactClockTimes(
|
|
|
821
860
|
joinList(hourSegmentParts(ir, plan.minute, ir.analyses.clockSecond, sep));
|
|
822
861
|
}
|
|
823
862
|
|
|
824
|
-
// A
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
863
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence; else
|
|
864
|
+
// a range among the hours reads as a window, otherwise a flat hour list.
|
|
865
|
+
const hours = unevenHourCadence(ir) ??
|
|
866
|
+
(fieldSegments(ir, 'hour').some((segment) => segment.kind === 'range') ?
|
|
867
|
+
joinList(hourSegmentParts(ir, 0, 0, sep)) :
|
|
868
|
+
atHours(hourFires(ir)));
|
|
829
869
|
|
|
830
870
|
// A folded second has no single clock time to attach to here, so it leads
|
|
831
871
|
// as its own clause ("in Sekunde 30, ..."). It is the bare second (not
|
|
@@ -858,7 +898,14 @@ function renderMinuteFrequency(
|
|
|
858
898
|
}
|
|
859
899
|
|
|
860
900
|
if (plan.hours.kind === 'during') {
|
|
861
|
-
|
|
901
|
+
// A bounded or uneven hour stride confines the minute cadence to its own
|
|
902
|
+
// endpoint-pinning hour cadence ("alle 15 Minuten, alle 5 Stunden von 0 bis
|
|
903
|
+
// 20 Uhr").
|
|
904
|
+
const cadence = unevenHourCadence(ir);
|
|
905
|
+
|
|
906
|
+
return cadence ?
|
|
907
|
+
base + ', ' + cadence :
|
|
908
|
+
base + ' ' + duringHours(ir, plan.hours.times, sep);
|
|
862
909
|
}
|
|
863
910
|
|
|
864
911
|
if (plan.hours.kind === 'step') {
|
|
@@ -871,10 +918,16 @@ function renderMinuteFrequency(
|
|
|
871
918
|
return base;
|
|
872
919
|
}
|
|
873
920
|
|
|
874
|
-
// A stepped hour field as a phrase:
|
|
875
|
-
//
|
|
876
|
-
// Uhr"). Shared by the bare hour step and the minute-step compositions.
|
|
921
|
+
// A stepped hour field as a phrase: an offset-clean stride is its bare or "ab"
|
|
922
|
+
// cadence; a bounded or uneven stride pins both ends ("alle 2 Stunden von 9
|
|
923
|
+
// bis 17 Uhr"). Shared by the bare hour step and the minute-step compositions.
|
|
877
924
|
function hourStepPhrase(ir: IR): string {
|
|
925
|
+
const cadence = unevenHourCadence(ir);
|
|
926
|
+
|
|
927
|
+
if (cadence !== null) {
|
|
928
|
+
return cadence;
|
|
929
|
+
}
|
|
930
|
+
|
|
878
931
|
const segment = stepSegment(ir.analyses.segments.hour);
|
|
879
932
|
|
|
880
933
|
return cleanStep(segment, 24) ?
|
|
@@ -909,12 +962,56 @@ function hourStrideCadence(
|
|
|
909
962
|
return cadence + ' von ' + start + ' bis ' + last + ' Uhr';
|
|
910
963
|
}
|
|
911
964
|
|
|
965
|
+
// An hour list's arithmetic progression, or null when its values are not a step
|
|
966
|
+
// the renderer should speak as a cadence. The core rewrites a uneven hour step
|
|
967
|
+
// (whose interval does not tile 24, e.g. `*/5` → 0,5,10,15,20) to its literal
|
|
968
|
+
// fire list, indistinguishable in the IR from a hand-written list; the renderer
|
|
969
|
+
// recovers the cadence from the values. A progression starting at zero is a
|
|
970
|
+
// `*/n` step however short (0,7,14,21 is `*/7`); a non-zero progression is only
|
|
971
|
+
// a step when it is too long to be a deliberate clock-time list (9,17 is two
|
|
972
|
+
// named times, not a cadence). Interval one is a plain range, never a step.
|
|
973
|
+
function hourListStride(
|
|
974
|
+
values: number[]
|
|
975
|
+
): {start: number; interval: number; last: number} | null {
|
|
976
|
+
if (values.length < 2) {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const interval = values[1] - values[0];
|
|
981
|
+
|
|
982
|
+
if (interval < 2) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
987
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (values[0] !== 0 && values.length < 5) {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return {interval, last: values[values.length - 1], start: values[0]};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Whether an hour stride wraps the day cleanly from within its first interval
|
|
1000
|
+
// (a `*/n` from the top, or a `m/n` offset with m < n that divides 24): such a
|
|
1001
|
+
// stride has no distinct endpoint and keeps its bare or "ab" cadence. Every
|
|
1002
|
+
// other stride — a uneven interval, or one starting at or past its interval (a
|
|
1003
|
+
// bounded `a-b/n`) — is a bounded set the cadence pins both endpoints of.
|
|
1004
|
+
function offsetCleanStride(
|
|
1005
|
+
stride: {start: number; interval: number}
|
|
1006
|
+
): boolean {
|
|
1007
|
+
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
912
1010
|
// The hour field's stride, or null when the hour is not a cadence: a step
|
|
913
1011
|
// segment yields its {start, interval, last} directly; an all-single hour list
|
|
914
|
-
// yields one only when its values form a
|
|
915
|
-
//
|
|
916
|
-
// the
|
|
917
|
-
// clock-time cross-product.
|
|
1012
|
+
// yields one only when its values form a step progression (so an irregular list
|
|
1013
|
+
// like 9,17 keeps enumerating). The IR is unchanged — the renderer recognizes
|
|
1014
|
+
// the stride and speaks it as a cadence, not the clock-time cross-product.
|
|
918
1015
|
function hourStride(
|
|
919
1016
|
ir: IR
|
|
920
1017
|
): {start: number; interval: number; last: number} | null {
|
|
@@ -927,6 +1024,13 @@ function hourStride(
|
|
|
927
1024
|
|
|
928
1025
|
if (segments.length === 1 && segments[0].kind === 'step') {
|
|
929
1026
|
const segment = segments[0];
|
|
1027
|
+
|
|
1028
|
+
// A bounded step that fires only once (e.g. `9-10/5` -> just 9) is a single
|
|
1029
|
+
// value, not a stride: it has no interval to speak and no endpoint to pin.
|
|
1030
|
+
if (segment.fires.length < 2) {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
930
1034
|
const start = segment.startToken === '*' ?
|
|
931
1035
|
0 :
|
|
932
1036
|
+segment.startToken.split('-')[0];
|
|
@@ -936,9 +1040,25 @@ function hourStride(
|
|
|
936
1040
|
}
|
|
937
1041
|
|
|
938
1042
|
const values = singleValues(segments);
|
|
939
|
-
const step = values && arithmeticStep(values);
|
|
940
1043
|
|
|
941
|
-
return
|
|
1044
|
+
return values && hourListStride(values);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// The bounded cadence for an hour stride that pins both clock-time endpoints,
|
|
1048
|
+
// or null when the hour is not such a stride. The core rewrites a uneven step
|
|
1049
|
+
// to its fire list, so a minute window/list/step crossed with it lands in the
|
|
1050
|
+
// enumerating list paths; there the bounded hour reads better as its cadence
|
|
1051
|
+
// ("…, alle 5 Stunden von 0 bis 20 Uhr") than as a wall of clock times. An
|
|
1052
|
+
// offset-clean stride keeps its existing confinement form, so only the
|
|
1053
|
+
// endpoint-bearing case routes here.
|
|
1054
|
+
function unevenHourCadence(ir: IR): string | null {
|
|
1055
|
+
const stride = hourStride(ir);
|
|
1056
|
+
|
|
1057
|
+
if (!stride || offsetCleanStride(stride)) {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return hourStrideCadence(stride);
|
|
942
1062
|
}
|
|
943
1063
|
|
|
944
1064
|
// The second's status against a pinned minute: a wildcard or sub-minute step
|
|
@@ -994,7 +1114,13 @@ function hourCadence(ir: IR, minute: number): string | null {
|
|
|
994
1114
|
|
|
995
1115
|
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
996
1116
|
|
|
997
|
-
|
|
1117
|
+
// A short stride that spells out as few clock times stays an enumeration only
|
|
1118
|
+
// when it wraps cleanly (an offset-clean stride with no endpoint): the bare
|
|
1119
|
+
// or "ab" form is no shorter than the list. A bounded or uneven stride has no
|
|
1120
|
+
// clean wrap, so its endpoint-pinning cadence ("alle 5 Stunden von 0 bis 20
|
|
1121
|
+
// Uhr") reads better however short.
|
|
1122
|
+
if (ir.pattern.second === '0' && fires <= maxClockTimes &&
|
|
1123
|
+
offsetCleanStride(stride)) {
|
|
998
1124
|
return null;
|
|
999
1125
|
}
|
|
1000
1126
|
|
|
@@ -1012,15 +1138,68 @@ function hourCadence(ir: IR, minute: number): string | null {
|
|
|
1012
1138
|
everyNthHour(segment);
|
|
1013
1139
|
}
|
|
1014
1140
|
|
|
1141
|
+
// A plain top-of-the-hour fire (minute 0 with no meaningful second) has no
|
|
1142
|
+
// lead clause to fold in, so the bounded cadence stands on its own ("alle 5
|
|
1143
|
+
// Stunden von 0 bis 20 Uhr").
|
|
1144
|
+
if (minute === 0 && ir.pattern.second === '0') {
|
|
1145
|
+
return hourStrideCadence(stride);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1015
1148
|
return hourCadenceLead(ir, minute) + ', ' + hourStrideCadence(stride);
|
|
1016
1149
|
}
|
|
1017
1150
|
|
|
1018
|
-
// Whether an hour cadence applies to a plan with a single
|
|
1019
|
-
// signal that the clause is a cadence, not a daily
|
|
1020
|
-
// "täglich" frame must not be added.
|
|
1151
|
+
// Whether an hour cadence or hour-range window applies to a plan with a single
|
|
1152
|
+
// pinned minute — the signal that the clause is a cadence/window, not a daily
|
|
1153
|
+
// clock-time list, so the "täglich" frame must not be added.
|
|
1021
1154
|
function hourCadenceApplies(ir: IR): boolean {
|
|
1022
|
-
|
|
1023
|
-
|
|
1155
|
+
if (ir.shapes.minute !== 'single') {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const minute = +ir.pattern.minute;
|
|
1160
|
+
|
|
1161
|
+
return hourCadence(ir, minute) !== null ||
|
|
1162
|
+
hourRangeCadence(ir, minute) !== null;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Whether the hour field is a range — or a list whose segments include a
|
|
1166
|
+
// range — and so forms a window rather than a cross-product of clock times.
|
|
1167
|
+
// A pure single-value list (9,17) has no range to span and still enumerates;
|
|
1168
|
+
// a step is handled by hourStride/hourCadence.
|
|
1169
|
+
function hasHourWindow(ir: IR): boolean {
|
|
1170
|
+
const segments = fieldSegments(ir, 'hour');
|
|
1171
|
+
|
|
1172
|
+
return !!segments && segments.some(function range(segment) {
|
|
1173
|
+
return segment.kind === 'range';
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Render an hour range (or a list whose segments include a range) under
|
|
1178
|
+
// minute 0 and a meaningful second as the hour-range window — the lead clause,
|
|
1179
|
+
// then "von 9 bis 17 Uhr" (and any non-contiguous hour as "und um 22 Uhr") —
|
|
1180
|
+
// instead of cross-multiplying the hours into a wall of clock times. The
|
|
1181
|
+
// hour-RANGE analog of hourCadence; returns the bare clause (the day frame is
|
|
1182
|
+
// suppressed by hourCadenceApplies). Returns null when the hour has no range,
|
|
1183
|
+
// when the minute is non-zero (a real clock minute the existing window form
|
|
1184
|
+
// already speaks), or when a plain :00 set carries no clause. Renderer-only;
|
|
1185
|
+
// the IR is unchanged.
|
|
1186
|
+
function hourRangeCadence(ir: IR, minute: number): string | null {
|
|
1187
|
+
if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === '0') {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return hourCadenceLead(ir, minute) + ', ' + hourRangeWindowTail(ir);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// The hour-range window as a cadence tail at the top of each hour: each range
|
|
1195
|
+
// segment is "von X bis Y Uhr", any non-contiguous hour is "um Z Uhr", joined
|
|
1196
|
+
// — the same parts the bare "stündlich von 9 bis 17 Uhr" window forms, minus
|
|
1197
|
+
// the "stündlich" prefix the lead replaces. The minute has folded into the
|
|
1198
|
+
// lead, so the parts close on the top of their final hour.
|
|
1199
|
+
function hourRangeWindowTail(ir: IR): string {
|
|
1200
|
+
// Minute 0 with a falsy second renders each part as a bare hour ("von 9 bis
|
|
1201
|
+
// 17 Uhr", "um 22 Uhr"); the separator is unused in that path.
|
|
1202
|
+
return joinList(hourSegmentParts(ir, 0, 0, ':'));
|
|
1024
1203
|
}
|
|
1025
1204
|
|
|
1026
1205
|
// An hourly window: "stündlich von 9 bis 17 Uhr", or every minute across it.
|
|
@@ -1043,9 +1222,15 @@ function renderHourRange(
|
|
|
1043
1222
|
return 'stündlich ' + window;
|
|
1044
1223
|
}
|
|
1045
1224
|
|
|
1046
|
-
// A non-zero single minute ('lead') or a minute range leads the window.
|
|
1047
|
-
|
|
1048
|
-
|
|
1225
|
+
// A non-zero single minute ('lead') or a minute range leads the window. A
|
|
1226
|
+
// non-uniform minute step the core enumerated to a fire list reads as its
|
|
1227
|
+
// bounded cadence ("alle 2 Minuten von Minute 3 bis 59 jeder Stunde") instead
|
|
1228
|
+
// of the wall of fires; an irregular list or a single minute keeps the
|
|
1229
|
+
// counted form.
|
|
1230
|
+
return (strideFromSegments(fieldSegments(ir, 'minute'), UNITS.minute,
|
|
1231
|
+
'jeder Stunde') ??
|
|
1232
|
+
countedPhrase(ir, 'minute', 'Minute', 'Minuten') + ' jeder Stunde') +
|
|
1233
|
+
', ' + window;
|
|
1049
1234
|
}
|
|
1050
1235
|
|
|
1051
1236
|
// One or more clock times: "um 9 Uhr", "um 14:30 Uhr", "um 9 und 17 Uhr".
|
|
@@ -1054,10 +1239,12 @@ function renderClockTimes(
|
|
|
1054
1239
|
plan: Extract<PlanNode, {kind: 'clockTimes'}>,
|
|
1055
1240
|
opts: Opts
|
|
1056
1241
|
): string {
|
|
1057
|
-
// An hour step (or arithmetic-progression hour list) under a single
|
|
1058
|
-
// minute reads as a cadence rather than a cross-product of
|
|
1242
|
+
// An hour step or range (or arithmetic-progression hour list) under a single
|
|
1243
|
+
// pinned minute reads as a cadence or window rather than a cross-product of
|
|
1244
|
+
// clock times.
|
|
1059
1245
|
if (ir.shapes.minute === 'single') {
|
|
1060
|
-
const
|
|
1246
|
+
const minute = +ir.pattern.minute;
|
|
1247
|
+
const cadence = hourCadence(ir, minute) ?? hourRangeCadence(ir, minute);
|
|
1061
1248
|
|
|
1062
1249
|
if (cadence !== null) {
|
|
1063
1250
|
return cadence;
|