cronli5 0.3.4 → 0.7.2

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/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 tiles = cycle % interval === 0;
65
- if (start === 0 && tiles) {
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 && tiles) {
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,30 @@ 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,
907
948
  seconds: !!options.seconds,
908
949
  short: !!options.short,
909
- style: resolveDialect(options.dialect),
950
+ style,
910
951
  years: !!options.years
911
952
  };
912
953
  }
913
954
  var zh2 = {
914
955
  describe,
915
- fallback: "\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F",
956
+ // `reboot`/`fallback` are contract-fixed strings the core reads without
957
+ // `opts`; getters honor the variant `options()` latched, keeping the shared
958
+ // Language contract unchanged while the Traditional dialect still applies.
959
+ get fallback() {
960
+ return toVariant("\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F", activeVariant);
961
+ },
916
962
  options: normalizeOptions,
917
- reboot: "\u7CFB\u7EDF\u542F\u52A8\u65F6",
918
- sentence: (description) => "\u8FD0\u884C\u65F6\u95F4\uFF1A" + description + "\u3002"
963
+ get reboot() {
964
+ return toVariant("\u7CFB\u7EDF\u542F\u52A8\u65F6", activeVariant);
965
+ },
966
+ sentence: (description) => toVariant("\u8FD0\u884C\u65F6\u95F4\uFF1A", activeVariant) + description + "\u3002"
919
967
  };
920
968
  var index_default = zh2;
921
969
  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 tiles = cycle % interval === 0;
39
- if (start === 0 && tiles) {
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 && tiles) {
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,30 @@ 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,
881
922
  seconds: !!options.seconds,
882
923
  short: !!options.short,
883
- style: resolveDialect(options.dialect),
924
+ style,
884
925
  years: !!options.years
885
926
  };
886
927
  }
887
928
  var zh2 = {
888
929
  describe,
889
- fallback: "\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F",
930
+ // `reboot`/`fallback` are contract-fixed strings the core reads without
931
+ // `opts`; getters honor the variant `options()` latched, keeping the shared
932
+ // Language contract unchanged while the Traditional dialect still applies.
933
+ get fallback() {
934
+ return toVariant("\u65E0\u6CD5\u8BC6\u522B\u7684 cron \u8868\u8FBE\u5F0F", activeVariant);
935
+ },
890
936
  options: normalizeOptions,
891
- reboot: "\u7CFB\u7EDF\u542F\u52A8\u65F6",
892
- sentence: (description) => "\u8FD0\u884C\u65F6\u95F4\uFF1A" + description + "\u3002"
937
+ get reboot() {
938
+ return toVariant("\u7CFB\u7EDF\u542F\u52A8\u65F6", activeVariant);
939
+ },
940
+ sentence: (description) => toVariant("\u8FD0\u884C\u65F6\u95F4\uFF1A", activeVariant) + description + "\u3002"
893
941
  };
894
942
  var index_default = zh2;
895
943
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronli5",
3
- "version": "0.3.4",
3
+ "version": "0.7.2",
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": ">=18"
89
+ "node": ">=20"
79
90
  },
80
91
  "keywords": [
81
92
  "cron",
@@ -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
- // is the bare cadence; a uniform offset (start within the first interval, the
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, or an
102
- // interval that does not tile the cycle) pins both endpoints so the bounded,
103
- // non-wrapping set reads unambiguously. This is the one decision tree every
104
- // renderer's `renderStride`/`hourStrideCadence` shared (cycle 60 for
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
- const tiles = cycle % interval === 0;
113
-
114
- if (start === 0 && tiles) {
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 && tiles) {
131
+ if (start < interval && open) {
119
132
  return parts.offset();
120
133
  }
121
134
 
@@ -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'
@@ -1313,7 +1313,7 @@ function renderStride(stride: Stride, opts: NormalizedOptions): string {
1313
1313
  const {interval, start, last, cycle, unit, anchor} = stride;
1314
1314
  const cadence = 'every ' + getNumber(interval, opts) + ' ' + unit + 's';
1315
1315
 
1316
- return chooseStride({start, interval, cycle}, {
1316
+ return chooseStride({start, interval, last, cycle}, {
1317
1317
  bare: () => cadence,
1318
1318
 
1319
1319
  // A clean wrap from a non-zero offset: name the start, no endpoint.
@@ -1419,7 +1419,7 @@ function hourStrideCadence(stride: {start: number; interval: number;
1419
1419
  const {start, interval, last} = stride;
1420
1420
  const cadence = 'every ' + getNumber(interval, opts) + ' hours';
1421
1421
 
1422
- return chooseStride({start, interval, cycle: 24}, {
1422
+ return chooseStride({start, interval, last, cycle: 24}, {
1423
1423
  bare: () => cadence,
1424
1424
  offset: () => cadence + ' from ' + getTime({hour: start, minute: 0}, opts),
1425
1425
  bounded: () =>
@@ -1326,7 +1326,7 @@ function renderStride(stride: Stride, opts: Opts): string {
1326
1326
  // the cadence keeps its endpoints but drops the "de cada <anchor>" tail.
1327
1327
  const tail = anchor ? ' de cada ' + anchor : '';
1328
1328
 
1329
- return chooseStride({start, interval, cycle}, {
1329
+ return chooseStride({start, interval, last, cycle}, {
1330
1330
  bare: () => cadence,
1331
1331
  offset: () => cadence + ' a partir del ' + unit + ' ' + start + tail,
1332
1332
  bounded: () =>
@@ -1429,7 +1429,7 @@ function hourStrideCadence(
1429
1429
  const {start, interval, last} = stride;
1430
1430
  const cadence = 'cada ' + numero(interval, opts) + ' horas';
1431
1431
 
1432
- return chooseStride({start, interval, cycle: 24}, {
1432
+ return chooseStride({start, interval, last, cycle: 24}, {
1433
1433
  bare: () => cadence,
1434
1434
  offset: () => cadence + ' a partir de ' + timePhrase(start, 0, null, opts),
1435
1435
  bounded: () => cadence + ' de ' + timePhrase(start, 0, null, opts) + ' a ' +
@@ -1108,7 +1108,7 @@ function renderStride(stride: Stride, opts: NormalizedOptions): string {
1108
1108
  const {interval, start, last, cycle, unit} = stride;
1109
1109
  const cadence = genitive(interval, opts) + ' ' + unit.gen + ' välein';
1110
1110
 
1111
- return chooseStride({start, interval, cycle}, {
1111
+ return chooseStride({start, interval, last, cycle}, {
1112
1112
  bare: () => cadence,
1113
1113
  offset: () =>
1114
1114
  cadence + ' ' + unit.anchor + ' ' + unit.ela + ' ' + start + ' alkaen',
@@ -1206,7 +1206,7 @@ function hourStrideCadence(
1206
1206
  const {start, interval, last} = stride;
1207
1207
  const cadence = genitive(interval, opts) + ' tunnin välein';
1208
1208
 
1209
- return chooseStride({start, interval, cycle: 24}, {
1209
+ return chooseStride({start, interval, last, cycle: 24}, {
1210
1210
  bare: () => cadence,
1211
1211
  offset: () => cadence + ' klo ' + hourElatives[start] + ' alkaen',
1212
1212
  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};