jackwener/OpenCLI · error · ArgumentError

Date must be a real calendar date: ${date}

Error message

Date must be a real calendar date: ${date}

What it means

After the YYYY-MM-DD shape check passes, parseDate constructs a UTC Date and verifies the components round-trip (year/month/day unchanged). This rejects impossible calendar dates like 2026-02-31 or 2025-13-01, which the regex alone would accept. JavaScript's Date silently rolls such values over, so the round-trip check is the real validity gate.

Source

Thrown at clis/mercury/utils.js:76

    const currency = optionalString(kwargs, 'currency', 'CNY').toUpperCase();
    if (!/^[A-Z]{3}$/.test(currency)) {
        throw new ArgumentError(`Currency must be a three-letter ISO currency code: ${currency}`);
    }
    return currency;
}

function parseDate(kwargs) {
    const date = requireString(kwargs, 'date');
    const match = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (!match) {
        throw new ArgumentError(`Date must be YYYY-MM-DD: ${date}`);
    }
    const year = Number(match[1]);
    const month = Number(match[2]);
    const day = Number(match[3]);
    const parsed = new Date(Date.UTC(year, month - 1, day));
    if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {
        throw new ArgumentError(`Date must be a real calendar date: ${date}`);
    }
    return date;
}

function parseOcrWaitSeconds(kwargs) {
    const raw = optionalString(kwargs, 'ocr-wait-seconds', '8');
    if (!/^\d+$/.test(raw)) {
        throw new ArgumentError(`ocr-wait-seconds must be a non-negative integer: ${raw}`);
    }
    return Number(raw);
}

export function normalizeReimbursementInput(kwargs) {
    return {
        receipt: parseReceiptPath(kwargs),
        amount: parseAmount(kwargs),
        currency: parseCurrency(kwargs),
        date: parseDate(kwargs),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Correct the date to a real calendar date (check the month's day count).
  2. Generate dates programmatically instead of hardcoding: new Date(Date.UTC(y,m-1,d)).toISOString().slice(0,10).
  3. If constructing from zero-indexed months in JS, add 1 to the month before formatting.
  4. Validate upstream with a calendar-aware check (e.g. Date round-trip) before calling.

Example fix

// before
--date "2026-02-30"
// after
--date "2026-02-28"
Defensive patterns

Strategy: validation

Validate before calling

function isRealDate(d) {
  const m = /^\d{4}-\d{2}-\d{2}$/.exec(d);
  if (!m) return false;
  const dt = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
  return dt.getUTCFullYear() === +m[1] && dt.getUTCMonth() === +m[2] - 1 && dt.getUTCDate() === +m[3];
}

Try / catch

try {
  await reimburse({ date });
} catch (e) {
  if (e instanceof ArgumentError && e.message.includes('real calendar date')) {
    console.error(`'${date}' is not a valid calendar date (e.g. Feb 30).`);
  } else throw e;
}

Prevention

When it happens

Trigger: --date '2026-02-30', '2026-02-31', '2025-13-01', '2026-00-10', '2026-04-31' — syntactically valid shape but not a real date.

Common situations: Hand-typed dates with wrong day counts (February 30/31); month off-by-one from zero-indexed month values in generated scripts; day 31 in 30-day months; scripts computing dates incorrectly before passing them in.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/5e334b33648ff56b. Report an issue: GitHub.