cronli5 0.3.4 → 0.8.0
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 +120 -0
- package/README.md +63 -13
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +72 -9
- package/dist/cronli5.js +72 -9
- package/dist/lang/de.cjs +14 -6
- package/dist/lang/de.js +14 -6
- package/dist/lang/en.cjs +14 -6
- package/dist/lang/en.js +14 -6
- package/dist/lang/es.cjs +14 -6
- package/dist/lang/es.js +14 -6
- package/dist/lang/fi.cjs +14 -6
- package/dist/lang/fi.js +14 -6
- package/dist/lang/fr.cjs +1211 -0
- package/dist/lang/fr.js +1187 -0
- package/dist/lang/pt.cjs +1592 -0
- package/dist/lang/pt.js +1568 -0
- package/dist/lang/zh.cjs +58 -9
- package/dist/lang/zh.js +58 -9
- package/package.json +13 -2
- package/src/core/cadence.ts +25 -12
- package/src/core/index.ts +7 -3
- package/src/core/quartz.ts +97 -0
- package/src/core/schedule.ts +1 -0
- package/src/core/specs.ts +2 -2
- package/src/cronli5.ts +20 -3
- package/src/lang/de/index.ts +3 -2
- package/src/lang/en/index.ts +3 -2
- package/src/lang/es/index.ts +3 -2
- package/src/lang/fi/index.ts +3 -2
- package/src/lang/fr/dialects.ts +49 -0
- package/src/lang/fr/index.ts +2116 -0
- package/src/lang/fr/notes.md +280 -0
- package/src/lang/fr/status.json +8 -0
- package/src/lang/pt/dialects.ts +56 -0
- package/src/lang/pt/index.ts +2804 -0
- package/src/lang/pt/notes.md +199 -0
- package/src/lang/pt/status.json +8 -0
- package/src/lang/zh/index.ts +61 -5
- package/src/lang/zh/notes.md +16 -4
- package/src/lang/zh/status.json +10 -1
- package/src/types.ts +44 -0
- package/types/core/cadence.d.ts +1 -0
- package/types/core/quartz.d.ts +4 -0
- package/types/core/schedule.d.ts +1 -0
- package/types/cronli5.d.ts +4 -4
- package/types/lang/fr/dialects.d.ts +11 -0
- package/types/lang/fr/index.d.ts +4 -0
- package/types/lang/pt/dialects.d.ts +13 -0
- package/types/lang/pt/index.d.ts +4 -0
- package/types/types.d.ts +39 -0
package/dist/lang/zh.cjs
CHANGED
|
@@ -59,13 +59,20 @@ function singleValues(segments) {
|
|
|
59
59
|
function offsetCleanStride(stride) {
|
|
60
60
|
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
61
61
|
}
|
|
62
|
+
function lastTileOf(start, interval, cycle) {
|
|
63
|
+
return cycle - 1 - (cycle - 1 - start) % interval;
|
|
64
|
+
}
|
|
62
65
|
function renderStride(spec, parts) {
|
|
63
|
-
const { start, interval, cycle } = spec;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
+
const { start, interval, last, cycle } = spec;
|
|
67
|
+
const open = cycle % interval === 0 && last === lastTileOf(
|
|
68
|
+
start,
|
|
69
|
+
interval,
|
|
70
|
+
cycle
|
|
71
|
+
);
|
|
72
|
+
if (start === 0 && open) {
|
|
66
73
|
return parts.bare();
|
|
67
74
|
}
|
|
68
|
-
if (start < interval &&
|
|
75
|
+
if (start < interval && open) {
|
|
69
76
|
return parts.offset();
|
|
70
77
|
}
|
|
71
78
|
return parts.bounded();
|
|
@@ -165,6 +172,35 @@ function resolveDialect(dialect) {
|
|
|
165
172
|
// src/lang/zh/index.ts
|
|
166
173
|
var UNITS = { hour: "\u5C0F\u65F6", minute: "\u5206\u949F", second: "\u79D2" };
|
|
167
174
|
var WEEKDAYS = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
|
|
175
|
+
var HANT = {
|
|
176
|
+
\u4E2A: "\u500B",
|
|
177
|
+
\u4ECE: "\u5F9E",
|
|
178
|
+
\u5185: "\u5167",
|
|
179
|
+
\u522B: "\u5225",
|
|
180
|
+
\u52A8: "\u52D5",
|
|
181
|
+
\u5355: "\u55AE",
|
|
182
|
+
\u53CC: "\u96D9",
|
|
183
|
+
\u540E: "\u5F8C",
|
|
184
|
+
\u542F: "\u555F",
|
|
185
|
+
\u5468: "\u9031",
|
|
186
|
+
\u6570: "\u6578",
|
|
187
|
+
\u65E0: "\u7121",
|
|
188
|
+
\u65F6: "\u6642",
|
|
189
|
+
\u70B9: "\u9EDE",
|
|
190
|
+
\u7EDF: "\u7D71",
|
|
191
|
+
\u8BC6: "\u8B58",
|
|
192
|
+
\u8FBE: "\u9054",
|
|
193
|
+
\u8FD0: "\u904B",
|
|
194
|
+
\u949F: "\u9418",
|
|
195
|
+
\u95F4: "\u9593"
|
|
196
|
+
};
|
|
197
|
+
function toVariant(text, variant) {
|
|
198
|
+
if (variant !== "Hant") {
|
|
199
|
+
return text;
|
|
200
|
+
}
|
|
201
|
+
return Array.from(text, (glyph) => HANT[glyph] ?? glyph).join("");
|
|
202
|
+
}
|
|
203
|
+
var activeVariant = "Hans";
|
|
168
204
|
function joinAnd(items) {
|
|
169
205
|
if (items.length < 2) {
|
|
170
206
|
return items.join("");
|
|
@@ -177,7 +213,7 @@ function cadence(interval, unit) {
|
|
|
177
213
|
function renderStride2(stride) {
|
|
178
214
|
const { interval, start, last, cycle, unit, mark, anchor } = stride;
|
|
179
215
|
const lead = anchor + "\u4ECE" + start + mark + "\u8D77" + cadence(interval, unit);
|
|
180
|
-
return renderStride({ start, interval, cycle }, {
|
|
216
|
+
return renderStride({ start, interval, last, cycle }, {
|
|
181
217
|
bare: () => cadence(interval, unit),
|
|
182
218
|
offset: () => lead,
|
|
183
219
|
bounded: () => lead + "\uFF0C\u81F3" + last + mark
|
|
@@ -873,6 +909,9 @@ function hourCadenceApplies(schedule) {
|
|
|
873
909
|
return hourCadenceText(schedule) !== null;
|
|
874
910
|
}
|
|
875
911
|
function describe(schedule, opts) {
|
|
912
|
+
return toVariant(describeHans(schedule, opts), opts.style.variant);
|
|
913
|
+
}
|
|
914
|
+
function describeHans(schedule, opts) {
|
|
876
915
|
const { kind } = schedule.plan;
|
|
877
916
|
const core = render(schedule, schedule.plan, opts);
|
|
878
917
|
let composed = core;
|
|
@@ -901,21 +940,31 @@ function describe(schedule, opts) {
|
|
|
901
940
|
}
|
|
902
941
|
function normalizeOptions(options) {
|
|
903
942
|
options = options || {};
|
|
943
|
+
const style = resolveDialect(options.dialect);
|
|
944
|
+
activeVariant = style.variant;
|
|
904
945
|
return {
|
|
905
946
|
ampm: typeof options.ampm === "boolean" ? options.ampm : false,
|
|
906
947
|
lenient: !!options.lenient,
|
|
948
|
+
quartz: !!options.quartz,
|
|
907
949
|
seconds: !!options.seconds,
|
|
908
950
|
short: !!options.short,
|
|
909
|
-
style
|
|
951
|
+
style,
|
|
910
952
|
years: !!options.years
|
|
911
953
|
};
|
|
912
954
|
}
|
|
913
955
|
var zh2 = {
|
|
914
956
|
describe,
|
|
915
|
-
fallback
|
|
957
|
+
// `reboot`/`fallback` are contract-fixed strings the core reads without
|
|
958
|
+
// `opts`; getters honor the variant `options()` latched, keeping the shared
|
|
959
|
+
// Language contract unchanged while the Traditional dialect still applies.
|
|
960
|
+
get fallback() {
|
|
961
|
+
return toVariant("\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F", activeVariant);
|
|
962
|
+
},
|
|
916
963
|
options: normalizeOptions,
|
|
917
|
-
reboot
|
|
918
|
-
|
|
964
|
+
get reboot() {
|
|
965
|
+
return toVariant("\u7CFB\u7EDF\u542F\u52A8\u65F6", activeVariant);
|
|
966
|
+
},
|
|
967
|
+
sentence: (description) => toVariant("\u8FD0\u884C\u65F6\u95F4\uFF1A", activeVariant) + description + "\u3002"
|
|
919
968
|
};
|
|
920
969
|
var index_default = zh2;
|
|
921
970
|
module.exports = module.exports.default;
|
package/dist/lang/zh.js
CHANGED
|
@@ -33,13 +33,20 @@ function singleValues(segments) {
|
|
|
33
33
|
function offsetCleanStride(stride) {
|
|
34
34
|
return stride.start < stride.interval && 24 % stride.interval === 0;
|
|
35
35
|
}
|
|
36
|
+
function lastTileOf(start, interval, cycle) {
|
|
37
|
+
return cycle - 1 - (cycle - 1 - start) % interval;
|
|
38
|
+
}
|
|
36
39
|
function renderStride(spec, parts) {
|
|
37
|
-
const { start, interval, cycle } = spec;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
const { start, interval, last, cycle } = spec;
|
|
41
|
+
const open = cycle % interval === 0 && last === lastTileOf(
|
|
42
|
+
start,
|
|
43
|
+
interval,
|
|
44
|
+
cycle
|
|
45
|
+
);
|
|
46
|
+
if (start === 0 && open) {
|
|
40
47
|
return parts.bare();
|
|
41
48
|
}
|
|
42
|
-
if (start < interval &&
|
|
49
|
+
if (start < interval && open) {
|
|
43
50
|
return parts.offset();
|
|
44
51
|
}
|
|
45
52
|
return parts.bounded();
|
|
@@ -139,6 +146,35 @@ function resolveDialect(dialect) {
|
|
|
139
146
|
// src/lang/zh/index.ts
|
|
140
147
|
var UNITS = { hour: "\u5C0F\u65F6", minute: "\u5206\u949F", second: "\u79D2" };
|
|
141
148
|
var WEEKDAYS = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
|
|
149
|
+
var HANT = {
|
|
150
|
+
\u4E2A: "\u500B",
|
|
151
|
+
\u4ECE: "\u5F9E",
|
|
152
|
+
\u5185: "\u5167",
|
|
153
|
+
\u522B: "\u5225",
|
|
154
|
+
\u52A8: "\u52D5",
|
|
155
|
+
\u5355: "\u55AE",
|
|
156
|
+
\u53CC: "\u96D9",
|
|
157
|
+
\u540E: "\u5F8C",
|
|
158
|
+
\u542F: "\u555F",
|
|
159
|
+
\u5468: "\u9031",
|
|
160
|
+
\u6570: "\u6578",
|
|
161
|
+
\u65E0: "\u7121",
|
|
162
|
+
\u65F6: "\u6642",
|
|
163
|
+
\u70B9: "\u9EDE",
|
|
164
|
+
\u7EDF: "\u7D71",
|
|
165
|
+
\u8BC6: "\u8B58",
|
|
166
|
+
\u8FBE: "\u9054",
|
|
167
|
+
\u8FD0: "\u904B",
|
|
168
|
+
\u949F: "\u9418",
|
|
169
|
+
\u95F4: "\u9593"
|
|
170
|
+
};
|
|
171
|
+
function toVariant(text, variant) {
|
|
172
|
+
if (variant !== "Hant") {
|
|
173
|
+
return text;
|
|
174
|
+
}
|
|
175
|
+
return Array.from(text, (glyph) => HANT[glyph] ?? glyph).join("");
|
|
176
|
+
}
|
|
177
|
+
var activeVariant = "Hans";
|
|
142
178
|
function joinAnd(items) {
|
|
143
179
|
if (items.length < 2) {
|
|
144
180
|
return items.join("");
|
|
@@ -151,7 +187,7 @@ function cadence(interval, unit) {
|
|
|
151
187
|
function renderStride2(stride) {
|
|
152
188
|
const { interval, start, last, cycle, unit, mark, anchor } = stride;
|
|
153
189
|
const lead = anchor + "\u4ECE" + start + mark + "\u8D77" + cadence(interval, unit);
|
|
154
|
-
return renderStride({ start, interval, cycle }, {
|
|
190
|
+
return renderStride({ start, interval, last, cycle }, {
|
|
155
191
|
bare: () => cadence(interval, unit),
|
|
156
192
|
offset: () => lead,
|
|
157
193
|
bounded: () => lead + "\uFF0C\u81F3" + last + mark
|
|
@@ -847,6 +883,9 @@ function hourCadenceApplies(schedule) {
|
|
|
847
883
|
return hourCadenceText(schedule) !== null;
|
|
848
884
|
}
|
|
849
885
|
function describe(schedule, opts) {
|
|
886
|
+
return toVariant(describeHans(schedule, opts), opts.style.variant);
|
|
887
|
+
}
|
|
888
|
+
function describeHans(schedule, opts) {
|
|
850
889
|
const { kind } = schedule.plan;
|
|
851
890
|
const core = render(schedule, schedule.plan, opts);
|
|
852
891
|
let composed = core;
|
|
@@ -875,21 +914,31 @@ function describe(schedule, opts) {
|
|
|
875
914
|
}
|
|
876
915
|
function normalizeOptions(options) {
|
|
877
916
|
options = options || {};
|
|
917
|
+
const style = resolveDialect(options.dialect);
|
|
918
|
+
activeVariant = style.variant;
|
|
878
919
|
return {
|
|
879
920
|
ampm: typeof options.ampm === "boolean" ? options.ampm : false,
|
|
880
921
|
lenient: !!options.lenient,
|
|
922
|
+
quartz: !!options.quartz,
|
|
881
923
|
seconds: !!options.seconds,
|
|
882
924
|
short: !!options.short,
|
|
883
|
-
style
|
|
925
|
+
style,
|
|
884
926
|
years: !!options.years
|
|
885
927
|
};
|
|
886
928
|
}
|
|
887
929
|
var zh2 = {
|
|
888
930
|
describe,
|
|
889
|
-
fallback
|
|
931
|
+
// `reboot`/`fallback` are contract-fixed strings the core reads without
|
|
932
|
+
// `opts`; getters honor the variant `options()` latched, keeping the shared
|
|
933
|
+
// Language contract unchanged while the Traditional dialect still applies.
|
|
934
|
+
get fallback() {
|
|
935
|
+
return toVariant("\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F", activeVariant);
|
|
936
|
+
},
|
|
890
937
|
options: normalizeOptions,
|
|
891
|
-
reboot
|
|
892
|
-
|
|
938
|
+
get reboot() {
|
|
939
|
+
return toVariant("\u7CFB\u7EDF\u542F\u52A8\u65F6", activeVariant);
|
|
940
|
+
},
|
|
941
|
+
sentence: (description) => toVariant("\u8FD0\u884C\u65F6\u95F4\uFF1A", activeVariant) + description + "\u3002"
|
|
893
942
|
};
|
|
894
943
|
var index_default = zh2;
|
|
895
944
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cronli5",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Cron Like I'm Five: A Cron to English Utility",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"import": "./dist/lang/fi.js",
|
|
38
38
|
"require": "./dist/lang/fi.cjs"
|
|
39
39
|
},
|
|
40
|
+
"./lang/fr": {
|
|
41
|
+
"types": "./types/lang/fr/index.d.ts",
|
|
42
|
+
"import": "./dist/lang/fr.js",
|
|
43
|
+
"require": "./dist/lang/fr.cjs"
|
|
44
|
+
},
|
|
45
|
+
"./lang/pt": {
|
|
46
|
+
"types": "./types/lang/pt/index.d.ts",
|
|
47
|
+
"import": "./dist/lang/pt.js",
|
|
48
|
+
"require": "./dist/lang/pt.cjs"
|
|
49
|
+
},
|
|
40
50
|
"./lang/zh": {
|
|
41
51
|
"types": "./types/lang/zh/index.d.ts",
|
|
42
52
|
"import": "./dist/lang/zh.js",
|
|
@@ -61,6 +71,7 @@
|
|
|
61
71
|
"build": "node scripts/build.mjs && npm run types",
|
|
62
72
|
"docs": "node --import tsx scripts/docs.mjs",
|
|
63
73
|
"conciseness": "node --import tsx tooling/scripts/conciseness.mjs",
|
|
74
|
+
"divergence": "node --import tsx tooling/scripts/cronstrue-divergence.mjs",
|
|
64
75
|
"fuzz": "node --import tsx scripts/fuzz-lang.mjs",
|
|
65
76
|
"lint": "eslint src test cli.js eslint.config.js scripts tooling/scripts",
|
|
66
77
|
"metamorphic": "node --import tsx tooling/scripts/metamorphic.mjs",
|
|
@@ -75,7 +86,7 @@
|
|
|
75
86
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test:types && npm run build && npm test"
|
|
76
87
|
},
|
|
77
88
|
"engines": {
|
|
78
|
-
"node": ">=
|
|
89
|
+
"node": ">=20"
|
|
79
90
|
},
|
|
80
91
|
"keywords": [
|
|
81
92
|
"cron",
|
package/src/core/cadence.ts
CHANGED
|
@@ -94,28 +94,41 @@ interface StrideParts {
|
|
|
94
94
|
bounded(): string;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// The last value a stride reaches within `[0, cycle)`: the largest value below
|
|
98
|
+
// the cycle that is congruent to `start` modulo `interval`. A clean, full-field
|
|
99
|
+
// stride runs to this tile; a bounded one (`a-b/n`) stops short of it, and that
|
|
100
|
+
// shortfall is what distinguishes the open cadence from a bounded set.
|
|
101
|
+
function lastTileOf(start: number, interval: number, cycle: number): number {
|
|
102
|
+
return cycle - 1 - (cycle - 1 - start) % interval;
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
// Choose the stride/cadence branch for a step over a `cycle`-long field and
|
|
98
|
-
// emit the renderer's words for it. A clean stride from the top of the cycle
|
|
99
|
-
//
|
|
106
|
+
// emit the renderer's words for it. A clean stride from the top of the cycle is
|
|
107
|
+
// the bare cadence; a uniform offset (start within the first interval, the
|
|
100
108
|
// interval still tiling the cycle) names only its start, since it wraps cleanly
|
|
101
|
-
// with no distinct endpoint; a non-uniform stride (start >= interval,
|
|
102
|
-
// interval that does not tile the cycle
|
|
103
|
-
//
|
|
104
|
-
//
|
|
109
|
+
// with no distinct endpoint; a non-uniform stride (start >= interval, an
|
|
110
|
+
// interval that does not tile the cycle, or one whose last fire stops short of
|
|
111
|
+
// the cycle's final tile — a bounded `a-b/n`) pins both endpoints so the
|
|
112
|
+
// bounded, non-wrapping set reads unambiguously. This is the one decision tree
|
|
113
|
+
// every renderer's `renderStride`/`hourStrideCadence` shared (cycle 60 for
|
|
105
114
|
// minute/second, 24 for the hour); the branch lives here once, the prose in
|
|
106
115
|
// each language's `parts`.
|
|
107
116
|
function renderStride(
|
|
108
|
-
spec: {start: number; interval: number; cycle: number},
|
|
117
|
+
spec: {start: number; interval: number; last: number; cycle: number},
|
|
109
118
|
parts: StrideParts
|
|
110
119
|
): string {
|
|
111
|
-
const {start, interval, cycle} = spec;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
const {start, interval, last, cycle} = spec;
|
|
121
|
+
// A stride wraps the full field only when it both tiles the cycle and runs to
|
|
122
|
+
// the cycle's last tile; one that stops short (`0-20/2`, last 20 of 22) is a
|
|
123
|
+
// bounded set, not the open `*/n`, so it keeps its endpoint-pinning cadence.
|
|
124
|
+
const open = cycle % interval === 0 && last === lastTileOf(start, interval,
|
|
125
|
+
cycle);
|
|
126
|
+
|
|
127
|
+
if (start === 0 && open) {
|
|
115
128
|
return parts.bare();
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
if (start < interval &&
|
|
131
|
+
if (start < interval && open) {
|
|
119
132
|
return parts.offset();
|
|
120
133
|
}
|
|
121
134
|
|
package/src/core/index.ts
CHANGED
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
import {applyQuartzAliases, normalizeCronPattern} from './normalize.js';
|
|
7
7
|
import type {NormalizedOptions, Pattern} from './schedule.js';
|
|
8
|
+
import {applyQuartz} from './quartz.js';
|
|
8
9
|
import {parseCronPattern} from './parse.js';
|
|
9
10
|
import type {CronPattern} from '../types.js';
|
|
10
11
|
import {validateCronPattern} from './validate.js';
|
|
11
12
|
|
|
12
|
-
// Parse, alias, validate, and normalize cron input
|
|
13
|
-
// cron-like object of string fields, ready for semantic
|
|
14
|
-
// rendering.
|
|
13
|
+
// Parse, apply Quartz semantics, alias, validate, and normalize cron input
|
|
14
|
+
// into a canonical cron-like object of string fields, ready for semantic
|
|
15
|
+
// analysis and rendering. Quartz handling runs first: it gates the `?` token
|
|
16
|
+
// (rejected unless `quartz`) and re-indexes the Quartz day-of-week to the
|
|
17
|
+
// canonical cron numbering the rest of the core expects.
|
|
15
18
|
function prepare(cronPattern: CronPattern, opts: NormalizedOptions): Pattern {
|
|
16
19
|
const pattern = parseCronPattern(cronPattern, opts);
|
|
17
20
|
|
|
21
|
+
applyQuartz(pattern, opts.quartz);
|
|
18
22
|
applyQuartzAliases(pattern);
|
|
19
23
|
validateCronPattern(pattern);
|
|
20
24
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Quartz input semantics. cronli5 accepts Quartz tokens (`?`, `L`, `W`, `#`),
|
|
2
|
+
// but Quartz numbers the day-of-week differently from standard cron: Quartz is
|
|
3
|
+
// 1 = Sunday … 7 = Saturday, while cron is 0/7 = Sunday, 1 = Monday. Reading a
|
|
4
|
+
// Quartz pattern with cron indexing silently shifts every weekday by one, so a
|
|
5
|
+
// Quartz `2` (Monday) would read as Tuesday. This module gates and re-indexes
|
|
6
|
+
// the input so the rest of the core keeps facing canonical cron values.
|
|
7
|
+
//
|
|
8
|
+
// The `?` token (Quartz's "no specific value", mandatory in Quartz, absent in
|
|
9
|
+
// standard cron) is the unambiguous mark of a Quartz pattern. Outside Quartz
|
|
10
|
+
// mode it is rejected outright rather than aliased to `*`, so a real Quartz
|
|
11
|
+
// cron errors loudly instead of being mis-read; inside Quartz mode it is the
|
|
12
|
+
// equivalent of `*`.
|
|
13
|
+
|
|
14
|
+
import type {CronLike} from './specs.js';
|
|
15
|
+
import {isNonNegativeInteger} from './util.js';
|
|
16
|
+
|
|
17
|
+
// The error a `?` raises outside Quartz mode: a clear pointer at the option
|
|
18
|
+
// that makes Quartz semantics (and `?`) available.
|
|
19
|
+
const quartzTokenMessage =
|
|
20
|
+
'`?` is a Quartz token — pass { quartz: true } to enable Quartz semantics.';
|
|
21
|
+
|
|
22
|
+
// In standard (non-Quartz) mode, `?` is not a valid value: reject it with a
|
|
23
|
+
// pointer at the `quartz` option. In Quartz mode, accept `?` as `*` and
|
|
24
|
+
// re-index the day-of-week from Quartz numbering (1 = Sunday) to the canonical
|
|
25
|
+
// cron numbering (0 = Sunday) the rest of the core expects. Operates in place
|
|
26
|
+
// on the raw cron-like object, before aliasing and validation.
|
|
27
|
+
function applyQuartz(cronPattern: CronLike, quartz: boolean): void {
|
|
28
|
+
if (!quartz) {
|
|
29
|
+
rejectQuartzToken(cronPattern.date);
|
|
30
|
+
rejectQuartzToken(cronPattern.weekday);
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ('' + cronPattern.date === '?') {
|
|
36
|
+
cronPattern.date = '*';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ('' + cronPattern.weekday === '?') {
|
|
40
|
+
cronPattern.weekday = '*';
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cronPattern.weekday = reindexWeekday('' + cronPattern.weekday);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Throw the Quartz-token error if a field is exactly `?`.
|
|
49
|
+
function rejectQuartzToken(value: string | number): void {
|
|
50
|
+
if ('' + value === '?') {
|
|
51
|
+
throw new Error(quartzTokenMessage);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Re-index a Quartz day-of-week field to canonical cron numbering. Every
|
|
56
|
+
// numeric weekday position maps n -> n-1 (Quartz 1 = Sunday becomes cron 0),
|
|
57
|
+
// across singles, ranges, and step bounds, and inside the DOW operators `nL`
|
|
58
|
+
// (last weekday) and `n#k` (kth weekday). Day NAMES (`MON`) are unambiguous and
|
|
59
|
+
// left untouched, as is the bare `L` alias (Saturday in both numberings) and
|
|
60
|
+
// `*`. Quartz has no weekday 0; it is rejected here.
|
|
61
|
+
function reindexWeekday(value: string): string {
|
|
62
|
+
if (value === '*') {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return value.split(',').map(reindexSegment).join(',');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Re-index one comma-separated weekday segment: a single, a range, or either
|
|
70
|
+
// of those followed by a `#k` or `L` operator (or a `/step`).
|
|
71
|
+
function reindexSegment(segment: string): string {
|
|
72
|
+
const operator = (/(#\d+|L)$/).exec(segment);
|
|
73
|
+
const suffix = operator ? operator[0] : '';
|
|
74
|
+
const core = suffix ? segment.slice(0, -suffix.length) : segment;
|
|
75
|
+
const step = core.split('/');
|
|
76
|
+
const range = step[0].split('-').map(reindexNumber).join('-');
|
|
77
|
+
const head = step.length === 2 ? range + '/' + step[1] : range;
|
|
78
|
+
|
|
79
|
+
return head + suffix;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Re-index a single weekday token: a number maps n -> n-1 (rejecting 0, which
|
|
83
|
+
// Quartz does not use); a name passes through unchanged.
|
|
84
|
+
function reindexNumber(token: string): string {
|
|
85
|
+
if (!isNonNegativeInteger(token)) {
|
|
86
|
+
return token;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (token === '0') {
|
|
90
|
+
throw new Error('`cronli5` was passed an invalid Quartz day-of-week ' +
|
|
91
|
+
'value "0"; Quartz numbers weekdays 1 (Sunday) through 7 (Saturday).');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return '' + (+token - 1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {applyQuartz, quartzTokenMessage};
|
package/src/core/schedule.ts
CHANGED
package/src/core/specs.ts
CHANGED
|
@@ -55,9 +55,9 @@ const fieldSpecs: Record<Field, FieldSpec> = {
|
|
|
55
55
|
second: {cyclic: true, max: 59, min: 0, top: 59},
|
|
56
56
|
minute: {cyclic: true, max: 59, min: 0, top: 59},
|
|
57
57
|
hour: {cyclic: true, max: 23, min: 0, top: 23},
|
|
58
|
-
date: {
|
|
58
|
+
date: {cyclic: true, max: 31, min: 1, top: 31},
|
|
59
59
|
month: {cyclic: true, max: 12, min: 1, numbers: monthNumbers, top: 12},
|
|
60
|
-
weekday: {aliases: {
|
|
60
|
+
weekday: {aliases: {L: '6'}, cyclic: true, max: 7, min: 0,
|
|
61
61
|
numbers: weekdayNumbers, top: 6},
|
|
62
62
|
year: {max: 9999, min: 1970}
|
|
63
63
|
};
|
package/src/cronli5.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import {analyze, prepare} from './core/index.js';
|
|
31
31
|
import type {NormalizedOptions} from './core/schedule.js';
|
|
32
|
-
import type {CronPattern, Cronli5Language, Cronli5Options}
|
|
32
|
+
import type {Cronli5, CronPattern, Cronli5Language, Cronli5Options}
|
|
33
33
|
from './types.js';
|
|
34
34
|
import en from './lang/en/index.js';
|
|
35
35
|
|
|
@@ -88,8 +88,25 @@ function interpretCronPattern(
|
|
|
88
88
|
return lang.describe({...schedule, plan}, opts);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export
|
|
91
|
+
// Two named convenience methods are attached to the callable export as sugar
|
|
92
|
+
// over the `sentence` option: `.sentence(...)` forces the capitalized
|
|
93
|
+
// standalone, `.fragment(...)` forces the embeddable fragment (the default).
|
|
94
|
+
// The method's own intent wins, so a passed-through `sentence` flag is
|
|
95
|
+
// overridden. There is no `toString` method on purpose — it would shadow
|
|
96
|
+
// `Function.prototype.toString` (called arg-less by `String()`, template
|
|
97
|
+
// literals, and console/debug) and break coercion; named methods sidestep that.
|
|
98
|
+
function sentence(cronPattern: CronPattern, options?: Cronli5Options): string {
|
|
99
|
+
return cronli5(cronPattern, {...options, sentence: true});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function fragment(cronPattern: CronPattern, options?: Cronli5Options): string {
|
|
103
|
+
return cronli5(cronPattern, {...options, sentence: false});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const callable: Cronli5 = Object.assign(cronli5, {sentence, fragment});
|
|
107
|
+
|
|
108
|
+
export default callable;
|
|
92
109
|
export type {
|
|
93
|
-
Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern,
|
|
110
|
+
Cronli5, Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern,
|
|
94
111
|
CronPatternObject
|
|
95
112
|
} from './types.js';
|
package/src/lang/de/index.ts
CHANGED
|
@@ -90,7 +90,7 @@ function renderStride(stride: Stride): string {
|
|
|
90
90
|
// the cadence keeps its endpoints but drops the "jeder Stunde" tail.
|
|
91
91
|
const tail = anchor ? ' ' + anchor : '';
|
|
92
92
|
|
|
93
|
-
return chooseStride({start, interval, cycle}, {
|
|
93
|
+
return chooseStride({start, interval, last, cycle}, {
|
|
94
94
|
bare: () => cadence,
|
|
95
95
|
offset: () => cadence + ' ab ' + unit.singular + ' ' + start + tail,
|
|
96
96
|
bounded: () =>
|
|
@@ -1019,7 +1019,7 @@ function hourStrideCadence(
|
|
|
1019
1019
|
const {start, interval, last} = stride;
|
|
1020
1020
|
const cadence = everyN(interval, UNITS.hour);
|
|
1021
1021
|
|
|
1022
|
-
return chooseStride({start, interval, cycle: 24}, {
|
|
1022
|
+
return chooseStride({start, interval, last, cycle: 24}, {
|
|
1023
1023
|
bare: () => cadence,
|
|
1024
1024
|
offset: () => cadence + ' ab ' + start + ' Uhr',
|
|
1025
1025
|
bounded: () => cadence + ' von ' + start + ' bis ' + last + ' Uhr'
|
|
@@ -1464,6 +1464,7 @@ function normalizeOptions(options?: Cronli5Options): Opts {
|
|
|
1464
1464
|
return {
|
|
1465
1465
|
ampm: typeof options.ampm === 'boolean' ? options.ampm : false,
|
|
1466
1466
|
lenient: !!options.lenient,
|
|
1467
|
+
quartz: !!options.quartz,
|
|
1467
1468
|
seconds: !!options.seconds,
|
|
1468
1469
|
short: !!options.short,
|
|
1469
1470
|
style,
|
package/src/lang/en/index.ts
CHANGED
|
@@ -137,6 +137,7 @@ function normalizeOptions(options?: Cronli5Options): NormalizedOptions {
|
|
|
137
137
|
return {
|
|
138
138
|
ampm: typeof options.ampm === 'boolean' ? options.ampm : true,
|
|
139
139
|
lenient: !!options.lenient,
|
|
140
|
+
quartz: !!options.quartz,
|
|
140
141
|
seconds: !!options.seconds,
|
|
141
142
|
short: !!options.short,
|
|
142
143
|
style: resolveDialect(options.dialect),
|
|
@@ -1313,7 +1314,7 @@ function renderStride(stride: Stride, opts: NormalizedOptions): string {
|
|
|
1313
1314
|
const {interval, start, last, cycle, unit, anchor} = stride;
|
|
1314
1315
|
const cadence = 'every ' + getNumber(interval, opts) + ' ' + unit + 's';
|
|
1315
1316
|
|
|
1316
|
-
return chooseStride({start, interval, cycle}, {
|
|
1317
|
+
return chooseStride({start, interval, last, cycle}, {
|
|
1317
1318
|
bare: () => cadence,
|
|
1318
1319
|
|
|
1319
1320
|
// A clean wrap from a non-zero offset: name the start, no endpoint.
|
|
@@ -1419,7 +1420,7 @@ function hourStrideCadence(stride: {start: number; interval: number;
|
|
|
1419
1420
|
const {start, interval, last} = stride;
|
|
1420
1421
|
const cadence = 'every ' + getNumber(interval, opts) + ' hours';
|
|
1421
1422
|
|
|
1422
|
-
return chooseStride({start, interval, cycle: 24}, {
|
|
1423
|
+
return chooseStride({start, interval, last, cycle: 24}, {
|
|
1423
1424
|
bare: () => cadence,
|
|
1424
1425
|
offset: () => cadence + ' from ' + getTime({hour: start, minute: 0}, opts),
|
|
1425
1426
|
bounded: () =>
|
package/src/lang/es/index.ts
CHANGED
|
@@ -125,6 +125,7 @@ function normalizeOptions(options?: Cronli5Options): Opts {
|
|
|
125
125
|
// 12-hour for Mexico/US); an explicit `{ampm}` option overrides it.
|
|
126
126
|
ampm: typeof options.ampm === 'boolean' ? options.ampm : style.ampm,
|
|
127
127
|
lenient: !!options.lenient,
|
|
128
|
+
quartz: !!options.quartz,
|
|
128
129
|
seconds: !!options.seconds,
|
|
129
130
|
short: !!options.short,
|
|
130
131
|
style,
|
|
@@ -1326,7 +1327,7 @@ function renderStride(stride: Stride, opts: Opts): string {
|
|
|
1326
1327
|
// the cadence keeps its endpoints but drops the "de cada <anchor>" tail.
|
|
1327
1328
|
const tail = anchor ? ' de cada ' + anchor : '';
|
|
1328
1329
|
|
|
1329
|
-
return chooseStride({start, interval, cycle}, {
|
|
1330
|
+
return chooseStride({start, interval, last, cycle}, {
|
|
1330
1331
|
bare: () => cadence,
|
|
1331
1332
|
offset: () => cadence + ' a partir del ' + unit + ' ' + start + tail,
|
|
1332
1333
|
bounded: () =>
|
|
@@ -1429,7 +1430,7 @@ function hourStrideCadence(
|
|
|
1429
1430
|
const {start, interval, last} = stride;
|
|
1430
1431
|
const cadence = 'cada ' + numero(interval, opts) + ' horas';
|
|
1431
1432
|
|
|
1432
|
-
return chooseStride({start, interval, cycle: 24}, {
|
|
1433
|
+
return chooseStride({start, interval, last, cycle: 24}, {
|
|
1433
1434
|
bare: () => cadence,
|
|
1434
1435
|
offset: () => cadence + ' a partir de ' + timePhrase(start, 0, null, opts),
|
|
1435
1436
|
bounded: () => cadence + ' de ' + timePhrase(start, 0, null, opts) + ' a ' +
|
package/src/lang/fi/index.ts
CHANGED
|
@@ -200,6 +200,7 @@ function normalizeOptions(options?: Cronli5Options): NormalizedOptions {
|
|
|
200
200
|
return {
|
|
201
201
|
ampm: false,
|
|
202
202
|
lenient: !!options.lenient,
|
|
203
|
+
quartz: !!options.quartz,
|
|
203
204
|
seconds: !!options.seconds,
|
|
204
205
|
short: !!options.short,
|
|
205
206
|
style: resolveDialect(options.dialect),
|
|
@@ -1108,7 +1109,7 @@ function renderStride(stride: Stride, opts: NormalizedOptions): string {
|
|
|
1108
1109
|
const {interval, start, last, cycle, unit} = stride;
|
|
1109
1110
|
const cadence = genitive(interval, opts) + ' ' + unit.gen + ' välein';
|
|
1110
1111
|
|
|
1111
|
-
return chooseStride({start, interval, cycle}, {
|
|
1112
|
+
return chooseStride({start, interval, last, cycle}, {
|
|
1112
1113
|
bare: () => cadence,
|
|
1113
1114
|
offset: () =>
|
|
1114
1115
|
cadence + ' ' + unit.anchor + ' ' + unit.ela + ' ' + start + ' alkaen',
|
|
@@ -1206,7 +1207,7 @@ function hourStrideCadence(
|
|
|
1206
1207
|
const {start, interval, last} = stride;
|
|
1207
1208
|
const cadence = genitive(interval, opts) + ' tunnin välein';
|
|
1208
1209
|
|
|
1209
|
-
return chooseStride({start, interval, cycle: 24}, {
|
|
1210
|
+
return chooseStride({start, interval, last, cycle: 24}, {
|
|
1210
1211
|
bare: () => cadence,
|
|
1211
1212
|
offset: () => cadence + ' klo ' + hourElatives[start] + ' alkaen',
|
|
1212
1213
|
bounded: () => cadence + ' ' +
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// French dialect style tables. Dialect names are language-scoped; the default
|
|
2
|
+
// `fr` style is anchored to the fr-FR norm (Imprimerie nationale / Académie
|
|
3
|
+
// française, plus cronstrue `fr`); see notes.md. Custom objects merge over the
|
|
4
|
+
// `fr` defaults. fr-CA is a future dialect axis (notes.md §"Dialect axis"); no
|
|
5
|
+
// regional dialect ships yet.
|
|
6
|
+
import type {Cronli5Options} from '../../types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* French's own resolved style shape: a separator and the spacing of the `h`
|
|
10
|
+
* clock mark. fr is 24-hour only, so there is no `ampm`/`meridiem` axis.
|
|
11
|
+
*/
|
|
12
|
+
export interface FrenchStyle {
|
|
13
|
+
// The mark between hour and minute on the default clock. fr-FR writes the
|
|
14
|
+
// typographic "h" ("9 h 30"); a custom style can opt into a colon or other
|
|
15
|
+
// separator ("9:30"), which replaces the spaced "h" form entirely.
|
|
16
|
+
sep: string;
|
|
17
|
+
// When `sep` is the default "h", whether to drop the surrounding spaces. The
|
|
18
|
+
// spaced "9 h 30" is the ratified fr-FR default; the unspaced "9h30" is the
|
|
19
|
+
// opt-in casual register (notes.md §Dialect axis).
|
|
20
|
+
unspaced: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The fr-FR default: the spaced "h" clock mark.
|
|
24
|
+
const fr: FrenchStyle = {
|
|
25
|
+
sep: 'h',
|
|
26
|
+
unspaced: false
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// One `fr` table today = fr-FR. fr-CA is a future dialect axis (notes.md);
|
|
30
|
+
// it would clear its own native panel before shipping, so it is not declared
|
|
31
|
+
// here yet.
|
|
32
|
+
const dialects: {[name: string]: FrenchStyle} = {
|
|
33
|
+
fr,
|
|
34
|
+
// France is the default; named explicitly so it is a recognized choice and
|
|
35
|
+
// has a home if it ever diverges.
|
|
36
|
+
'fr-FR': fr
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Resolve the `dialect` option to a style table.
|
|
40
|
+
function resolveDialect(dialect: Cronli5Options['dialect']): FrenchStyle {
|
|
41
|
+
if (typeof dialect === 'object' && dialect !== null) {
|
|
42
|
+
return {...dialects.fr, ...dialect};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A string dialect indexes the table; unknown names fall back to `fr`.
|
|
46
|
+
return dialects[dialect as string] || dialects.fr;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {resolveDialect};
|