mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 15:12:48 +00:00
Run cron schedules when either restricted day field matches (#8611)
* fix(schedules): match either restricted cron day field * refactor(schedules): name the combined day match conditions Keep wildcard days conjunctive so weekday-only and date-only schedules retain their meaning, while restricted day fields follow cron's union rule. Preserve lexical wildcard identity rather than inferring it from allowed values, since an explicit full range is still restricted. Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com> --------- Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ interface CronFieldBounds {
|
||||
}
|
||||
|
||||
export interface CronFieldMatcher {
|
||||
isWildcard: boolean;
|
||||
matches(value: number): boolean;
|
||||
}
|
||||
|
||||
@@ -101,6 +102,7 @@ function parseField(source: string, bounds: CronFieldBounds): CronFieldMatcher {
|
||||
}
|
||||
|
||||
return {
|
||||
isWildcard: trimmed.startsWith("*"),
|
||||
matches(value: number): boolean {
|
||||
return allowed.has(value);
|
||||
},
|
||||
|
||||
@@ -20,6 +20,37 @@ describe("schedule cron cadence", () => {
|
||||
expect(next.toISOString()).toBe("2026-01-05T09:15:00.000Z");
|
||||
});
|
||||
|
||||
test("matches either restricted day field", () => {
|
||||
const next = computeNextRunAt(
|
||||
{ type: "cron", expression: "0 9 1 * 1" },
|
||||
new Date("2026-10-17T00:00:00.000Z"),
|
||||
);
|
||||
|
||||
expect(next.toISOString()).toBe("2026-10-19T09:00:00.000Z");
|
||||
});
|
||||
|
||||
test.each([
|
||||
["0 9 1 * 1", "2026-10-30T10:00:00.000Z", "2026-11-01T09:00:00.000Z"],
|
||||
["0 9 1 * *", "2026-10-17T00:00:00.000Z", "2026-11-01T09:00:00.000Z"],
|
||||
["0 9 * * 1", "2026-10-17T00:00:00.000Z", "2026-10-19T09:00:00.000Z"],
|
||||
["0 9 */2 * 1", "2026-10-19T09:00:00.000Z", "2026-11-09T09:00:00.000Z"],
|
||||
["0 9 1-31 * 1", "2026-10-17T00:00:00.000Z", "2026-10-17T09:00:00.000Z"],
|
||||
["0 9 1,15 * 5", "2026-10-17T00:00:00.000Z", "2026-10-23T09:00:00.000Z"],
|
||||
])("honors day-field semantics for %s", (expression, after, expected) => {
|
||||
const next = computeNextRunAt({ type: "cron", expression }, new Date(after));
|
||||
|
||||
expect(next.toISOString()).toBe(expected);
|
||||
});
|
||||
|
||||
test("matches either restricted day field in the schedule's time zone", () => {
|
||||
const next = computeNextRunAt(
|
||||
{ type: "cron", expression: "0 9 1 * 1", timezone: "America/New_York" },
|
||||
new Date("2026-10-17T00:00:00.000Z"),
|
||||
);
|
||||
|
||||
expect(next.toISOString()).toBe("2026-10-19T13:00:00.000Z");
|
||||
});
|
||||
|
||||
test("computes timezone cron matches at the requested wall-clock time", () => {
|
||||
const winter = computeNextRunAt(
|
||||
{ type: "cron", expression: "0 9 * * 1-5", timezone: "America/New_York" },
|
||||
|
||||
@@ -99,19 +99,24 @@ export function computeNextRunAt(cadence: ScheduleCadence, after: Date): Date {
|
||||
}
|
||||
|
||||
const cron = parseCronExpression(cadence.expression);
|
||||
const hasWildcardDay = cron.dayOfMonth.isWildcard || cron.dayOfWeek.isWildcard;
|
||||
const readDateParts = createCronDatePartsReader(cadence.timezone);
|
||||
const limit = 366 * 24 * 60;
|
||||
let cursor = startOfNextMinute(after);
|
||||
|
||||
for (let index = 0; index < limit; index += 1) {
|
||||
const { minute, hour, dayOfMonth, month, dayOfWeek } = readDateParts(cursor);
|
||||
const dayOfMonthMatches = cron.dayOfMonth.matches(dayOfMonth);
|
||||
const dayOfWeekMatches = cron.dayOfWeek.matches(dayOfWeek);
|
||||
const bothDaysMatch = dayOfMonthMatches && dayOfWeekMatches;
|
||||
const eitherDayMatches = dayOfMonthMatches || dayOfWeekMatches;
|
||||
const dayMatches = hasWildcardDay ? bothDaysMatch : eitherDayMatches;
|
||||
|
||||
if (
|
||||
cron.minute.matches(minute) &&
|
||||
cron.hour.matches(hour) &&
|
||||
cron.dayOfMonth.matches(dayOfMonth) &&
|
||||
cron.month.matches(month) &&
|
||||
cron.dayOfWeek.matches(dayOfWeek)
|
||||
dayMatches
|
||||
) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user