cronli5 0.1.5 → 0.1.7
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 +71 -0
- package/README.md +2 -2
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +156 -27
- package/dist/cronli5.js +156 -27
- package/dist/lang/de.cjs +157 -36
- package/dist/lang/de.js +157 -36
- package/dist/lang/en.cjs +156 -27
- package/dist/lang/en.js +156 -27
- package/dist/lang/es.cjs +156 -18
- package/dist/lang/es.js +156 -18
- package/dist/lang/fi.cjs +128 -20
- package/dist/lang/fi.js +128 -20
- package/dist/lang/zh.cjs +126 -58
- package/dist/lang/zh.js +126 -58
- package/package.json +2 -2
- package/src/core/util.ts +52 -1
- package/src/lang/de/index.ts +331 -74
- package/src/lang/en/index.ts +327 -62
- package/src/lang/es/index.ts +306 -39
- package/src/lang/fi/index.ts +251 -41
- package/src/lang/zh/index.ts +246 -105
- package/types/core/util.d.ts +10 -1
package/src/lang/fi/index.ts
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
import {clockDigits, numeral} from '../../core/format.js';
|
|
13
13
|
import {maxClockTimes, weekdayNumbers} from '../../core/specs.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
arithmeticStep, orderWeekdaysForDisplay, toFieldNumber
|
|
16
|
+
} from '../../core/util.js';
|
|
15
17
|
import {resolveDialect} from './dialects.js';
|
|
16
18
|
import type {
|
|
17
19
|
ClockTime, HourTimesPlan, IR, Language, NormalizedOptions, PlanNode,
|
|
@@ -353,9 +355,13 @@ function composeHourCadence(
|
|
|
353
355
|
const clockRest = plan.rest.kind === 'clockTimes' ||
|
|
354
356
|
plan.rest.kind === 'compactClockTimes';
|
|
355
357
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
if (!clockRest || ir.shapes.minute !== 'single') {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const minute = +ir.pattern.minute;
|
|
363
|
+
|
|
364
|
+
return hourCadence(ir, minute, opts) ?? hourRangeCadence(ir, minute, opts);
|
|
359
365
|
}
|
|
360
366
|
|
|
361
367
|
function renderComposeSeconds(
|
|
@@ -400,7 +406,15 @@ function renderComposeSeconds(
|
|
|
400
406
|
return secondsLeadClause(ir, opts) + ' joka toisena minuuttina';
|
|
401
407
|
}
|
|
402
408
|
|
|
403
|
-
|
|
409
|
+
// A compact clock-time rest folds a meaningful SINGLE second into its own
|
|
410
|
+
// leading clause, so the composer must not prepend a second lead that would
|
|
411
|
+
// double it. A wildcard or stepped second is not folded there (no
|
|
412
|
+
// clockSecond), so it still leads its own clause here.
|
|
413
|
+
const restOwnsLead = plan.rest.kind === 'compactClockTimes' &&
|
|
414
|
+
ir.analyses.clockSecond;
|
|
415
|
+
const lead = restOwnsLead ? '' : secondsLeadClause(ir, opts) + ', ';
|
|
416
|
+
|
|
417
|
+
return lead + render(ir, plan.rest, opts);
|
|
404
418
|
}
|
|
405
419
|
|
|
406
420
|
// A wildcard second over an unoffset minute */2 with a wildcard hour: the two
|
|
@@ -627,6 +641,16 @@ function renderMinuteFrequency(
|
|
|
627
641
|
const seg = stepSegment(ir.analyses.segments.minute!);
|
|
628
642
|
|
|
629
643
|
if (plan.hours.kind === 'during') {
|
|
644
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence
|
|
645
|
+
// after the minute step ("15 minuutin välein, viiden tunnin välein klo
|
|
646
|
+
// 0–20").
|
|
647
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
648
|
+
|
|
649
|
+
if (cadence !== null) {
|
|
650
|
+
return stepCycle60(seg, units.minute, opts) + ', ' + cadence +
|
|
651
|
+
trailingQualifier(ir, opts);
|
|
652
|
+
}
|
|
653
|
+
|
|
630
654
|
// When the step renders as anchored ("kohdalla"), the per-hour windows
|
|
631
655
|
// are redundant — use bare clock hours instead, then reorder to
|
|
632
656
|
// hours-first: "klo <hours> aina minuuttien <spec> kohdalla".
|
|
@@ -687,9 +711,20 @@ function renderMinutesAcrossHours(
|
|
|
687
711
|
plan: Extract<PlanNode, {kind: 'minutesAcrossHours'}>,
|
|
688
712
|
opts: NormalizedOptions
|
|
689
713
|
): string {
|
|
714
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence after
|
|
715
|
+
// the minute clause ("joka minuutti, viiden tunnin välein klo 0–20"), not a
|
|
716
|
+
// wall of hour windows.
|
|
717
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
718
|
+
|
|
690
719
|
if (plan.form === 'wildcard') {
|
|
691
|
-
return
|
|
692
|
-
trailingQualifier(ir, opts)
|
|
720
|
+
return cadence ?
|
|
721
|
+
'joka minuutti, ' + cadence + trailingQualifier(ir, opts) :
|
|
722
|
+
'joka minuutti ' + hourWindowsFromTimes(ir, plan.times, opts) +
|
|
723
|
+
trailingQualifier(ir, opts);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (cadence !== null) {
|
|
727
|
+
return bareMinutes(ir, opts) + ', ' + cadence + trailingQualifier(ir, opts);
|
|
693
728
|
}
|
|
694
729
|
|
|
695
730
|
// Range+isolated hours: minute-first, bare minutes, sekä klo.
|
|
@@ -714,25 +749,25 @@ function renderMinuteSpanAcrossHourStep(
|
|
|
714
749
|
): string {
|
|
715
750
|
// An hour-step plan's first hour segment is always a step segment.
|
|
716
751
|
const segment = stepSegment(ir.analyses.segments.hour!);
|
|
752
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence; an
|
|
753
|
+
// offset-clean stride keeps its confinement / per-step phrasing.
|
|
754
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
717
755
|
|
|
718
756
|
// A wildcard span always sets the step off with a comma ("joka
|
|
719
757
|
// minuutti, joka toinen tunti"); a restricted span joins a plain step
|
|
720
758
|
// directly ("minuuteilla 0–30 joka toinen tunti").
|
|
721
|
-
// A wildcard minute (a cadence) is reached only for a clean step
|
|
722
|
-
//
|
|
723
|
-
//
|
|
759
|
+
// A wildcard minute (a cadence) is reached only for a clean step (a bounded
|
|
760
|
+
// or uneven step routes through minutesAcrossHours instead) and is confined
|
|
761
|
+
// to every Nth hour; a restricted span is a per-hour window + plain step.
|
|
724
762
|
if (plan.form === 'wildcard') {
|
|
725
763
|
return 'joka minuutti ' + everyNthHour(segment, opts) +
|
|
726
764
|
trailingQualifier(ir, opts);
|
|
727
765
|
}
|
|
728
766
|
|
|
729
|
-
// A bounded
|
|
730
|
-
//
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const hoursStr = kloList(segment.fires, opts);
|
|
734
|
-
|
|
735
|
-
return hoursFirstMinutes(hoursStr, ir, opts) + trailingQualifier(ir, opts);
|
|
767
|
+
// A bounded or uneven stride reads as its bounded cadence after the bare
|
|
768
|
+
// minutes ("minuuteilla 0–30, kahden tunnin välein klo 9–17").
|
|
769
|
+
if (cadence !== null) {
|
|
770
|
+
return bareMinutes(ir, opts) + ', ' + cadence + trailingQualifier(ir, opts);
|
|
736
771
|
}
|
|
737
772
|
|
|
738
773
|
return bareMinutes(ir, opts) + hourStepTail(segment, opts) +
|
|
@@ -840,6 +875,15 @@ function renderHourStep(
|
|
|
840
875
|
plan: Extract<PlanNode, {kind: 'hourStep'}>,
|
|
841
876
|
opts: NormalizedOptions
|
|
842
877
|
): string {
|
|
878
|
+
// A bounded or uneven hour step reads as its endpoint-pinning cadence
|
|
879
|
+
// ("kahden tunnin välein klo 9–17"); an offset-clean step keeps its bare or
|
|
880
|
+
// "alkaen" cadence.
|
|
881
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
882
|
+
|
|
883
|
+
if (cadence !== null) {
|
|
884
|
+
return cadence + trailingQualifier(ir, opts);
|
|
885
|
+
}
|
|
886
|
+
|
|
843
887
|
return stepHours(stepSegment(ir.analyses.segments.hour!), opts) +
|
|
844
888
|
trailingQualifier(ir, opts);
|
|
845
889
|
}
|
|
@@ -867,10 +911,13 @@ function renderClockTimes(
|
|
|
867
911
|
plan: Extract<PlanNode, {kind: 'clockTimes'}>,
|
|
868
912
|
opts: NormalizedOptions
|
|
869
913
|
): string {
|
|
870
|
-
// An hour step (or arithmetic-progression hour list) under a single
|
|
871
|
-
// minute reads as a cadence rather than a cross-product of
|
|
914
|
+
// An hour step or range (or arithmetic-progression hour list) under a single
|
|
915
|
+
// pinned minute reads as a cadence or window rather than a cross-product of
|
|
916
|
+
// clock times.
|
|
872
917
|
if (ir.shapes.minute === 'single') {
|
|
873
|
-
const
|
|
918
|
+
const minute = +ir.pattern.minute;
|
|
919
|
+
const cadence = hourCadence(ir, minute, opts) ??
|
|
920
|
+
hourRangeCadence(ir, minute, opts);
|
|
874
921
|
|
|
875
922
|
if (cadence !== null) {
|
|
876
923
|
return cadence;
|
|
@@ -904,7 +951,8 @@ function renderCompactClockTimes(
|
|
|
904
951
|
// minute reads as a cadence, not a wall of clock times. (Returns null for an
|
|
905
952
|
// irregular list or a range, which keep folding below.)
|
|
906
953
|
if (plan.fold) {
|
|
907
|
-
const cadence = hourCadence(ir, plan.minute, opts)
|
|
954
|
+
const cadence = hourCadence(ir, plan.minute, opts) ??
|
|
955
|
+
hourRangeCadence(ir, plan.minute, opts);
|
|
908
956
|
|
|
909
957
|
if (cadence !== null) {
|
|
910
958
|
return cadence;
|
|
@@ -938,11 +986,16 @@ function renderCompactClockTimes(
|
|
|
938
986
|
hourSegmentTimes(ir, plan.minute, ir.analyses.clockSecond, opts);
|
|
939
987
|
}
|
|
940
988
|
|
|
941
|
-
// A
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
const
|
|
945
|
-
|
|
989
|
+
// A bounded or uneven hour stride reads as its endpoint-pinning cadence after
|
|
990
|
+
// the bare minute clause ("minuuteilla 0, 25 ja 50, viiden tunnin välein klo
|
|
991
|
+
// 0–20"), not a wall of clock-time columns.
|
|
992
|
+
const cadence = unevenHourCadence(ir, opts);
|
|
993
|
+
const phrase = cadence ?
|
|
994
|
+
bareMinutes(ir, opts) + ', ' + cadence + trailingQualifier(ir, opts) :
|
|
995
|
+
// A minute list over purely enumerated hours (step fires, all singles) —
|
|
996
|
+
// hours-first, drop "joka tunti".
|
|
997
|
+
hoursFirstMinutes(hourSegmentTimes(ir, 0, null, opts), ir, opts) +
|
|
998
|
+
trailingQualifier(ir, opts);
|
|
946
999
|
|
|
947
1000
|
return ir.analyses.clockSecond ?
|
|
948
1001
|
secondsLeadClause(ir, opts) + ', ' + phrase :
|
|
@@ -1128,12 +1181,56 @@ function hourStrideCadence(
|
|
|
1128
1181
|
kloRange({hour: start, minute: 0}, {hour: last, minute: 0}, opts);
|
|
1129
1182
|
}
|
|
1130
1183
|
|
|
1184
|
+
// An hour list's arithmetic progression, or null when its values are not a step
|
|
1185
|
+
// the renderer should speak as a cadence. The core rewrites a uneven hour step
|
|
1186
|
+
// (whose interval does not tile 24, e.g. `*/5` → 0,5,10,15,20) to its literal
|
|
1187
|
+
// fire list, indistinguishable in the IR from a hand-written list; the renderer
|
|
1188
|
+
// recovers the cadence from the values. A progression starting at zero is a
|
|
1189
|
+
// `*/n` step however short (0,7,14,21 is `*/7`); a non-zero progression is only
|
|
1190
|
+
// a step when it is too long to be a deliberate clock-time list (9,17 is two
|
|
1191
|
+
// named times, not a cadence). Interval one is a plain range, never a step.
|
|
1192
|
+
function hourListStride(
|
|
1193
|
+
values: number[]
|
|
1194
|
+
): {start: number; interval: number; last: number} | null {
|
|
1195
|
+
if (values.length < 2) {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const interval = values[1] - values[0];
|
|
1200
|
+
|
|
1201
|
+
if (interval < 2) {
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
for (let i = 2; i < values.length; i += 1) {
|
|
1206
|
+
if (values[i] - values[i - 1] !== interval) {
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (values[0] !== 0 && values.length < 5) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return {interval, last: values[values.length - 1], start: values[0]};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Whether an hour stride wraps the day cleanly from within its first interval
|
|
1219
|
+
// (a `*/n` from the top, or a `m/n` offset with m < n that divides 24): such a
|
|
1220
|
+
// stride has no distinct endpoint and keeps its bare or "alkaen" cadence. Every
|
|
1221
|
+
// other stride — a uneven interval, or one starting at or past its interval (a
|
|
1222
|
+
// bounded `a-b/n`) — is a bounded set the cadence pins both endpoints of.
|
|
1223
|
+
function offsetCleanStride(
|
|
1224
|
+
stride: {start: number; interval: number}
|
|
1225
|
+
): boolean {
|
|
1226
|
+
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1131
1229
|
// The hour field's stride, or null when the hour is not a cadence: a step
|
|
1132
1230
|
// segment yields its {start, interval, last} directly; an all-single hour list
|
|
1133
|
-
// yields one only when its values form a
|
|
1134
|
-
//
|
|
1135
|
-
// the
|
|
1136
|
-
// clock-time cross-product.
|
|
1231
|
+
// yields one only when its values form a step progression (so an irregular list
|
|
1232
|
+
// like 9,17 keeps enumerating). The IR is unchanged — the renderer recognizes
|
|
1233
|
+
// the stride and speaks it as a cadence, not the clock-time cross-product.
|
|
1137
1234
|
function hourStride(
|
|
1138
1235
|
ir: IR
|
|
1139
1236
|
): {start: number; interval: number; last: number} | null {
|
|
@@ -1146,6 +1243,13 @@ function hourStride(
|
|
|
1146
1243
|
|
|
1147
1244
|
if (segments.length === 1 && segments[0].kind === 'step') {
|
|
1148
1245
|
const segment = segments[0];
|
|
1246
|
+
|
|
1247
|
+
// A bounded step that fires only once (e.g. `9-10/5` -> just 9) is a single
|
|
1248
|
+
// value, not a stride: it has no interval to speak and no endpoint to pin.
|
|
1249
|
+
if (segment.fires.length < 2) {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1149
1253
|
const start = segment.startToken === '*' ?
|
|
1150
1254
|
0 :
|
|
1151
1255
|
+segment.startToken.split('-')[0];
|
|
@@ -1155,9 +1259,25 @@ function hourStride(
|
|
|
1155
1259
|
}
|
|
1156
1260
|
|
|
1157
1261
|
const values = singleValues(segments);
|
|
1158
|
-
const step = values && arithmeticStep(values);
|
|
1159
1262
|
|
|
1160
|
-
return
|
|
1263
|
+
return values && hourListStride(values);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// The bounded cadence for an hour stride that pins both clock-time endpoints,
|
|
1267
|
+
// or null when the hour is not such a stride. The core rewrites a uneven step
|
|
1268
|
+
// to its fire list, so a minute window/list/step crossed with it lands in the
|
|
1269
|
+
// enumerating list paths; there the bounded hour reads better as its cadence
|
|
1270
|
+
// ("…, viiden tunnin välein klo 0–20") than as a wall of clock times. An
|
|
1271
|
+
// offset-clean stride keeps its existing confinement form, so only the
|
|
1272
|
+
// endpoint-bearing case routes here.
|
|
1273
|
+
function unevenHourCadence(ir: IR, opts: NormalizedOptions): string | null {
|
|
1274
|
+
const stride = hourStride(ir);
|
|
1275
|
+
|
|
1276
|
+
if (!stride || offsetCleanStride(stride)) {
|
|
1277
|
+
return null;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return hourStrideCadence(stride, opts);
|
|
1161
1281
|
}
|
|
1162
1282
|
|
|
1163
1283
|
// The second's status against a pinned minute: a wildcard or sub-minute step
|
|
@@ -1214,7 +1334,13 @@ function hourCadence(ir: IR, minute: number,
|
|
|
1214
1334
|
|
|
1215
1335
|
const fires = (stride.last - stride.start) / stride.interval + 1;
|
|
1216
1336
|
|
|
1217
|
-
|
|
1337
|
+
// A short stride that spells out as few clock times stays an enumeration only
|
|
1338
|
+
// when it wraps cleanly (an offset-clean stride with no endpoint): the bare
|
|
1339
|
+
// or "alkaen" form is no shorter than the list. A bounded or uneven stride
|
|
1340
|
+
// has no clean wrap, so its endpoint-pinning cadence ("viiden tunnin välein
|
|
1341
|
+
// klo 0–20") reads better however short.
|
|
1342
|
+
if (ir.pattern.second === '0' && fires <= maxClockTimes &&
|
|
1343
|
+
offsetCleanStride(stride)) {
|
|
1218
1344
|
return null;
|
|
1219
1345
|
}
|
|
1220
1346
|
|
|
@@ -1232,6 +1358,13 @@ function hourCadence(ir: IR, minute: number,
|
|
|
1232
1358
|
everyNthHour(segment, opts) + trailingQualifier(ir, opts);
|
|
1233
1359
|
}
|
|
1234
1360
|
|
|
1361
|
+
// A plain top-of-the-hour fire (minute 0 with no meaningful second) has no
|
|
1362
|
+
// lead clause to fold in, so the bounded cadence stands on its own ("viiden
|
|
1363
|
+
// tunnin välein klo 0–20").
|
|
1364
|
+
if (minute === 0 && ir.pattern.second === '0') {
|
|
1365
|
+
return hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1235
1368
|
return hourCadenceLead(ir, minute, opts) + ', ' +
|
|
1236
1369
|
hourStrideCadence(stride, opts) + trailingQualifier(ir, opts);
|
|
1237
1370
|
}
|
|
@@ -1249,6 +1382,55 @@ function cleanHourStride(segment: StepSegment): boolean {
|
|
|
1249
1382
|
return 24 % segment.interval === 0 && start < segment.interval;
|
|
1250
1383
|
}
|
|
1251
1384
|
|
|
1385
|
+
// Whether the hour field is a range — or a list whose segments include a
|
|
1386
|
+
// range — and so forms a window rather than a cross-product of clock times.
|
|
1387
|
+
// A pure single-value list (9,17) has no range to span and still enumerates;
|
|
1388
|
+
// a step is handled by hourStride/hourCadence.
|
|
1389
|
+
function hasHourWindow(ir: IR): boolean {
|
|
1390
|
+
const segments = ir.analyses.segments.hour;
|
|
1391
|
+
|
|
1392
|
+
return !!segments && segments.some(function range(segment: Segment) {
|
|
1393
|
+
return segment.kind === 'range';
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// The hour-range window as a cadence tail at the top of each hour: a lone
|
|
1398
|
+
// range is the bare "klo 9–17"; a range plus a non-contiguous hour joins it
|
|
1399
|
+
// with "sekä klo" ("klo 9–20 sekä klo 22"), the same idiom the bare folded
|
|
1400
|
+
// window uses. The minute has folded into the lead, so the window closes on
|
|
1401
|
+
// the top of its final hour.
|
|
1402
|
+
function hourRangeWindowTail(ir: IR, opts: NormalizedOptions): string {
|
|
1403
|
+
return ir.analyses.segments.hour!.length === 1 ?
|
|
1404
|
+
hourSegmentTimes(ir, 0, null, opts) :
|
|
1405
|
+
hourSegmentTimesWithSeka(ir, 0, null, opts);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Render an hour range (or a list whose segments include a range) under
|
|
1409
|
+
// minute 0 and a meaningful second as the hour-range window — the lead clause,
|
|
1410
|
+
// then "klo 9–17" — instead of cross-multiplying the hours into a wall of
|
|
1411
|
+
// clock times. The hour-RANGE analog of hourCadence. Returns null when the
|
|
1412
|
+
// hour has no range, when the minute is non-zero (a real clock minute the
|
|
1413
|
+
// existing window form already speaks), or when a plain :00 set carries no
|
|
1414
|
+
// clause. Renderer-only; the IR is unchanged.
|
|
1415
|
+
function hourRangeCadence(ir: IR, minute: number,
|
|
1416
|
+
opts: NormalizedOptions): string | null {
|
|
1417
|
+
if (minute !== 0 || !hasHourWindow(ir) || ir.pattern.second === '0') {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const tail = hourRangeWindowTail(ir, opts);
|
|
1422
|
+
|
|
1423
|
+
// A wildcard or sub-minute step second is the whole minute-0 window
|
|
1424
|
+
// ("minuutin ajan", carried by hourCadenceLead), then the window — kept
|
|
1425
|
+
// distinct from the bare "joka tunti klo 9–17" so the confinement is never
|
|
1426
|
+
// heard as it (the hour-range analog of "minuutin ajan joka toisen tunnin
|
|
1427
|
+
// aikana"). A meaningful second leads at its mark, then the window.
|
|
1428
|
+
const joiner = subMinuteSecond(ir) ? ' ' : ', ';
|
|
1429
|
+
|
|
1430
|
+
return hourCadenceLead(ir, minute, opts) + joiner + tail +
|
|
1431
|
+
trailingQualifier(ir, opts);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1252
1434
|
// --- Hour-time phrasing. ---
|
|
1253
1435
|
|
|
1254
1436
|
// On-the-hour fires as one klo phrase: "klo 0, 10 ja 20".
|
|
@@ -1276,23 +1458,32 @@ function kloFromTimes(
|
|
|
1276
1458
|
return hourSegmentTimes(ir, 0, null, opts);
|
|
1277
1459
|
}
|
|
1278
1460
|
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1461
|
+
// The hours accompanying a named-once minute clause under an hour list or
|
|
1462
|
+
// step. On-the-hour hours (a fires set, or a segment set with no real range)
|
|
1463
|
+
// are listed once — "klo 0, 5, 10, 15 ja 20" — so the minute is never repeated
|
|
1464
|
+
// as a per-hour span. A real hour RANGE segment is a genuine span and keeps its
|
|
1465
|
+
// per-segment window ("klo 8.00–18.59 ja 22.00–22.59"), mirroring the other
|
|
1466
|
+
// languages, which list discrete hours but keep range windows.
|
|
1282
1467
|
function hourWindowsFromTimes(
|
|
1283
1468
|
ir: IR,
|
|
1284
1469
|
times: HourTimesPlan,
|
|
1285
1470
|
opts: NormalizedOptions
|
|
1286
1471
|
): string {
|
|
1287
1472
|
if (times.kind === 'fires') {
|
|
1288
|
-
return
|
|
1289
|
-
|
|
1290
|
-
|
|
1473
|
+
return kloList(times.fires, opts);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const segments = ir.analyses.segments.hour!;
|
|
1477
|
+
|
|
1478
|
+
if (!segments.some(function ranged(segment: Segment) {
|
|
1479
|
+
return segment.kind === 'range';
|
|
1480
|
+
})) {
|
|
1481
|
+
return kloList(hourSegmentFires(segments), opts);
|
|
1291
1482
|
}
|
|
1292
1483
|
|
|
1293
1484
|
const pieces: string[] = [];
|
|
1294
1485
|
|
|
1295
|
-
|
|
1486
|
+
segments.forEach(function window(segment: Segment) {
|
|
1296
1487
|
if (segment.kind === 'range') {
|
|
1297
1488
|
pieces.push(rangeDigits({hour: +segment.bounds[0], minute: 0},
|
|
1298
1489
|
{hour: +segment.bounds[1], minute: 59}, opts));
|
|
@@ -1310,6 +1501,23 @@ function hourWindowsFromTimes(
|
|
|
1310
1501
|
return 'klo ' + joinList(pieces);
|
|
1311
1502
|
}
|
|
1312
1503
|
|
|
1504
|
+
// The on-the-hour fires of a range-free hour segment set, in order: a step
|
|
1505
|
+
// segment contributes its enumerated fires, a single its one value.
|
|
1506
|
+
function hourSegmentFires(segments: Segment[]): number[] {
|
|
1507
|
+
const hours: number[] = [];
|
|
1508
|
+
|
|
1509
|
+
segments.forEach(function each(segment: Segment) {
|
|
1510
|
+
if (segment.kind === 'step') {
|
|
1511
|
+
hours.push(...segment.fires);
|
|
1512
|
+
}
|
|
1513
|
+
else if (segment.kind === 'single') {
|
|
1514
|
+
hours.push(+segment.value);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
return hours;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1313
1521
|
// "9.00–9.59": one hour as a dash window, in digits.
|
|
1314
1522
|
function hourWindowDigits(hour: number, opts: NormalizedOptions): string {
|
|
1315
1523
|
return rangeDigits({hour, minute: 0}, {hour, minute: 59}, opts);
|
|
@@ -1497,7 +1705,9 @@ function weekdayQualifier(ir: IR): string {
|
|
|
1497
1705
|
return quartz;
|
|
1498
1706
|
}
|
|
1499
1707
|
|
|
1500
|
-
|
|
1708
|
+
// Weekday lists display Monday-first (Sunday last); a lone range keeps its
|
|
1709
|
+
// form. The IR stays canonical (Sunday=0). The helper flattens steps.
|
|
1710
|
+
const segments = orderWeekdaysForDisplay(ir.analyses.segments.weekday!);
|
|
1501
1711
|
|
|
1502
1712
|
return joinList(segments.map(function piece(segment: FlatSegment) {
|
|
1503
1713
|
if (segment.kind === 'range') {
|