effect 3.21.4 → 3.21.5
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/cjs/Cron.js +96 -52
- package/dist/cjs/Cron.js.map +1 -1
- package/dist/cjs/internal/schedule.js +3 -0
- package/dist/cjs/internal/schedule.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Cron.d.ts +1 -0
- package/dist/dts/Cron.d.ts.map +1 -1
- package/dist/esm/Cron.js +96 -52
- package/dist/esm/Cron.js.map +1 -1
- package/dist/esm/internal/schedule.js +3 -0
- package/dist/esm/internal/schedule.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Cron.ts +107 -58
- package/src/internal/schedule.ts +8 -0
- package/src/internal/version.ts +1 -1
package/src/Cron.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface Cron extends Pipeable, Equal.Equal, Inspectable {
|
|
|
44
44
|
readonly months: ReadonlySet<number>
|
|
45
45
|
readonly weekdays: ReadonlySet<number>
|
|
46
46
|
/** @internal */
|
|
47
|
+
readonly and: boolean
|
|
48
|
+
/** @internal */
|
|
47
49
|
readonly first: {
|
|
48
50
|
readonly second: number
|
|
49
51
|
readonly minute: number
|
|
@@ -89,6 +91,7 @@ const CronProto = {
|
|
|
89
91
|
[Hash.symbol](this: Cron): number {
|
|
90
92
|
return pipe(
|
|
91
93
|
Hash.hash(this.tz),
|
|
94
|
+
Hash.combine(Hash.hash(this.and)),
|
|
92
95
|
Hash.combine(Hash.array(Arr.fromIterable(this.seconds))),
|
|
93
96
|
Hash.combine(Hash.array(Arr.fromIterable(this.minutes))),
|
|
94
97
|
Hash.combine(Hash.array(Arr.fromIterable(this.hours))),
|
|
@@ -142,6 +145,7 @@ export const make = (values: {
|
|
|
142
145
|
readonly days: Iterable<number>
|
|
143
146
|
readonly months: Iterable<number>
|
|
144
147
|
readonly weekdays: Iterable<number>
|
|
148
|
+
readonly and?: boolean | undefined
|
|
145
149
|
readonly tz?: DateTime.TimeZone | undefined
|
|
146
150
|
}): Cron => {
|
|
147
151
|
const o: Mutable<Cron> = Object.create(CronProto)
|
|
@@ -151,6 +155,7 @@ export const make = (values: {
|
|
|
151
155
|
o.days = new Set(Arr.sort(values.days, N.Order))
|
|
152
156
|
o.months = new Set(Arr.sort(values.months, N.Order))
|
|
153
157
|
o.weekdays = new Set(Arr.sort(values.weekdays, N.Order))
|
|
158
|
+
o.and = values.and === true
|
|
154
159
|
o.tz = Option.fromNullable(values.tz)
|
|
155
160
|
|
|
156
161
|
const seconds = Array.from(o.seconds)
|
|
@@ -291,7 +296,7 @@ export const isParseError = (u: unknown): u is ParseError => hasProperty(u, Pars
|
|
|
291
296
|
* @category constructors
|
|
292
297
|
*/
|
|
293
298
|
export const parse = (cron: string, tz?: DateTime.TimeZone | string): Either.Either<Cron, ParseError> => {
|
|
294
|
-
const segments = cron.split(
|
|
299
|
+
const segments = cron.trim().split(/\s+/).filter(String.isNonEmpty)
|
|
295
300
|
if (segments.length !== 5 && segments.length !== 6) {
|
|
296
301
|
return Either.left(
|
|
297
302
|
new ParseError({
|
|
@@ -322,7 +327,18 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Either.Eit
|
|
|
322
327
|
days: parseSegment(days, dayOptions),
|
|
323
328
|
months: parseSegment(months, monthOptions),
|
|
324
329
|
weekdays: parseSegment(weekdays, weekdayOptions)
|
|
325
|
-
}).pipe(Either.map(
|
|
330
|
+
}).pipe(Either.map(({ days, hours, minutes, months, seconds, tz, weekdays }) =>
|
|
331
|
+
make({
|
|
332
|
+
tz,
|
|
333
|
+
seconds: seconds.values,
|
|
334
|
+
minutes: minutes.values,
|
|
335
|
+
hours: hours.values,
|
|
336
|
+
days: days.values,
|
|
337
|
+
months: months.values,
|
|
338
|
+
weekdays: weekdays.values,
|
|
339
|
+
and: (days.wildcard || weekdays.wildcard) && days.values.size !== 0 && weekdays.values.size !== 0
|
|
340
|
+
})
|
|
341
|
+
))
|
|
326
342
|
}
|
|
327
343
|
|
|
328
344
|
/**
|
|
@@ -410,6 +426,11 @@ export const match = (cron: Cron, date: DateTime.DateTime.Input): boolean => {
|
|
|
410
426
|
return true
|
|
411
427
|
}
|
|
412
428
|
|
|
429
|
+
if (cron.and) {
|
|
430
|
+
return (cron.days.size === 0 || cron.days.has(parts.day)) &&
|
|
431
|
+
(cron.weekdays.size === 0 || cron.weekdays.has(parts.weekDay))
|
|
432
|
+
}
|
|
433
|
+
|
|
413
434
|
if (cron.weekdays.size === 0) {
|
|
414
435
|
return cron.days.has(parts.day)
|
|
415
436
|
}
|
|
@@ -553,48 +574,60 @@ const stepCron = (cron: Cron, startFrom: DateTime.DateTime.Input | undefined, di
|
|
|
553
574
|
}
|
|
554
575
|
|
|
555
576
|
if (cron.weekdays.size !== 0 || cron.days.size !== 0) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
? currentWeekday - 7 + boundary.weekday
|
|
565
|
-
: 7 - currentWeekday + boundary.weekday
|
|
566
|
-
} else {
|
|
567
|
-
a = nextWeekday - currentWeekday
|
|
577
|
+
if (cron.and) {
|
|
578
|
+
const matchesDay = cron.days.size === 0 || cron.days.has(current.getUTCDate())
|
|
579
|
+
const matchesWeekday = cron.weekdays.size === 0 || cron.weekdays.has(current.getUTCDay())
|
|
580
|
+
if (!matchesDay || !matchesWeekday) {
|
|
581
|
+
current.setUTCDate(current.getUTCDate() + tick)
|
|
582
|
+
current.setUTCHours(boundary.hour, boundary.minute, boundary.second)
|
|
583
|
+
adjustDst(current)
|
|
584
|
+
continue
|
|
568
585
|
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const prevMonthDays = daysInMonth(
|
|
581
|
-
new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth(), 0))
|
|
582
|
-
)
|
|
583
|
-
b = -(currentDay + (prevMonthDays - boundary.day))
|
|
586
|
+
} else {
|
|
587
|
+
let a: number = prev ? -Infinity : Infinity
|
|
588
|
+
let b: number = prev ? -Infinity : Infinity
|
|
589
|
+
|
|
590
|
+
if (cron.weekdays.size !== 0) {
|
|
591
|
+
const currentWeekday = current.getUTCDay()
|
|
592
|
+
const nextWeekday = table.weekday[currentWeekday]
|
|
593
|
+
if (nextWeekday === undefined) {
|
|
594
|
+
a = prev
|
|
595
|
+
? currentWeekday - 7 + boundary.weekday
|
|
596
|
+
: 7 - currentWeekday + boundary.weekday
|
|
584
597
|
} else {
|
|
598
|
+
a = nextWeekday - currentWeekday
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Only check day-of-month if weekday constraint not already satisfied (they're OR'd)
|
|
603
|
+
if (cron.days.size !== 0 && a !== 0) {
|
|
604
|
+
const currentDay = current.getUTCDate()
|
|
605
|
+
const nextDay = table.day[currentDay]
|
|
606
|
+
if (nextDay === undefined) {
|
|
607
|
+
if (prev) {
|
|
608
|
+
const prevMonthDays = daysInMonth(
|
|
609
|
+
new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth(), 0))
|
|
610
|
+
)
|
|
611
|
+
b = -(currentDay + (prevMonthDays - boundary.day))
|
|
612
|
+
} else {
|
|
613
|
+
b = daysInMonth(current) - currentDay + boundary.day
|
|
614
|
+
}
|
|
615
|
+
} else if (!prev && nextDay > daysInMonth(current)) {
|
|
616
|
+
// The next matching day does not exist in the current month. Setting it
|
|
617
|
+
// directly would overflow and skip earlier matching days next month.
|
|
585
618
|
b = daysInMonth(current) - currentDay + boundary.day
|
|
619
|
+
} else {
|
|
620
|
+
b = nextDay - currentDay
|
|
586
621
|
}
|
|
587
|
-
} else {
|
|
588
|
-
b = nextDay - currentDay
|
|
589
622
|
}
|
|
590
|
-
}
|
|
591
623
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
624
|
+
const addDays = prev ? Math.max(a, b) : Math.min(a, b)
|
|
625
|
+
if (addDays !== 0) {
|
|
626
|
+
current.setUTCDate(current.getUTCDate() + addDays)
|
|
627
|
+
current.setUTCHours(boundary.hour, boundary.minute, boundary.second)
|
|
628
|
+
adjustDst(current)
|
|
629
|
+
continue
|
|
630
|
+
}
|
|
598
631
|
}
|
|
599
632
|
}
|
|
600
633
|
|
|
@@ -602,11 +635,11 @@ const stepCron = (cron: Cron, startFrom: DateTime.DateTime.Input | undefined, di
|
|
|
602
635
|
const currentMonth = current.getUTCMonth() + 1
|
|
603
636
|
const nextMonth = table.month[currentMonth]
|
|
604
637
|
const clampBoundaryDay = (targetMonthIndex: number): number => {
|
|
605
|
-
if (cron.days.size !== 0) {
|
|
638
|
+
if (cron.days.size !== 0 && cron.weekdays.size === 0) {
|
|
606
639
|
return boundary.day
|
|
607
640
|
}
|
|
608
641
|
const maxDayInMonth = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), targetMonthIndex, 1)))
|
|
609
|
-
return
|
|
642
|
+
return prev ? maxDayInMonth : 1
|
|
610
643
|
}
|
|
611
644
|
if (nextMonth === undefined) {
|
|
612
645
|
current.setUTCFullYear(current.getUTCFullYear() + tick)
|
|
@@ -661,6 +694,7 @@ export const sequenceReverse = function*(cron: Cron, startFrom?: DateTime.DateTi
|
|
|
661
694
|
* @since 2.0.0
|
|
662
695
|
*/
|
|
663
696
|
export const Equivalence: equivalence.Equivalence<Cron> = equivalence.make((self, that) =>
|
|
697
|
+
self.and === that.and &&
|
|
664
698
|
restrictionsEquals(self.seconds, that.seconds) &&
|
|
665
699
|
restrictionsEquals(self.minutes, that.minutes) &&
|
|
666
700
|
restrictionsEquals(self.hours, that.hours) &&
|
|
@@ -700,6 +734,12 @@ interface SegmentOptions {
|
|
|
700
734
|
min: number
|
|
701
735
|
max: number
|
|
702
736
|
aliases?: Record<string, number> | undefined
|
|
737
|
+
normalize?: ((value: number) => number) | undefined
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
interface ParsedSegment {
|
|
741
|
+
readonly values: Set<number>
|
|
742
|
+
readonly wildcard: boolean
|
|
703
743
|
}
|
|
704
744
|
|
|
705
745
|
const secondOptions: SegmentOptions = {
|
|
@@ -743,7 +783,8 @@ const monthOptions: SegmentOptions = {
|
|
|
743
783
|
|
|
744
784
|
const weekdayOptions: SegmentOptions = {
|
|
745
785
|
min: 0,
|
|
746
|
-
max:
|
|
786
|
+
max: 7,
|
|
787
|
+
normalize: (value) => value === 7 ? 0 : value,
|
|
747
788
|
aliases: {
|
|
748
789
|
sun: 0,
|
|
749
790
|
mon: 1,
|
|
@@ -758,17 +799,21 @@ const weekdayOptions: SegmentOptions = {
|
|
|
758
799
|
const parseSegment = (
|
|
759
800
|
input: string,
|
|
760
801
|
options: SegmentOptions
|
|
761
|
-
): Either.Either<
|
|
762
|
-
const capacity = options.max - options.min + 1
|
|
802
|
+
): Either.Either<ParsedSegment, ParseError> => {
|
|
763
803
|
const values = new Set<number>()
|
|
764
804
|
const fields = input.split(",")
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
805
|
+
const first = splitStep(fields[0]!)
|
|
806
|
+
const wildcard = first[0] === "*"
|
|
807
|
+
const normalize = options.normalize ?? ((value: number) => value)
|
|
808
|
+
const add = wildcard && (first[1] === undefined || first[1] === 1)
|
|
809
|
+
? constVoid
|
|
810
|
+
: (value: number) => {
|
|
811
|
+
values.add(normalize(value))
|
|
770
812
|
}
|
|
771
813
|
|
|
814
|
+
for (let index = 0; index < fields.length; index++) {
|
|
815
|
+
const field = fields[index]!
|
|
816
|
+
const [raw, step] = index === 0 ? first : splitStep(field)
|
|
772
817
|
if (step !== undefined) {
|
|
773
818
|
if (!Number.isInteger(step)) {
|
|
774
819
|
return Either.left(new ParseError({ message: `Expected step value to be a positive integer`, input }))
|
|
@@ -782,8 +827,11 @@ const parseSegment = (
|
|
|
782
827
|
}
|
|
783
828
|
|
|
784
829
|
if (raw === "*") {
|
|
830
|
+
if (index === 0 && (step === undefined || step === 1)) {
|
|
831
|
+
continue
|
|
832
|
+
}
|
|
785
833
|
for (let i = options.min; i <= options.max; i += step ?? 1) {
|
|
786
|
-
|
|
834
|
+
add(i)
|
|
787
835
|
}
|
|
788
836
|
} else {
|
|
789
837
|
const [left, right] = splitRange(raw, options.aliases)
|
|
@@ -797,7 +845,9 @@ const parseSegment = (
|
|
|
797
845
|
}
|
|
798
846
|
|
|
799
847
|
if (right === undefined) {
|
|
800
|
-
|
|
848
|
+
for (let i = left; i <= (step === undefined ? left : options.max); i += step ?? 1) {
|
|
849
|
+
add(i)
|
|
850
|
+
}
|
|
801
851
|
} else {
|
|
802
852
|
if (!Number.isInteger(right)) {
|
|
803
853
|
return Either.left(new ParseError({ message: `Expected a positive integer`, input }))
|
|
@@ -812,23 +862,20 @@ const parseSegment = (
|
|
|
812
862
|
}
|
|
813
863
|
|
|
814
864
|
for (let i = left; i <= right; i += step ?? 1) {
|
|
815
|
-
|
|
865
|
+
add(i)
|
|
816
866
|
}
|
|
817
867
|
}
|
|
818
868
|
}
|
|
819
|
-
|
|
820
|
-
if (values.size >= capacity) {
|
|
821
|
-
return Either.right(new Set())
|
|
822
|
-
}
|
|
823
869
|
}
|
|
824
870
|
|
|
825
|
-
return Either.right(values)
|
|
871
|
+
return Either.right({ values, wildcard })
|
|
826
872
|
}
|
|
827
873
|
|
|
828
874
|
const splitStep = (input: string): [string, number | undefined] => {
|
|
829
875
|
const seperator = input.indexOf("/")
|
|
830
876
|
if (seperator !== -1) {
|
|
831
|
-
|
|
877
|
+
const step = input.slice(seperator + 1)
|
|
878
|
+
return [input.slice(0, seperator), decimalRegex.test(step) ? Number(step) : NaN]
|
|
832
879
|
}
|
|
833
880
|
|
|
834
881
|
return [input, undefined]
|
|
@@ -844,5 +891,7 @@ const splitRange = (input: string, aliases?: Record<string, number>): [number, n
|
|
|
844
891
|
}
|
|
845
892
|
|
|
846
893
|
function aliasOrValue(field: string, aliases?: Record<string, number>): number {
|
|
847
|
-
return aliases?.[field.toLocaleLowerCase()] ?? Number(field)
|
|
894
|
+
return aliases?.[field.toLocaleLowerCase()] ?? (decimalRegex.test(field) ? Number(field) : NaN)
|
|
848
895
|
}
|
|
896
|
+
|
|
897
|
+
const decimalRegex = /^\d+$/
|
package/src/internal/schedule.ts
CHANGED
|
@@ -482,6 +482,14 @@ export const cron: {
|
|
|
482
482
|
return makeWithState<[boolean, [number, number, number]], unknown, [number, number]>(
|
|
483
483
|
[true, [Number.MIN_SAFE_INTEGER, 0, 0]],
|
|
484
484
|
(now, _, [initial, previous]) => {
|
|
485
|
+
if (now === Number.POSITIVE_INFINITY) {
|
|
486
|
+
return core.succeed([
|
|
487
|
+
[false, previous],
|
|
488
|
+
[previous[1], previous[2]],
|
|
489
|
+
ScheduleDecision.done
|
|
490
|
+
])
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
if (now < previous[0]) {
|
|
486
494
|
return core.succeed([
|
|
487
495
|
[false, previous],
|
package/src/internal/version.ts
CHANGED