Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/lib/cron.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ describe('describeCron', () => {
}
});

it('returns the raw expression when the month is restricted', () => {
// Every phrasing describeCron can produce names a cadence, and none of them
// can carry "only in these months". Saying "daily at 03:00" for a June-only
// cron contradicts the ~3 times/month printed next to it by the advisory.
for (const cron of ['0 3 * 6 *', '0 3 * 1,7 *', '0 3 * */6 *', '0 3 1 3 *', '0 3 * 6 1']) {
expect(describeCron(cron), cron).toBe(cron);
}
});

it('returns the raw expression when the field count is not 5', () => {
// A 6-field Quartz cron shifts every index, so reading f[1] as the hour
// would describe a time the schedule never runs at.
Expand Down Expand Up @@ -145,6 +154,16 @@ describe('formatScheduleFrequencyAdvisory', () => {
expect(out).not.toContain('~0 time');
});

it('does not call a month-restricted schedule daily', () => {
// Regression: describeCron used to skip the month field entirely, so this
// rendered as "~3 time(s)/month (daily at 03:00)" -- two claims that cannot
// both be true, in one sentence.
const out = formatScheduleFrequencyAdvisory('0 3 * 6 *');
expect(out).toContain('~3 time(s)/month');
expect(out).not.toContain('daily');
expect(out).toContain('0 3 * 6 *');
});

it('still advises, without a frequency, for an expression it cannot read', () => {
const out = formatScheduleFrequencyAdvisory('0 0 3 * * *');
expect(out).toContain('0 0 3 * * *');
Expand Down
8 changes: 7 additions & 1 deletion src/lib/cron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,15 @@ export function runsPerMonth(cron: string): number | null {
export function describeCron(cron: string): string {
const f = fields(cron);
if (f.length !== FIELD_COUNT) return cron.trim();
const [minute, hour, dayOfMonth, , dayOfWeek] = f;
const [minute, hour, dayOfMonth, month, dayOfWeek] = f;

if (!isPinned(minute) || !isPinned(hour)) return cron.trim();
// A restricted month makes every phrasing below wrong, because none of them
// can say "except in the months this cron skips": `0 3 * 6 *` is not "daily
// at 03:00", it is daily *during June*. runsPerMonth already divides by the
// month count, so describing it as daily contradicts the frequency printed
// beside it in the same sentence.
if (!isWildcard(month)) return cron.trim();
const at = `${hour!.padStart(2, '0')}:${minute!.padStart(2, '0')}`;

if (isWildcard(dayOfMonth) && isWildcard(dayOfWeek)) return `daily at ${at}`;
Expand Down
Loading