diff --git a/src/to-human.ts b/src/to-human.ts index 855efc3..5d7ccd5 100644 --- a/src/to-human.ts +++ b/src/to-human.ts @@ -154,9 +154,9 @@ export function toHuman(expression: string): string { if (parts.length !== 5 && parts.length !== 6) { throw new CronTranslateError(`Expected a 5- or 6-field cron expression, got ${parts.length} fields.`); } - const [sec, min, hour, dom, monthRaw, dowRaw] = parts.length === 6 - ? parts - : ['0', ...parts]; + // `?` (Quartz "no specific value") means no constraint on the field, same as `*`. + const fields = (parts.length === 6 ? parts : ['0', ...parts]).map((f) => (f === '?' ? '*' : f)); + const [sec, min, hour, dom, monthRaw, dowRaw] = fields; const month = mapNames(monthRaw, MONTH_NUMS); const dow = mapNames(dowRaw, DOW_NUMS); diff --git a/test/to-human.test.ts b/test/to-human.test.ts index 3c1e3d2..91f7ec9 100644 --- a/test/to-human.test.ts +++ b/test/to-human.test.ts @@ -69,6 +69,17 @@ describe('toHuman — accepts month/weekday names in input', () => { }); }); +describe('toHuman — treats ? (Quartz no-value) like *', () => { + const cases: [string, string][] = [ + ['0 0 9 ? * MON', 'at 9am on monday'], + ['0 0 9 15 * ?', 'at 9am on day 15'], + ['0 0 0 ? * 1-5', 'on monday to friday'], + ]; + it.each(cases)('%s => %s', (cron, expected) => { + expect(toHuman(cron)).toBe(expected); + }); +}); + describe('toHuman — rejects malformed input', () => { it.each(['', 'a b c', '1 2 3 4 5 6 7'])('rejects %j', (bad) => { expect(() => toHuman(bad)).toThrow();