cronli5 0.1.6 → 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 +43 -0
- package/README.md +2 -2
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +39 -7
- package/dist/cronli5.js +39 -7
- package/dist/lang/de.cjs +63 -17
- package/dist/lang/de.js +63 -17
- package/dist/lang/en.cjs +39 -7
- package/dist/lang/en.js +39 -7
- package/dist/lang/es.cjs +68 -5
- package/dist/lang/es.js +68 -5
- package/dist/lang/fi.cjs +21 -1
- package/dist/lang/fi.js +21 -1
- package/dist/lang/zh.cjs +38 -7
- package/dist/lang/zh.js +38 -7
- package/package.json +1 -1
- package/src/core/util.ts +52 -1
- package/src/lang/de/index.ts +95 -25
- package/src/lang/en/index.ts +47 -16
- package/src/lang/es/index.ts +85 -9
- package/src/lang/fi/index.ts +6 -2
- package/src/lang/zh/index.ts +44 -18
- package/types/core/util.d.ts +10 -1
package/src/lang/es/index.ts
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import {clockDigits, numeral} from '../../core/format.js';
|
|
12
12
|
import {maxClockTimes, weekdayNumbers} from '../../core/specs.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
arithmeticStep, orderWeekdaysForDisplay, toFieldNumber
|
|
15
|
+
} from '../../core/util.js';
|
|
14
16
|
import type {Cronli5Options} from '../../types.js';
|
|
15
17
|
import type {
|
|
16
18
|
Field, HourTimesPlan, IR, Language, NormalizedOptions, PlanNode,
|
|
@@ -313,7 +315,7 @@ function renderComposeSeconds(
|
|
|
313
315
|
if (plan.rest.kind === 'hourRange' && ir.shapes.second === 'step' &&
|
|
314
316
|
ir.pattern.weekday !== '*') {
|
|
315
317
|
const restNode = plan.rest;
|
|
316
|
-
const window = hourWindow(restNode, opts);
|
|
318
|
+
const window = hourWindow(boundedWindow(restNode), opts);
|
|
317
319
|
const dayFrame = weekdayQualifier(ir) + monthScope(ir);
|
|
318
320
|
const cadence = 'cada ' +
|
|
319
321
|
numero(stepSegment(ir.analyses.segments.second).interval, opts) +
|
|
@@ -754,13 +756,18 @@ function renderHourStep(
|
|
|
754
756
|
trailingQualifier(ir, opts);
|
|
755
757
|
}
|
|
756
758
|
|
|
757
|
-
// The hour-range plan as a window
|
|
758
|
-
//
|
|
759
|
-
//
|
|
759
|
+
// The hour-range plan as a window. The close lands on the top of the final
|
|
760
|
+
// hour (minute 0) unless the minute genuinely runs to the end of that hour —
|
|
761
|
+
// i.e. a wildcard minute, which fills every minute and states no separate
|
|
762
|
+
// clause. A pinned/listed/ranged minute is named in its own lead clause, so
|
|
763
|
+
// folding it into the close too would read as a span ("a las 17:05") that
|
|
764
|
+
// contradicts the minute clause; the window stays bare ("a las 17:00").
|
|
760
765
|
function boundedWindow(
|
|
761
766
|
plan: Extract<PlanNode, {kind: 'hourRange'}>
|
|
762
767
|
): {from: number; to: number; last: number} {
|
|
763
|
-
|
|
768
|
+
const last = plan.minuteForm === 'wildcard' ? plan.boundMinute ?? 0 : 0;
|
|
769
|
+
|
|
770
|
+
return {from: plan.from, last, to: plan.to};
|
|
764
771
|
}
|
|
765
772
|
|
|
766
773
|
// "de las 9:00 a las 17:45": a window from the top of the first hour to
|
|
@@ -839,7 +846,9 @@ function dowArm(ir: IR): string {
|
|
|
839
846
|
return quartz;
|
|
840
847
|
}
|
|
841
848
|
|
|
842
|
-
|
|
849
|
+
// Weekday lists display Monday-first (Sunday last); a lone range keeps its
|
|
850
|
+
// form. The IR stays canonical (Sunday=0). The helper flattens steps.
|
|
851
|
+
const segments = orderWeekdaysForDisplay(fieldSegments(ir, 'weekday'));
|
|
843
852
|
const allSingles = segments.every(function single(segment) {
|
|
844
853
|
return segment.kind === 'single';
|
|
845
854
|
});
|
|
@@ -1258,7 +1267,7 @@ function renderCompactClockTimes(
|
|
|
1258
1267
|
const phrase = cadence ?
|
|
1259
1268
|
minutesList(ir, opts) + ', ' + cadence + trailingQualifier(ir, opts) :
|
|
1260
1269
|
minutesList(ir, opts) + ', ' +
|
|
1261
|
-
|
|
1270
|
+
hourContextTimes(ir, opts) + trailingQualifier(ir, opts);
|
|
1262
1271
|
|
|
1263
1272
|
return ir.analyses.clockSecond ?
|
|
1264
1273
|
secondsLeadClause(ir, opts) + ', ' + phrase :
|
|
@@ -1681,6 +1690,71 @@ function hourRangeCadence(ir: IR, minute: number, opts: Opts): string | null {
|
|
|
1681
1690
|
|
|
1682
1691
|
// --- Hour-time phrasing. ---
|
|
1683
1692
|
|
|
1693
|
+
// The fixed hour(s) of a stepped/listed minute, named as the HOUR rather than a
|
|
1694
|
+
// "a las HH:00" clock instant the minute never fires at: noon and midnight read
|
|
1695
|
+
// as the hour word ("al mediodía"/"a medianoche"), any other hour as the whole
|
|
1696
|
+
// hour "de la hora de las HH:00" (the idiom a wildcard minute already uses).
|
|
1697
|
+
// Used by the compact-clock non-fold path, where the minute is a step or list
|
|
1698
|
+
// (a single-value minute keeps its real "a las HH:MM" clock time elsewhere).
|
|
1699
|
+
function hourContextTimes(ir: IR, opts: Opts): string {
|
|
1700
|
+
const segments = hourSegments(ir);
|
|
1701
|
+
|
|
1702
|
+
// Collect the point hours (singles and step fires) — a range stays a window.
|
|
1703
|
+
const points: number[] = [];
|
|
1704
|
+
const hasRange = segments.some(function range(segment) {
|
|
1705
|
+
return segment.kind === 'range';
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
segments.forEach(function collect(segment) {
|
|
1709
|
+
if (segment.kind === 'step') {
|
|
1710
|
+
points.push(...segment.fires);
|
|
1711
|
+
}
|
|
1712
|
+
else if (segment.kind === 'single') {
|
|
1713
|
+
points.push(+segment.value);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
// All point hours, all noon/midnight: stand alone as their own words ("a
|
|
1718
|
+
// medianoche y al mediodía").
|
|
1719
|
+
function isWord(hour: number): boolean {
|
|
1720
|
+
return !opts.ampm && (hour === 0 || hour === 12);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (!hasRange && points.every(isWord)) {
|
|
1724
|
+
return joinList(points.map(function each(hour) {
|
|
1725
|
+
return atTime(bareHourPhrase(hour, opts));
|
|
1726
|
+
}));
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// A point hour as the whole hour: "de la hora de las HH:00".
|
|
1730
|
+
function wholeHour(hour: number): string {
|
|
1731
|
+
return 'de la hora ' + fromTime(explicitTimePhrase(hour, 0, opts));
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// Otherwise each whole hour reads as a window ("de las HH:00 a las HH:00" for
|
|
1735
|
+
// a range, "de la hora de las HH:00" for a point), never a false "a las
|
|
1736
|
+
// HH:00" clock instant the stepped minute never fires at.
|
|
1737
|
+
const pieces: string[] = [];
|
|
1738
|
+
|
|
1739
|
+
segments.forEach(function place(segment) {
|
|
1740
|
+
if (segment.kind === 'range') {
|
|
1741
|
+
pieces.push(timeRange(
|
|
1742
|
+
{hour: +segment.bounds[0], minute: 0},
|
|
1743
|
+
{hour: +segment.bounds[1], minute: 0}, opts));
|
|
1744
|
+
}
|
|
1745
|
+
else if (segment.kind === 'step') {
|
|
1746
|
+
segment.fires.forEach(function each(hour) {
|
|
1747
|
+
pieces.push(wholeHour(hour));
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
pieces.push(wholeHour(+segment.value));
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
return joinList(pieces);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1684
1758
|
// "a las 9:00" / "a la 1:00" / "al mediodía" for each fire hour.
|
|
1685
1759
|
function atTimes(hours: number[], opts: Opts): string[] {
|
|
1686
1760
|
return hours.map(function each(hour) {
|
|
@@ -2131,7 +2205,9 @@ function weekdayQualifier(ir: IR): string {
|
|
|
2131
2205
|
return quartz;
|
|
2132
2206
|
}
|
|
2133
2207
|
|
|
2134
|
-
|
|
2208
|
+
// Weekday lists display Monday-first (Sunday last); a lone range keeps its
|
|
2209
|
+
// form. The IR stays canonical (Sunday=0). The helper flattens steps.
|
|
2210
|
+
const segments = orderWeekdaysForDisplay(fieldSegments(ir, 'weekday'));
|
|
2135
2211
|
const allSingles = segments.every(function single(segment) {
|
|
2136
2212
|
return segment.kind === 'single';
|
|
2137
2213
|
});
|
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,
|
|
@@ -1703,7 +1705,9 @@ function weekdayQualifier(ir: IR): string {
|
|
|
1703
1705
|
return quartz;
|
|
1704
1706
|
}
|
|
1705
1707
|
|
|
1706
|
-
|
|
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!);
|
|
1707
1711
|
|
|
1708
1712
|
return joinList(segments.map(function piece(segment: FlatSegment) {
|
|
1709
1713
|
if (segment.kind === 'range') {
|
package/src/lang/zh/index.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
// big-endian dates, 每 for recurrence, 24-hour clock with 凌晨0点/正午 anchors,
|
|
5
5
|
// day periods under `ampm`. The style contract is src/lang/zh/notes.md.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
arithmeticStep, orderWeekdaysForDisplay, toFieldNumber
|
|
9
|
+
} from '../../core/util.js';
|
|
8
10
|
import {maxClockTimes, monthNumbers, weekdayNumbers} from '../../core/specs.js';
|
|
9
11
|
import type {Cronli5Options} from '../../types.js';
|
|
10
12
|
import type {
|
|
@@ -591,6 +593,30 @@ function hourCadencePhrase(ir: IR): string | null {
|
|
|
591
593
|
});
|
|
592
594
|
}
|
|
593
595
|
|
|
596
|
+
// A wildcard or sub-minute step second confined to minute 0 of an hour stride
|
|
597
|
+
// is a confinement, not a juxtaposed cadence. The even-hour stride (interval 2
|
|
598
|
+
// from midnight) reuses the even-hours idiom ("在偶数小时0分的每一秒") so the form
|
|
599
|
+
// does NOT contain the bare "每2小时" and can never be misread as the absorbing
|
|
600
|
+
// hour cadence (the same reason en says "for one minute during every other
|
|
601
|
+
// hour", not "every two hours"). An OFFSET stride names its start ("从1点起每2小时"),
|
|
602
|
+
// already unambiguous — it cannot be heard as the bare cadence — so it folds
|
|
603
|
+
// "0分" and the second onto that named cadence ("从1点起每2小时0分的每一秒"). A bare
|
|
604
|
+
// cadence from midnight (no start named, e.g. "每3小时") keeps enumerating its
|
|
605
|
+
// hours so it is never heard as the absorbing form.
|
|
606
|
+
function minuteZeroConfinement(
|
|
607
|
+
ir: IR, stride: {interval: number; start: number}, prefix: string
|
|
608
|
+
): string | null {
|
|
609
|
+
if (stride.interval === 2 && stride.start === 0) {
|
|
610
|
+
return '在偶数小时0分' + secondTail(ir);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (prefix.indexOf('从') !== -1) {
|
|
614
|
+
return prefix + '0分' + secondTail(ir);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
|
|
594
620
|
// Render an hour step (or arithmetic-progression hour list) under a single
|
|
595
621
|
// pinned minute and a second as a cadence — the hour cadence plus the
|
|
596
622
|
// minute/second — instead of cross-multiplying the hours into a wall of clock
|
|
@@ -626,17 +652,8 @@ function hourCadence(ir: IR): string | null {
|
|
|
626
652
|
const minute = +ir.pattern.minute;
|
|
627
653
|
const subMinute = ir.pattern.second === '*' || ir.shapes.second === 'step';
|
|
628
654
|
|
|
629
|
-
// A wildcard or sub-minute step second confined to minute 0 of the even-hour
|
|
630
|
-
// stride is a confinement, not a juxtaposed cadence. Reuse the even-hours
|
|
631
|
-
// idiom ("在偶数小时0分的每一秒") so the form does NOT contain the bare "每2小时"
|
|
632
|
-
// and can never be misread as the absorbing hour cadence (the same reason en
|
|
633
|
-
// says "for one minute during every other hour", not "every two hours"). The
|
|
634
|
-
// idiom exists only for the even-hour stride (interval 2 from midnight);
|
|
635
|
-
// another stride keeps enumerating (return null) rather than coin a
|
|
636
|
-
// misleading "…小时…" form.
|
|
637
655
|
if (minute === 0 && subMinute) {
|
|
638
|
-
return
|
|
639
|
-
'在偶数小时0分' + secondTail(ir) : null;
|
|
656
|
+
return minuteZeroConfinement(ir, stride, prefix);
|
|
640
657
|
}
|
|
641
658
|
|
|
642
659
|
// A pinned minute 0 folds into the cadence with the explicit "0分" so the
|
|
@@ -1050,7 +1067,7 @@ function quartzDate(token: string, monthPrefix: string): string {
|
|
|
1050
1067
|
return monthPrefix + '最后第' + token.slice(2) + '天';
|
|
1051
1068
|
}
|
|
1052
1069
|
|
|
1053
|
-
return '最接近' + token.slice(0, -1) + '日的工作日';
|
|
1070
|
+
return monthPrefix + '最接近' + token.slice(0, -1) + '日的工作日';
|
|
1054
1071
|
}
|
|
1055
1072
|
|
|
1056
1073
|
// The date side of a qualifier (month folded in): "每月1日", "1月1日",
|
|
@@ -1071,7 +1088,17 @@ function datePhrase(ir: IR): string {
|
|
|
1071
1088
|
return month + cadence(stepSegment(ir, 'date').interval, '天');
|
|
1072
1089
|
}
|
|
1073
1090
|
|
|
1074
|
-
|
|
1091
|
+
if (!month) {
|
|
1092
|
+
return '每月' + dayList(ir);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// A multi-month scope (range/list) ends in 月 and would run straight into the
|
|
1096
|
+
// day — "6月至8月1日" reads "8月1日" as August 1st. The comma keeps the month
|
|
1097
|
+
// scope distinct from the day ("6月至8月,1日"). A single month stays glued
|
|
1098
|
+
// ("6月1日"), which is unambiguous.
|
|
1099
|
+
const monthMulti = ir.shapes.month === 'range' || ir.shapes.month === 'list';
|
|
1100
|
+
|
|
1101
|
+
return month + (monthMulti ? ',' : '') + dayList(ir);
|
|
1075
1102
|
}
|
|
1076
1103
|
|
|
1077
1104
|
// The date side WITHOUT its month or 每月 lead — just the day part: "1日",
|
|
@@ -1140,13 +1167,12 @@ function weekdayPhrase(
|
|
|
1140
1167
|
return '每' + weekdayName(from) + '至' + weekdayName(to);
|
|
1141
1168
|
}
|
|
1142
1169
|
|
|
1170
|
+
// Weekday lists display Monday-first (Sunday last); the IR stays canonical
|
|
1171
|
+
// (Sunday=0). The helper flattens steps into singles and orders the list.
|
|
1143
1172
|
const days: number[] = [];
|
|
1144
1173
|
|
|
1145
|
-
segs.forEach(function expand(seg) {
|
|
1146
|
-
if (seg.kind === '
|
|
1147
|
-
days.push(...seg.fires);
|
|
1148
|
-
}
|
|
1149
|
-
else if (seg.kind === 'single') {
|
|
1174
|
+
orderWeekdaysForDisplay(segs).forEach(function expand(seg) {
|
|
1175
|
+
if (seg.kind === 'single') {
|
|
1150
1176
|
days.push(toFieldNumber(seg.value, weekdayNumbers));
|
|
1151
1177
|
}
|
|
1152
1178
|
});
|
package/types/core/util.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Segment } from './ir.js';
|
|
1
2
|
declare function includes(str: string | number, sub: string): boolean;
|
|
2
3
|
declare function unique<T>(items: T[]): T[];
|
|
3
4
|
declare function isNonNegativeInteger(value: string): boolean;
|
|
@@ -6,7 +7,15 @@ declare function arithmeticStep(values: number[]): {
|
|
|
6
7
|
interval: number;
|
|
7
8
|
last: number;
|
|
8
9
|
} | null;
|
|
10
|
+
type WeekdaySegment = {
|
|
11
|
+
kind: 'single';
|
|
12
|
+
value: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'range';
|
|
15
|
+
bounds: [string, string];
|
|
16
|
+
};
|
|
17
|
+
declare function orderWeekdaysForDisplay(segments: Segment[]): WeekdaySegment[];
|
|
9
18
|
declare function toFieldNumber(token: string, numberMap?: {
|
|
10
19
|
[name: string]: number;
|
|
11
20
|
}): number;
|
|
12
|
-
export { arithmeticStep, includes, isNonNegativeInteger, toFieldNumber, unique };
|
|
21
|
+
export { arithmeticStep, includes, isNonNegativeInteger, orderWeekdaysForDisplay, toFieldNumber, unique };
|