252 lines
6.5 KiB
TypeScript
252 lines
6.5 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { parseCron } from "../src/index.js";
|
|
|
|
interface Vector {
|
|
expr: string;
|
|
after: number;
|
|
tz: string;
|
|
expected: number;
|
|
label: string;
|
|
}
|
|
|
|
const utc = (y: number, mo: number, d: number, h = 0, mi = 0, s = 0): number =>
|
|
Date.UTC(y, mo, d, h, mi, s);
|
|
|
|
const vectors: Vector[] = [
|
|
// Hourly variants
|
|
{
|
|
expr: "0 * * * *",
|
|
after: utc(2026, 4, 11, 10, 30),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 11, 0),
|
|
label: "hourly @00",
|
|
},
|
|
{
|
|
expr: "*/15 * * * *",
|
|
after: utc(2026, 4, 11, 10, 7),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 10, 15),
|
|
label: "every 15 minutes",
|
|
},
|
|
{
|
|
expr: "*/15 * * * *",
|
|
after: utc(2026, 4, 11, 10, 45),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 11, 0),
|
|
label: "every 15 wrap",
|
|
},
|
|
{
|
|
expr: "*/30 * * * *",
|
|
after: utc(2026, 4, 11, 10, 29),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 10, 30),
|
|
label: "every 30 minutes",
|
|
},
|
|
|
|
// Daily
|
|
{
|
|
expr: "0 9 * * *",
|
|
after: utc(2026, 4, 11, 8, 0),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 9, 0),
|
|
label: "daily 9am",
|
|
},
|
|
{
|
|
expr: "0 0 * * *",
|
|
after: utc(2026, 4, 11, 12, 0),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 12, 0, 0),
|
|
label: "daily midnight",
|
|
},
|
|
{
|
|
expr: "@daily",
|
|
after: utc(2026, 4, 11, 12, 0),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 12, 0, 0),
|
|
label: "@daily macro",
|
|
},
|
|
{
|
|
expr: "@hourly",
|
|
after: utc(2026, 4, 11, 10, 30),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 11, 0),
|
|
label: "@hourly macro",
|
|
},
|
|
|
|
// Weekdays / weekends
|
|
// 2026-05-09 is Saturday. Next 9am weekday = Mon 2026-05-11.
|
|
{
|
|
expr: "0 9 * * MON-FRI",
|
|
after: utc(2026, 4, 9),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 9, 0),
|
|
label: "9am weekdays",
|
|
},
|
|
{
|
|
expr: "0 12 * * 0",
|
|
after: utc(2026, 4, 11),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 17, 12, 0),
|
|
label: "noon Sundays",
|
|
},
|
|
{
|
|
expr: "0 9 * * 7",
|
|
after: utc(2026, 4, 11),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 17, 9, 0),
|
|
label: "9am Sundays via 7",
|
|
},
|
|
|
|
// Monthly / yearly
|
|
{
|
|
expr: "0 0 1 * *",
|
|
after: utc(2026, 4, 11),
|
|
tz: "UTC",
|
|
expected: utc(2026, 5, 1, 0, 0),
|
|
label: "first of month",
|
|
},
|
|
{
|
|
expr: "@monthly",
|
|
after: utc(2026, 4, 11),
|
|
tz: "UTC",
|
|
expected: utc(2026, 5, 1, 0, 0),
|
|
label: "@monthly macro",
|
|
},
|
|
{
|
|
expr: "0 0 1 1 *",
|
|
after: utc(2026, 0, 2),
|
|
tz: "UTC",
|
|
expected: utc(2027, 0, 1, 0, 0),
|
|
label: "annual Jan 1",
|
|
},
|
|
{
|
|
expr: "@yearly",
|
|
after: utc(2026, 0, 2),
|
|
tz: "UTC",
|
|
expected: utc(2027, 0, 1, 0, 0),
|
|
label: "@yearly macro",
|
|
},
|
|
|
|
// Month aliases
|
|
{
|
|
expr: "0 0 1 JUL *",
|
|
after: utc(2026, 4, 11),
|
|
tz: "UTC",
|
|
expected: utc(2026, 6, 1, 0, 0),
|
|
label: "month alias JUL",
|
|
},
|
|
|
|
// 4 specific minutes
|
|
{
|
|
expr: "0,15,30,45 9-17 * * MON-FRI",
|
|
after: utc(2026, 4, 11, 9, 16),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 9, 30),
|
|
label: "quarter-hour business hours",
|
|
},
|
|
{
|
|
expr: "0,15,30,45 9-17 * * MON-FRI",
|
|
after: utc(2026, 4, 11, 17, 46),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 12, 9, 0),
|
|
label: "wrap to next business day",
|
|
},
|
|
|
|
// Range/step variations
|
|
{
|
|
expr: "0 10-12/2 * * *",
|
|
after: utc(2026, 4, 11, 9, 0),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 10, 0),
|
|
label: "step in range first",
|
|
},
|
|
{
|
|
expr: "0 10-12/2 * * *",
|
|
after: utc(2026, 4, 11, 11, 0),
|
|
tz: "UTC",
|
|
expected: utc(2026, 4, 11, 12, 0),
|
|
label: "step in range second",
|
|
},
|
|
|
|
// Leap year
|
|
{
|
|
expr: "0 0 29 2 *",
|
|
after: utc(2026, 0, 1),
|
|
tz: "UTC",
|
|
expected: utc(2028, 1, 29, 0, 0),
|
|
label: "leap day next",
|
|
},
|
|
|
|
// DST — America/New_York 2026-03-08 spring-forward
|
|
{
|
|
expr: "0 3 * * *",
|
|
after: utc(2026, 2, 8, 6, 0),
|
|
tz: "America/New_York",
|
|
// 03:00 EDT on 2026-03-08 = 07:00 UTC
|
|
expected: utc(2026, 2, 8, 7, 0),
|
|
label: "NY 3am day-of-spring-forward",
|
|
},
|
|
|
|
// Europe/London non-DST anchor
|
|
// 2026-01-01 09:00 London = 09:00 UTC (GMT in January)
|
|
{
|
|
expr: "0 9 * * *",
|
|
after: utc(2025, 11, 31, 23, 0),
|
|
tz: "Europe/London",
|
|
expected: utc(2026, 0, 1, 9, 0),
|
|
label: "London winter 9am",
|
|
},
|
|
|
|
// Australia/Sydney winter 12:00 local = 02:00 UTC (UTC+10).
|
|
// after = 2026-05-31T00:00Z = 2026-05-31T10:00 Sydney, next noon
|
|
// Sydney = 2026-05-31T12:00 local = 2026-05-31T02:00 UTC.
|
|
{
|
|
expr: "0 12 * * *",
|
|
after: utc(2026, 4, 31, 0, 0),
|
|
tz: "Australia/Sydney",
|
|
expected: utc(2026, 4, 31, 2, 0),
|
|
label: "Sydney winter noon",
|
|
},
|
|
|
|
// Seconds + Quartz variants (need seconds: true)
|
|
// Tested separately because parseCron options differ.
|
|
];
|
|
|
|
describe("vectors — 5-field UTC + DST", () => {
|
|
for (const v of vectors) {
|
|
it(v.label, { timeout: 5000 }, () => {
|
|
const expr = parseCron(v.expr);
|
|
const next = expr.nextRun(v.after, v.tz);
|
|
expect(next).toBe(v.expected);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe("vectors — 6-field seconds", () => {
|
|
it("every 10 seconds", { timeout: 5000 }, () => {
|
|
const expr = parseCron("*/10 * * * * *", { seconds: true });
|
|
expect(expr.nextRun(utc(2026, 4, 11, 10, 0, 5), "UTC")).toBe(
|
|
utc(2026, 4, 11, 10, 0, 10)
|
|
);
|
|
});
|
|
|
|
it("seconds with hour wrap", { timeout: 5000 }, () => {
|
|
const expr = parseCron("30 0 * * * *", { seconds: true });
|
|
expect(expr.nextRun(utc(2026, 4, 11, 10, 0, 30), "UTC")).toBe(
|
|
utc(2026, 4, 11, 11, 0, 30)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("vectors — 7-field Quartz year", () => {
|
|
it("year-constrained next run", { timeout: 5000 }, () => {
|
|
const expr = parseCron("0 0 12 1 1 * 2030", {
|
|
seconds: true,
|
|
years: true,
|
|
});
|
|
expect(expr.nextRun(utc(2026, 0, 1), "UTC")).toBe(
|
|
utc(2030, 0, 1, 12, 0, 0)
|
|
);
|
|
});
|
|
});
|