cronli5 0.7.2 → 0.8.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/CHANGELOG.md +57 -0
- package/README.md +30 -13
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +67 -7
- package/dist/cronli5.js +67 -7
- package/dist/lang/de.cjs +10 -9
- package/dist/lang/de.js +10 -9
- package/dist/lang/en.cjs +9 -4
- package/dist/lang/en.js +9 -4
- package/dist/lang/es.cjs +7 -3
- package/dist/lang/es.js +7 -3
- package/dist/lang/fi.cjs +11 -6
- package/dist/lang/fi.js +11 -6
- package/dist/lang/fr.cjs +7 -3
- package/dist/lang/fr.js +7 -3
- package/dist/lang/pt.cjs +7 -3
- package/dist/lang/pt.js +7 -3
- package/dist/lang/zh.cjs +29 -8
- package/dist/lang/zh.js +29 -8
- package/package.json +1 -1
- 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 +41 -30
- package/src/lang/en/index.ts +25 -7
- package/src/lang/es/index.ts +23 -7
- package/src/lang/fi/index.ts +36 -18
- package/src/lang/fr/index.ts +23 -7
- package/src/lang/pt/index.ts +23 -6
- package/src/lang/zh/index.ts +81 -12
- package/src/types.ts +44 -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/types.d.ts +39 -0
package/src/lang/zh/index.ts
CHANGED
|
@@ -327,6 +327,16 @@ function renderMinutePast(schedule: Schedule): string {
|
|
|
327
327
|
return minuteHourClause(schedule);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
// Strip the generic "每小时" (every-hour) anchor that leads a minute clause.
|
|
331
|
+
// Under an hour STEP the hour cadence is the sole hour authority, so the minute
|
|
332
|
+
// clause must not also assert "每小时" — alongside a stepped hour ("每4小时…每小
|
|
333
|
+
// 时…") it reads as a conflicting every-hour scope. An hour WINDOW and an
|
|
334
|
+
// unrestricted hour keep "每小时" (the window already names the hours; an open
|
|
335
|
+
// hour has no other hour statement).
|
|
336
|
+
function withoutHourAnchor(clause: string): string {
|
|
337
|
+
return clause.replace(/^每小时/, '');
|
|
338
|
+
}
|
|
339
|
+
|
|
330
340
|
// One hour segment as clock words by its form: a range is a span ("9点至20点"),
|
|
331
341
|
// a single is one clock word ("22点"), a step keeps its fires enumerated as
|
|
332
342
|
// clock words ("9点、11点、13点"). A range stated as a list element should read
|
|
@@ -383,7 +393,14 @@ function renderMinuteFrequency(schedule: Schedule, plan: PlanNode): string {
|
|
|
383
393
|
const hourCad = unevenHourCadence(schedule);
|
|
384
394
|
|
|
385
395
|
if (hourCad !== null) {
|
|
386
|
-
|
|
396
|
+
// An hour STEP is the sole hour authority, so an offset minute cadence
|
|
397
|
+
// drops its leading "每小时" ("每4小时从5分起每10分钟"); a discrete hour
|
|
398
|
+
// list (during) keeps it. Only the step path reaches a non-null cadence
|
|
399
|
+
// here — an irregular list falls through to the enumerated frame below.
|
|
400
|
+
const minuteBase = hours.kind === 'step' ?
|
|
401
|
+
withoutHourAnchor(base) : base;
|
|
402
|
+
|
|
403
|
+
return hourCad + (hourCad.indexOf('至') === -1 ? '' : ',') + minuteBase;
|
|
387
404
|
}
|
|
388
405
|
}
|
|
389
406
|
|
|
@@ -445,15 +462,17 @@ function renderMinuteSpanAcrossHourStep(
|
|
|
445
462
|
const {form} = plan as Extract<PlanNode, {kind: 'minuteSpanAcrossHourStep'}>;
|
|
446
463
|
|
|
447
464
|
// A minute list reads as the hour cadence plus the minute list ("每2小时,
|
|
448
|
-
//
|
|
449
|
-
//
|
|
465
|
+
// 0、25、50分"; offset "从1点起每2小时,5分和30分"), the same compaction the
|
|
466
|
+
// wildcard/range minute already uses, rather than the enumerated hours. The
|
|
467
|
+
// hour cadence scopes the hours, so the minute clause drops its "每小时".
|
|
450
468
|
if (form === 'list') {
|
|
451
|
-
return hourCadencePhrase(schedule) + ',' +
|
|
469
|
+
return hourCadencePhrase(schedule) + ',' +
|
|
470
|
+
withoutHourAnchor(renderMinutePast(schedule));
|
|
452
471
|
}
|
|
453
472
|
|
|
454
473
|
const minuteTail = form === 'wildcard' ?
|
|
455
474
|
'每分钟' :
|
|
456
|
-
minuteHourClause(schedule) + ',每分钟';
|
|
475
|
+
withoutHourAnchor(minuteHourClause(schedule)) + ',每分钟';
|
|
457
476
|
|
|
458
477
|
// An offset or non-tiling stride (2/6 fires at 2,8,14,20) reads as its
|
|
459
478
|
// cadence ("从2点起每6小时"). A wildcard minute hangs off it with a comma; a
|
|
@@ -518,9 +537,13 @@ function renderCompactClockTimes(schedule: Schedule, plan: PlanNode): string {
|
|
|
518
537
|
if (!compact.fold) {
|
|
519
538
|
const hourCad = unevenHourCadence(schedule);
|
|
520
539
|
|
|
540
|
+
// A bounded/uneven hour step leads as the cadence and is the sole hour
|
|
541
|
+
// authority, so the minute clause drops its generic "每小时" every-hour
|
|
542
|
+
// scope; an enumerated hour list (hourCad null) names specific hours and
|
|
543
|
+
// keeps the anchor.
|
|
521
544
|
return hourCad === null ?
|
|
522
545
|
minuteHourClause(schedule) + ',在' + hourList(schedule) + tail :
|
|
523
|
-
hourCad + ',' + minuteHourClause(schedule) + tail;
|
|
546
|
+
hourCad + ',' + withoutHourAnchor(minuteHourClause(schedule)) + tail;
|
|
524
547
|
}
|
|
525
548
|
|
|
526
549
|
// A single pinned minute past 0 leads with its clause; a pinned 0 folds into
|
|
@@ -870,6 +893,15 @@ function composeSecondsOnHour(
|
|
|
870
893
|
return composeMinuteZeroClocks(schedule, sec);
|
|
871
894
|
}
|
|
872
895
|
|
|
896
|
+
// A single fixed (non-zero) minute under enumerated clock times fuses the
|
|
897
|
+
// seconds onto the composed clock time the same way ("0点2分的每一秒").
|
|
898
|
+
const fusedSingleMinute =
|
|
899
|
+
composeSingleMinuteClocks(schedule, rest, sec, opts);
|
|
900
|
+
|
|
901
|
+
if (fusedSingleMinute !== null) {
|
|
902
|
+
return fusedSingleMinute;
|
|
903
|
+
}
|
|
904
|
+
|
|
873
905
|
const restText = render(schedule, rest, opts);
|
|
874
906
|
const secTail = clockRestCarriesSecond(rest) ? '' : sec;
|
|
875
907
|
|
|
@@ -886,6 +918,29 @@ function composeSecondsOnHour(
|
|
|
886
918
|
return restText + secTail;
|
|
887
919
|
}
|
|
888
920
|
|
|
921
|
+
// A single fixed (non-zero) minute under enumerated clock times: each clock
|
|
922
|
+
// point already names the minute ("0点2分", "9点5分和17点5分"), so bind the
|
|
923
|
+
// seconds to it with "的" — the same fusion the minute-0 ("0分的每一秒") and
|
|
924
|
+
// minute-step ("5、20…分的每一秒") cases use — rather than leaving a bare
|
|
925
|
+
// trailing "每秒" that floats as a second, unlinked adverbial. A single second
|
|
926
|
+
// already folded into each clock time ("9点5分30秒") is not re-appended. The
|
|
927
|
+
// compactClockTimes window form states its minute separately ("每小时5分") and
|
|
928
|
+
// keeps its own seconds clause, so it does not qualify (returns null). minute 0
|
|
929
|
+
// is handled by composeMinuteZeroClocks before this point.
|
|
930
|
+
function composeSingleMinuteClocks(
|
|
931
|
+
schedule: Schedule, rest: PlanNode, sec: string, opts: Opts
|
|
932
|
+
): string | null {
|
|
933
|
+
if (rest.kind !== 'clockTimes' || schedule.shapes.minute !== 'single' ||
|
|
934
|
+
clockRestCarriesSecond(rest)) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const core =
|
|
939
|
+
render(schedule, rest, opts) + minuteZeroSecondTail(schedule, sec);
|
|
940
|
+
|
|
941
|
+
return isDaily(schedule) ? '每天' + core : core;
|
|
942
|
+
}
|
|
943
|
+
|
|
889
944
|
// A minute pinned to 0 under specific clock hours (not a compacted cadence): a
|
|
890
945
|
// bare clock word ("9点") would hide the :00 and leave the second dangling
|
|
891
946
|
// ("…9点每秒"), reading as the whole hour. Fuse the seconds with the explicit
|
|
@@ -907,14 +962,24 @@ function composeMinuteZeroClocks(schedule: Schedule, sec: string): string {
|
|
|
907
962
|
// midnight (凌晨0点) and other hours still need it to pin the minute.
|
|
908
963
|
return hour === 12 ? '正午' : hourWord(hour) + '0分';
|
|
909
964
|
});
|
|
910
|
-
|
|
911
|
-
|
|
965
|
+
const core = joinAnd(clocks) + minuteZeroSecondTail(schedule, sec);
|
|
966
|
+
|
|
967
|
+
return isDaily(schedule) ? '每天' + core : core;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// The "的"-fused second tail for a clock time that already names a single pinned
|
|
971
|
+
// minute ("…的每一秒" for a wildcard second, else "…的" + the second's clause).
|
|
972
|
+
// A pinned minute makes the seconds' own "每分钟" anchor misleading (it is a
|
|
973
|
+
// single minute, not every minute), so a stride here drops it.
|
|
974
|
+
function minuteZeroSecondTail(schedule: Schedule, sec: string): string {
|
|
975
|
+
if (sec === '每秒') {
|
|
976
|
+
return '的每一秒';
|
|
977
|
+
}
|
|
978
|
+
|
|
912
979
|
const nested =
|
|
913
980
|
strideFromSegments(segmentsOf(schedule, 'second'), '秒', '秒', '');
|
|
914
|
-
const tail = sec === '每秒' ? '的每一秒' : '的' + (nested ?? sec);
|
|
915
|
-
const core = joinAnd(clocks) + tail;
|
|
916
981
|
|
|
917
|
-
return
|
|
982
|
+
return '的' + (nested ?? sec);
|
|
918
983
|
}
|
|
919
984
|
|
|
920
985
|
// Whether the hour field is a range — or a list whose segments include a
|
|
@@ -1010,7 +1075,10 @@ function composeSecondsListed(schedule: Schedule): string {
|
|
|
1010
1075
|
const hourCad = unevenHourCadence(schedule);
|
|
1011
1076
|
|
|
1012
1077
|
if (hourCad !== null) {
|
|
1013
|
-
|
|
1078
|
+
// An hour STEP cadence is the sole hour authority, so the minute clause
|
|
1079
|
+
// drops its "每小时" ("每2小时,0至30分,每秒"); a discrete hour list keeps it
|
|
1080
|
+
// (it falls through to the hourFrame branch below with a null cadence).
|
|
1081
|
+
return hourCad + ',' + withoutHourAnchor(minutes) + ',' + sec;
|
|
1014
1082
|
}
|
|
1015
1083
|
|
|
1016
1084
|
return hourFrame(schedule) + minutes + ',' + sec;
|
|
@@ -1503,6 +1571,7 @@ function normalizeOptions(options?: Cronli5Options): Opts {
|
|
|
1503
1571
|
return {
|
|
1504
1572
|
ampm: typeof options.ampm === 'boolean' ? options.ampm : false,
|
|
1505
1573
|
lenient: !!options.lenient,
|
|
1574
|
+
quartz: !!options.quartz,
|
|
1506
1575
|
seconds: !!options.seconds,
|
|
1507
1576
|
short: !!options.short,
|
|
1508
1577
|
style,
|
package/src/types.ts
CHANGED
|
@@ -96,6 +96,20 @@ export interface Cronli5Options {
|
|
|
96
96
|
*/
|
|
97
97
|
lenient?: boolean;
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Read the pattern with Quartz semantics. Quartz numbers the day-of-week
|
|
101
|
+
* **1 = Sunday, 2 = Monday, … 7 = Saturday** (standard cron uses
|
|
102
|
+
* 0/7 = Sunday, 1 = Monday), and **requires** exactly one of day-of-month or
|
|
103
|
+
* day-of-week to be `?` ("no specific value"). Off by default; with it off,
|
|
104
|
+
* `?` is rejected (rather than silently mis-read as standard cron), since a
|
|
105
|
+
* `?` is the unambiguous mark of a Quartz pattern. The Quartz numbering also
|
|
106
|
+
* applies inside the day-of-week operators (`6L`, `2#2`) and the weekday `L`
|
|
107
|
+
* alias (Saturday). Day names (`MON`, `SUN`) and the day-of-month, month,
|
|
108
|
+
* hour, and minute fields are unaffected. Composable with `seconds`/`years`.
|
|
109
|
+
* Defaults to `false`.
|
|
110
|
+
*/
|
|
111
|
+
quartz?: boolean;
|
|
112
|
+
|
|
99
113
|
/**
|
|
100
114
|
* Return a complete standalone sentence (`'Runs every day at midnight.'`)
|
|
101
115
|
* instead of the embeddable fragment (`'every day at midnight'`). Each
|
|
@@ -122,6 +136,36 @@ export interface Cronli5Options {
|
|
|
122
136
|
years?: boolean;
|
|
123
137
|
}
|
|
124
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The callable default export: a function that turns a cron pattern into a
|
|
141
|
+
* description, carrying two named convenience methods that are sugar over the
|
|
142
|
+
* `sentence` option.
|
|
143
|
+
*
|
|
144
|
+
* There is deliberately **no** `toString` method: it would shadow
|
|
145
|
+
* `Function.prototype.toString`, which the runtime calls arg-less for
|
|
146
|
+
* `String(cronli5)`, template-literal coercion, and `console`/debug output.
|
|
147
|
+
* The named methods avoid that collision.
|
|
148
|
+
*/
|
|
149
|
+
export interface Cronli5 {
|
|
150
|
+
|
|
151
|
+
/** Describe a cron pattern (lowercase embeddable fragment by default). */
|
|
152
|
+
(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Describe a cron pattern as a capitalized standalone sentence
|
|
156
|
+
* (`'Runs every day at midnight.'`). Sugar for
|
|
157
|
+
* `{...options, sentence: true}`.
|
|
158
|
+
*/
|
|
159
|
+
sentence(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Describe a cron pattern as a lowercase embeddable fragment
|
|
163
|
+
* (`'every day at midnight'`) — the default form. Sugar for
|
|
164
|
+
* `{...options, sentence: false}`.
|
|
165
|
+
*/
|
|
166
|
+
fragment(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
167
|
+
}
|
|
168
|
+
|
|
125
169
|
/**
|
|
126
170
|
* Object form of a cron pattern. Fields may be strings or numbers; at least
|
|
127
171
|
* one of `second`, `minute`, or `hour` is required.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CronLike } from './specs.js';
|
|
2
|
+
declare const quartzTokenMessage = "`?` is a Quartz token \u2014 pass { quartz: true } to enable Quartz semantics.";
|
|
3
|
+
declare function applyQuartz(cronPattern: CronLike, quartz: boolean): void;
|
|
4
|
+
export { applyQuartz, quartzTokenMessage };
|
package/types/core/schedule.d.ts
CHANGED
package/types/cronli5.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license MIT, Copyright (c) 2026 Andrew Brož
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
declare
|
|
6
|
-
export default
|
|
7
|
-
export type { Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern, CronPatternObject } from './types.js';
|
|
4
|
+
import type { Cronli5 } from './types.js';
|
|
5
|
+
declare const callable: Cronli5;
|
|
6
|
+
export default callable;
|
|
7
|
+
export type { Cronli5, Cronli5Dialect, Cronli5Language, Cronli5Options, CronPattern, CronPatternObject } from './types.js';
|
package/types/types.d.ts
CHANGED
|
@@ -71,6 +71,19 @@ export interface Cronli5Options {
|
|
|
71
71
|
* Defaults to `false`.
|
|
72
72
|
*/
|
|
73
73
|
lenient?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Read the pattern with Quartz semantics. Quartz numbers the day-of-week
|
|
76
|
+
* **1 = Sunday, 2 = Monday, … 7 = Saturday** (standard cron uses
|
|
77
|
+
* 0/7 = Sunday, 1 = Monday), and **requires** exactly one of day-of-month or
|
|
78
|
+
* day-of-week to be `?` ("no specific value"). Off by default; with it off,
|
|
79
|
+
* `?` is rejected (rather than silently mis-read as standard cron), since a
|
|
80
|
+
* `?` is the unambiguous mark of a Quartz pattern. The Quartz numbering also
|
|
81
|
+
* applies inside the day-of-week operators (`6L`, `2#2`) and the weekday `L`
|
|
82
|
+
* alias (Saturday). Day names (`MON`, `SUN`) and the day-of-month, month,
|
|
83
|
+
* hour, and minute fields are unaffected. Composable with `seconds`/`years`.
|
|
84
|
+
* Defaults to `false`.
|
|
85
|
+
*/
|
|
86
|
+
quartz?: boolean;
|
|
74
87
|
/**
|
|
75
88
|
* Return a complete standalone sentence (`'Runs every day at midnight.'`)
|
|
76
89
|
* instead of the embeddable fragment (`'every day at midnight'`). Each
|
|
@@ -93,6 +106,32 @@ export interface Cronli5Options {
|
|
|
93
106
|
*/
|
|
94
107
|
years?: boolean;
|
|
95
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The callable default export: a function that turns a cron pattern into a
|
|
111
|
+
* description, carrying two named convenience methods that are sugar over the
|
|
112
|
+
* `sentence` option.
|
|
113
|
+
*
|
|
114
|
+
* There is deliberately **no** `toString` method: it would shadow
|
|
115
|
+
* `Function.prototype.toString`, which the runtime calls arg-less for
|
|
116
|
+
* `String(cronli5)`, template-literal coercion, and `console`/debug output.
|
|
117
|
+
* The named methods avoid that collision.
|
|
118
|
+
*/
|
|
119
|
+
export interface Cronli5 {
|
|
120
|
+
/** Describe a cron pattern (lowercase embeddable fragment by default). */
|
|
121
|
+
(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
122
|
+
/**
|
|
123
|
+
* Describe a cron pattern as a capitalized standalone sentence
|
|
124
|
+
* (`'Runs every day at midnight.'`). Sugar for
|
|
125
|
+
* `{...options, sentence: true}`.
|
|
126
|
+
*/
|
|
127
|
+
sentence(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
128
|
+
/**
|
|
129
|
+
* Describe a cron pattern as a lowercase embeddable fragment
|
|
130
|
+
* (`'every day at midnight'`) — the default form. Sugar for
|
|
131
|
+
* `{...options, sentence: false}`.
|
|
132
|
+
*/
|
|
133
|
+
fragment(cronPattern: CronPattern, options?: Cronli5Options): string;
|
|
134
|
+
}
|
|
96
135
|
/**
|
|
97
136
|
* Object form of a cron pattern. Fields may be strings or numbers; at least
|
|
98
137
|
* one of `second`, `minute`, or `hour` is required.
|