cronli5 0.3.1 → 0.3.4
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 +44 -0
- package/README.md +4 -4
- package/dist/lang/zh.cjs +16 -8
- package/dist/lang/zh.js +16 -8
- package/package.json +6 -6
- package/src/lang/zh/index.ts +46 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,50 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.4]
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **zh:** a bounded *parity* month step dropped its bound — `0 0 1 2-10/2`
|
|
14
|
+
(months 2,4,6,8,10) rendered "每个偶数月" (every even-numbered month), which
|
|
15
|
+
wrongly includes December. zh now enumerates a bounded parity step
|
|
16
|
+
("2、4、6、8、10月") like en/es/de/fi; the open `*/2`/`2/2` step keeps
|
|
17
|
+
"单数月/偶数月". The exact analogue of the 0.3.3 day-step fix; found by auditing
|
|
18
|
+
the bug class that fix implied (the bounded case was thin in the corpora).
|
|
19
|
+
|
|
20
|
+
## [0.3.3]
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **zh:** a bounded day-of-month step dropped its bounds — `0 0 9-17/2` rendered
|
|
25
|
+
"每2天" (every 2 days) instead of the days 9, 11, 13, 15, 17. It now enumerates
|
|
26
|
+
the days like en/es/de/fi ("每月9、11、13、15、17日"); the open `*/N` step keeps
|
|
27
|
+
"每N天". Surfaced by the new coverage tests (the zh corpus had only open-step
|
|
28
|
+
rows, so the bounded branch was untested).
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Test coverage closed to its true floor after the Vitest migration: 74 new
|
|
33
|
+
verified corpus rows (core and the English renderer now ~100%), thresholds
|
|
34
|
+
raised to lines 98.5 / branches 97 / functions 99.2 / statements 98.5. The
|
|
35
|
+
remaining uncovered branches are genuinely-unreachable defensive guards.
|
|
36
|
+
|
|
37
|
+
## [0.3.2]
|
|
38
|
+
|
|
39
|
+
Tooling and docs only — the published library is functionally unchanged.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **Test runner migrated to Vitest** (from mocha + c8-over-tsx). V8 coverage is
|
|
44
|
+
source-accurate, so the esbuild/tsx phantom-branch artifact is gone and
|
|
45
|
+
thresholds are gated at the true numbers. (The old c8 figures were wrong in
|
|
46
|
+
both directions — inflated as well as deflated; real coverage is ~98 lines /
|
|
47
|
+
96 branches / 99 functions, and the genuine untested-source gaps it revealed
|
|
48
|
+
are now tracked in the backlog.)
|
|
49
|
+
- Pruned the shipped rendering items (0.2.0–0.3.1) from the backlog, and ran a
|
|
50
|
+
comment/reference cleanup over the scripts and tooling (stale `IR`→`Schedule`,
|
|
51
|
+
util-split, and mocha/c8 references; process-label scrub; dead code).
|
|
52
|
+
|
|
9
53
|
## [0.3.1]
|
|
10
54
|
|
|
11
55
|
### Changed
|
package/README.md
CHANGED
|
@@ -330,13 +330,13 @@ required.
|
|
|
330
330
|
|
|
331
331
|
## Development
|
|
332
332
|
|
|
333
|
-
The library has no runtime dependencies. The toolchain (ESLint,
|
|
334
|
-
|
|
333
|
+
The library has no runtime dependencies. The toolchain (ESLint, Vitest, Chai,
|
|
334
|
+
esbuild) lives in `devDependencies`.
|
|
335
335
|
|
|
336
336
|
```bash
|
|
337
337
|
npm install # install dev dependencies (also wires the git hooks)
|
|
338
|
-
npm test # run the
|
|
339
|
-
npm run coverage # run tests with
|
|
338
|
+
npm test # run the Vitest suite (runs against src/, no build needed)
|
|
339
|
+
npm run coverage # run tests with Vitest V8 coverage and enforce thresholds
|
|
340
340
|
npm run lint # lint source and tests with ESLint
|
|
341
341
|
npm run build # emit dist/ (ESM + CJS) and the minified browser global
|
|
342
342
|
npm run verify # the full CI gate: lint, types, tests, coverage, docs, build
|
package/dist/lang/zh.cjs
CHANGED
|
@@ -674,13 +674,20 @@ function render(schedule, plan, opts) {
|
|
|
674
674
|
opts
|
|
675
675
|
);
|
|
676
676
|
}
|
|
677
|
+
function boundedParityMonth(schedule) {
|
|
678
|
+
if (schedule.shapes.month !== "step" || isOpenStep(schedule.pattern.month)) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
const segs = segmentsOf(schedule, "month");
|
|
682
|
+
return segs.length === 1 && segs[0].kind === "step" && segs[0].interval === 2;
|
|
683
|
+
}
|
|
677
684
|
function monthPhrase(schedule) {
|
|
678
685
|
if (schedule.pattern.month === "*") {
|
|
679
686
|
return "";
|
|
680
687
|
}
|
|
681
688
|
const segs = segmentsOf(schedule, "month");
|
|
682
689
|
const first = segs[0];
|
|
683
|
-
if (segs.length === 1 && first.kind === "step" && first.interval === 2) {
|
|
690
|
+
if (segs.length === 1 && first.kind === "step" && first.interval === 2 && isOpenStep(schedule.pattern.month)) {
|
|
684
691
|
return "\u6BCF\u4E2A" + (first.fires[0] % 2 ? "\u5947" : "\u5076") + "\u6570\u6708";
|
|
685
692
|
}
|
|
686
693
|
if (segs.length === 1 && first.kind === "range") {
|
|
@@ -698,14 +705,14 @@ function monthPhrase(schedule) {
|
|
|
698
705
|
}
|
|
699
706
|
function dayList(schedule) {
|
|
700
707
|
const segs = segmentsOf(schedule, "date");
|
|
701
|
-
if (segs.every((seg) => seg.kind === "single")) {
|
|
702
|
-
return segs
|
|
708
|
+
if (segs.every((seg) => seg.kind === "single" || seg.kind === "step")) {
|
|
709
|
+
return fireValues(segs).join("\u3001") + "\u65E5";
|
|
703
710
|
}
|
|
704
711
|
return segs.map(function day(seg) {
|
|
705
712
|
if (seg.kind === "range") {
|
|
706
713
|
return seg.bounds[0] + "\u65E5\u81F3" + seg.bounds[1] + "\u65E5";
|
|
707
714
|
}
|
|
708
|
-
return seg.value + "\u65E5";
|
|
715
|
+
return seg.kind === "step" ? seg.fires.join("\u3001") + "\u65E5" : seg.value + "\u65E5";
|
|
709
716
|
}).join("\u3001");
|
|
710
717
|
}
|
|
711
718
|
function quartzDate(token, monthPrefix) {
|
|
@@ -743,20 +750,20 @@ function datePhrase(schedule) {
|
|
|
743
750
|
if (schedule.shapes.date === "quartz") {
|
|
744
751
|
return quartzDate(date, month || "\u672C\u6708");
|
|
745
752
|
}
|
|
746
|
-
if (schedule.shapes.date === "step") {
|
|
753
|
+
if (schedule.shapes.date === "step" && isOpenStep(date)) {
|
|
747
754
|
return month + cadence(stepSegment(schedule, "date").interval, "\u5929");
|
|
748
755
|
}
|
|
749
756
|
if (!month) {
|
|
750
757
|
return "\u6BCF\u6708" + dayList(schedule);
|
|
751
758
|
}
|
|
752
|
-
const monthMulti = schedule.shapes.month === "range" || schedule.shapes.month === "list";
|
|
759
|
+
const monthMulti = schedule.shapes.month === "range" || schedule.shapes.month === "list" || boundedParityMonth(schedule);
|
|
753
760
|
return month + (monthMulti ? "\uFF0C" : "") + dayList(schedule);
|
|
754
761
|
}
|
|
755
762
|
function dateCore(schedule, quartzPrefix) {
|
|
756
763
|
if (schedule.shapes.date === "quartz") {
|
|
757
764
|
return quartzDate(schedule.pattern.date, quartzPrefix);
|
|
758
765
|
}
|
|
759
|
-
if (schedule.shapes.date === "step") {
|
|
766
|
+
if (schedule.shapes.date === "step" && isOpenStep(schedule.pattern.date)) {
|
|
760
767
|
return cadence(stepSegment(schedule, "date").interval, "\u5929");
|
|
761
768
|
}
|
|
762
769
|
return dayList(schedule);
|
|
@@ -837,7 +844,8 @@ function composePoint(schedule, core) {
|
|
|
837
844
|
}
|
|
838
845
|
const dateSet = isSet(schedule.pattern.date);
|
|
839
846
|
const weekdaySet = isSet(schedule.pattern.weekday);
|
|
840
|
-
const
|
|
847
|
+
const dateCadence = schedule.shapes.date === "step" && isOpenStep(schedule.pattern.date);
|
|
848
|
+
const comma = dateSet && weekdaySet || dateCadence;
|
|
841
849
|
return qual + (comma ? "\uFF0C" : "") + core;
|
|
842
850
|
}
|
|
843
851
|
function composeCadence(schedule, core) {
|
package/dist/lang/zh.js
CHANGED
|
@@ -648,13 +648,20 @@ function render(schedule, plan, opts) {
|
|
|
648
648
|
opts
|
|
649
649
|
);
|
|
650
650
|
}
|
|
651
|
+
function boundedParityMonth(schedule) {
|
|
652
|
+
if (schedule.shapes.month !== "step" || isOpenStep(schedule.pattern.month)) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
const segs = segmentsOf(schedule, "month");
|
|
656
|
+
return segs.length === 1 && segs[0].kind === "step" && segs[0].interval === 2;
|
|
657
|
+
}
|
|
651
658
|
function monthPhrase(schedule) {
|
|
652
659
|
if (schedule.pattern.month === "*") {
|
|
653
660
|
return "";
|
|
654
661
|
}
|
|
655
662
|
const segs = segmentsOf(schedule, "month");
|
|
656
663
|
const first = segs[0];
|
|
657
|
-
if (segs.length === 1 && first.kind === "step" && first.interval === 2) {
|
|
664
|
+
if (segs.length === 1 && first.kind === "step" && first.interval === 2 && isOpenStep(schedule.pattern.month)) {
|
|
658
665
|
return "\u6BCF\u4E2A" + (first.fires[0] % 2 ? "\u5947" : "\u5076") + "\u6570\u6708";
|
|
659
666
|
}
|
|
660
667
|
if (segs.length === 1 && first.kind === "range") {
|
|
@@ -672,14 +679,14 @@ function monthPhrase(schedule) {
|
|
|
672
679
|
}
|
|
673
680
|
function dayList(schedule) {
|
|
674
681
|
const segs = segmentsOf(schedule, "date");
|
|
675
|
-
if (segs.every((seg) => seg.kind === "single")) {
|
|
676
|
-
return segs
|
|
682
|
+
if (segs.every((seg) => seg.kind === "single" || seg.kind === "step")) {
|
|
683
|
+
return fireValues(segs).join("\u3001") + "\u65E5";
|
|
677
684
|
}
|
|
678
685
|
return segs.map(function day(seg) {
|
|
679
686
|
if (seg.kind === "range") {
|
|
680
687
|
return seg.bounds[0] + "\u65E5\u81F3" + seg.bounds[1] + "\u65E5";
|
|
681
688
|
}
|
|
682
|
-
return seg.value + "\u65E5";
|
|
689
|
+
return seg.kind === "step" ? seg.fires.join("\u3001") + "\u65E5" : seg.value + "\u65E5";
|
|
683
690
|
}).join("\u3001");
|
|
684
691
|
}
|
|
685
692
|
function quartzDate(token, monthPrefix) {
|
|
@@ -717,20 +724,20 @@ function datePhrase(schedule) {
|
|
|
717
724
|
if (schedule.shapes.date === "quartz") {
|
|
718
725
|
return quartzDate(date, month || "\u672C\u6708");
|
|
719
726
|
}
|
|
720
|
-
if (schedule.shapes.date === "step") {
|
|
727
|
+
if (schedule.shapes.date === "step" && isOpenStep(date)) {
|
|
721
728
|
return month + cadence(stepSegment(schedule, "date").interval, "\u5929");
|
|
722
729
|
}
|
|
723
730
|
if (!month) {
|
|
724
731
|
return "\u6BCF\u6708" + dayList(schedule);
|
|
725
732
|
}
|
|
726
|
-
const monthMulti = schedule.shapes.month === "range" || schedule.shapes.month === "list";
|
|
733
|
+
const monthMulti = schedule.shapes.month === "range" || schedule.shapes.month === "list" || boundedParityMonth(schedule);
|
|
727
734
|
return month + (monthMulti ? "\uFF0C" : "") + dayList(schedule);
|
|
728
735
|
}
|
|
729
736
|
function dateCore(schedule, quartzPrefix) {
|
|
730
737
|
if (schedule.shapes.date === "quartz") {
|
|
731
738
|
return quartzDate(schedule.pattern.date, quartzPrefix);
|
|
732
739
|
}
|
|
733
|
-
if (schedule.shapes.date === "step") {
|
|
740
|
+
if (schedule.shapes.date === "step" && isOpenStep(schedule.pattern.date)) {
|
|
734
741
|
return cadence(stepSegment(schedule, "date").interval, "\u5929");
|
|
735
742
|
}
|
|
736
743
|
return dayList(schedule);
|
|
@@ -811,7 +818,8 @@ function composePoint(schedule, core) {
|
|
|
811
818
|
}
|
|
812
819
|
const dateSet = isSet(schedule.pattern.date);
|
|
813
820
|
const weekdaySet = isSet(schedule.pattern.weekday);
|
|
814
|
-
const
|
|
821
|
+
const dateCadence = schedule.shapes.date === "step" && isOpenStep(schedule.pattern.date);
|
|
822
|
+
const comma = dateSet && weekdaySet || dateCadence;
|
|
815
823
|
return qual + (comma ? "\uFF0C" : "") + core;
|
|
816
824
|
}
|
|
817
825
|
function composeCadence(schedule, core) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cronli5",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Cron Like I'm Five: A Cron to English Utility",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"lint": "eslint src test cli.js eslint.config.js scripts tooling/scripts",
|
|
66
66
|
"metamorphic": "node --import tsx tooling/scripts/metamorphic.mjs",
|
|
67
67
|
"lint:fix": "eslint src test cli.js eslint.config.js scripts tooling/scripts --fix",
|
|
68
|
-
"test": "
|
|
68
|
+
"test": "vitest run",
|
|
69
69
|
"types": "tsc -p tsconfig.types.json",
|
|
70
70
|
"test:types": "npm run types && tsd",
|
|
71
71
|
"typecheck": "tsc -p tsconfig.json",
|
|
72
|
-
"coverage": "
|
|
72
|
+
"coverage": "vitest run --coverage",
|
|
73
73
|
"verify": "npm run lint && npm run typecheck && npm run test:types && npm test && npm run coverage && npm run conciseness && npm run docs -- --check && npm run build",
|
|
74
74
|
"prepare": "node scripts/install-hooks.mjs",
|
|
75
75
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test:types && npm run build && npm test"
|
|
@@ -90,18 +90,18 @@
|
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@eslint/eslintrc": "^3.2.0",
|
|
92
92
|
"@eslint/js": "^9.15.0",
|
|
93
|
-
"
|
|
93
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
94
94
|
"chai": "^4.5.0",
|
|
95
95
|
"cronstrue": "^3.14.0",
|
|
96
96
|
"esbuild": "^0.28.1",
|
|
97
97
|
"eslint": "^9.15.0",
|
|
98
98
|
"fast-check": "^3.23.1",
|
|
99
99
|
"globals": "^15.12.0",
|
|
100
|
-
"mocha": "^11.0.1",
|
|
101
100
|
"tsd": "^0.31.2",
|
|
102
101
|
"tsx": "^4.22.4",
|
|
103
102
|
"typescript": "^6.0.3",
|
|
104
|
-
"typescript-eslint": "^8.61.0"
|
|
103
|
+
"typescript-eslint": "^8.61.0",
|
|
104
|
+
"vitest": "^4.1.9"
|
|
105
105
|
},
|
|
106
106
|
"tsd": {
|
|
107
107
|
"directory": "test-d"
|
package/src/lang/zh/index.ts
CHANGED
|
@@ -1041,8 +1041,26 @@ function render(schedule: Schedule, plan: PlanNode, opts: Opts): string {
|
|
|
1041
1041
|
|
|
1042
1042
|
// --- Day-level qualifier (date / month / weekday / year). ---
|
|
1043
1043
|
|
|
1044
|
-
//
|
|
1044
|
+
// Whether the month is a BOUNDED parity step ("2-10/2") — an interval-2 step
|
|
1045
|
+
// that does NOT span the open parity set. It enumerates as a list of singles
|
|
1046
|
+
// ("2、4、6、8、10月"), so it takes the multi-month comma like an explicit list,
|
|
1047
|
+
// unlike a single month or a non-parity bounded step ("3-11/3", glued).
|
|
1048
|
+
function boundedParityMonth(schedule: Schedule): boolean {
|
|
1049
|
+
if (schedule.shapes.month !== 'step' ||
|
|
1050
|
+
isOpenStep(schedule.pattern.month)) {
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const segs = segmentsOf(schedule, 'month');
|
|
1055
|
+
|
|
1056
|
+
return segs.length === 1 && segs[0].kind === 'step' && segs[0].interval === 2;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// The month phrase: "" (wildcard), "每个奇数月"/"每个偶数月" (OPEN step ×2),
|
|
1045
1060
|
// "1月至3月" (range), else the enumerated numbers sharing one 月 ("1、4、7、10月").
|
|
1061
|
+
// A BOUNDED parity step ("2-10/2" = months 2,4,6,8,10) fires a finite set, so
|
|
1062
|
+
// it enumerates through the number path below rather than the open parity class
|
|
1063
|
+
// — the "每个偶数月" wording asserts December too, which the bound excludes.
|
|
1046
1064
|
function monthPhrase(schedule: Schedule): string {
|
|
1047
1065
|
if (schedule.pattern.month === '*') {
|
|
1048
1066
|
return '';
|
|
@@ -1051,7 +1069,8 @@ function monthPhrase(schedule: Schedule): string {
|
|
|
1051
1069
|
const segs = segmentsOf(schedule, 'month');
|
|
1052
1070
|
const first = segs[0];
|
|
1053
1071
|
|
|
1054
|
-
if (segs.length === 1 && first.kind === 'step' && first.interval === 2
|
|
1072
|
+
if (segs.length === 1 && first.kind === 'step' && first.interval === 2 &&
|
|
1073
|
+
isOpenStep(schedule.pattern.month)) {
|
|
1055
1074
|
return '每个' + (first.fires[0] % 2 ? '奇' : '偶') + '数月';
|
|
1056
1075
|
}
|
|
1057
1076
|
|
|
@@ -1074,13 +1093,14 @@ function monthPhrase(schedule: Schedule): string {
|
|
|
1074
1093
|
return nums.join('、') + '月';
|
|
1075
1094
|
}
|
|
1076
1095
|
|
|
1077
|
-
// The day-of-month list. A
|
|
1078
|
-
// (
|
|
1096
|
+
// The day-of-month list. A list of singles — or a bounded step enumerated to
|
|
1097
|
+
// its fires (9-17/2 = 9,11,13,15,17) — shares one trailing 日 ("1、3、8日",
|
|
1098
|
+
// "9、11、13、15、17日"); any range gives each segment its own 日 ("1至5日、10日").
|
|
1079
1099
|
function dayList(schedule: Schedule): string {
|
|
1080
1100
|
const segs = segmentsOf(schedule, 'date');
|
|
1081
1101
|
|
|
1082
|
-
if (segs.every((seg) => seg.kind === 'single')) {
|
|
1083
|
-
return segs
|
|
1102
|
+
if (segs.every((seg) => seg.kind === 'single' || seg.kind === 'step')) {
|
|
1103
|
+
return fireValues(segs).join('、') + '日';
|
|
1084
1104
|
}
|
|
1085
1105
|
|
|
1086
1106
|
return segs.map(function day(seg) {
|
|
@@ -1088,7 +1108,9 @@ function dayList(schedule: Schedule): string {
|
|
|
1088
1108
|
return seg.bounds[0] + '日至' + seg.bounds[1] + '日';
|
|
1089
1109
|
}
|
|
1090
1110
|
|
|
1091
|
-
return
|
|
1111
|
+
return seg.kind === 'step' ?
|
|
1112
|
+
seg.fires.join('、') + '日' :
|
|
1113
|
+
(seg as {value: string}).value + '日';
|
|
1092
1114
|
}).join('、');
|
|
1093
1115
|
}
|
|
1094
1116
|
|
|
@@ -1152,7 +1174,11 @@ function datePhrase(schedule: Schedule): string {
|
|
|
1152
1174
|
return quartzDate(date, month || '本月');
|
|
1153
1175
|
}
|
|
1154
1176
|
|
|
1155
|
-
|
|
1177
|
+
// An OPEN day step ("*/N") is a frequency — the bare "每N天" cadence. A
|
|
1178
|
+
// BOUNDED step ("a-b/N") fires a finite set of days, so it enumerates them
|
|
1179
|
+
// through the day-list path below, never the cadence (which would drop the
|
|
1180
|
+
// bounds).
|
|
1181
|
+
if (schedule.shapes.date === 'step' && isOpenStep(date)) {
|
|
1156
1182
|
return month + cadence(stepSegment(schedule, 'date').interval, '天');
|
|
1157
1183
|
}
|
|
1158
1184
|
|
|
@@ -1160,12 +1186,13 @@ function datePhrase(schedule: Schedule): string {
|
|
|
1160
1186
|
return '每月' + dayList(schedule);
|
|
1161
1187
|
}
|
|
1162
1188
|
|
|
1163
|
-
// A multi-month scope (range/list
|
|
1189
|
+
// A multi-month scope (range/list, or a bounded parity step that enumerates
|
|
1190
|
+
// like a list — "2、4、6、8、10月") ends in 月 and would run straight into the
|
|
1164
1191
|
// day — "6月至8月1日" reads "8月1日" as August 1st. The comma keeps the month
|
|
1165
1192
|
// scope distinct from the day ("6月至8月,1日"). A single month stays glued
|
|
1166
1193
|
// ("6月1日"), which is unambiguous.
|
|
1167
1194
|
const monthMulti = schedule.shapes.month === 'range' ||
|
|
1168
|
-
schedule.shapes.month === 'list';
|
|
1195
|
+
schedule.shapes.month === 'list' || boundedParityMonth(schedule);
|
|
1169
1196
|
|
|
1170
1197
|
return month + (monthMulti ? ',' : '') + dayList(schedule);
|
|
1171
1198
|
}
|
|
@@ -1178,7 +1205,9 @@ function dateCore(schedule: Schedule, quartzPrefix: string): string {
|
|
|
1178
1205
|
return quartzDate(schedule.pattern.date, quartzPrefix);
|
|
1179
1206
|
}
|
|
1180
1207
|
|
|
1181
|
-
|
|
1208
|
+
// An open day step is the bare "每N天" cadence; a bounded step enumerates its
|
|
1209
|
+
// days through dayList (see datePhrase), so the bounds are not dropped.
|
|
1210
|
+
if (schedule.shapes.date === 'step' && isOpenStep(schedule.pattern.date)) {
|
|
1182
1211
|
return cadence(stepSegment(schedule, 'date').interval, '天');
|
|
1183
1212
|
}
|
|
1184
1213
|
|
|
@@ -1318,7 +1347,12 @@ function composePoint(schedule: Schedule, core: string): string {
|
|
|
1318
1347
|
|
|
1319
1348
|
const dateSet = isSet(schedule.pattern.date);
|
|
1320
1349
|
const weekdaySet = isSet(schedule.pattern.weekday);
|
|
1321
|
-
|
|
1350
|
+
// The comma separates an OR union or the open "每N天" cadence from the core. A
|
|
1351
|
+
// bounded date step renders as a glued day list ("每月9、11…日"), not a
|
|
1352
|
+
// cadence, so it takes no comma — only an open step does.
|
|
1353
|
+
const dateCadence = schedule.shapes.date === 'step' &&
|
|
1354
|
+
isOpenStep(schedule.pattern.date);
|
|
1355
|
+
const comma = dateSet && weekdaySet || dateCadence;
|
|
1322
1356
|
|
|
1323
1357
|
return qual + (comma ? ',' : '') + core;
|
|
1324
1358
|
}
|