jackwener/OpenCLI · error · ArgumentError

--${name} is not a real calendar date: ${value}

Error message

--${name} is not a real calendar date: ${value}

What it means

parseIsoDate performs a UTC round-trip check so that dates like 2026-02-30 (regex-valid, in-range) are rejected as non-existent calendar dates. The constructed Date must read back as exactly the given year/month/day. This is the final correctness gate before the value is returned.

Source

Thrown at clis/trip/utils.js:46

export function parseIsoDate(name, raw) {
    if (raw === undefined || raw === null || raw === '') {
        throw new ArgumentError(`--${name} is required (YYYY-MM-DD)`);
    }
    const value = String(raw).trim();
    const m = ISO_DATE_RE.exec(value);
    if (!m) {
        throw new ArgumentError(`--${name} must be YYYY-MM-DD, got ${JSON.stringify(raw)}`);
    }
    const year = Number(m[1]);
    const month = Number(m[2]);
    const day = Number(m[3]);
    if (month < 1 || month > 12 || day < 1 || day > 31) {
        throw new ArgumentError(`--${name} has invalid month/day: ${value}`);
    }
    // Cross-check via UTC date math so 2026-02-30 doesn't pass.
    const parsed = new Date(Date.UTC(year, month - 1, day));
    if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {
        throw new ArgumentError(`--${name} is not a real calendar date: ${value}`);
    }
    return value;
}

export function parseListLimit(raw, fallback = 20) {
    if (raw === undefined || raw === null || raw === '') return fallback;
    const parsed = Number(raw);
    if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {
        throw new ArgumentError(`--limit must be an integer between ${MIN_LIMIT} and ${MAX_LIMIT}, got ${JSON.stringify(raw)}`);
    }
    if (parsed < MIN_LIMIT || parsed > MAX_LIMIT) {
        throw new ArgumentError(`--limit must be between ${MIN_LIMIT} and ${MAX_LIMIT}, got ${parsed}`);
    }
    return parsed;
}

export function buildFlightSearchUrl(fromCode, toCode, date) {
    const params = new URLSearchParams({

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pick an existing calendar date (e.g. 2026-02-28 instead of 2026-02-30)
  2. Compute dates with a calendar-aware method: new Date(Date.UTC(y,m,d)) then format via toISOString().slice(0,10)
  3. Clamp day-of-month after month arithmetic rather than assuming 31 days for every month
  4. Use a date library (date-fns, Luxon, Day.js) to construct valid dates instead of string assembly

Example fix

// before
const depart = `2026-02-${String(startDay + 5).padStart(2, '0')}`; // may yield 02-30
// after
const d = new Date(Date.UTC(2026, 1, startDay + 5));
const depart = d.toISOString().slice(0, 10);
Defensive patterns

Strategy: validation

Validate before calling

function isRealCalendarDate(v) {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(v).trim());
  if (!m) return false;
  const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
  const dt = new Date(Date.UTC(y, mo - 1, d));
  return dt.getUTCFullYear() === y && dt.getUTCMonth() === mo - 1 && dt.getUTCDate() === d;
}
if (!isRealCalendarDate(depart)) throw new Error(`not a real date: ${depart}`);

Type guard

function isRealDate(v) {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(v).trim());
  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 {
  runTripCli(['flights', '--depart', depart]);
} catch (err) {
  if (err instanceof ArgumentError && /is not a real calendar date/.test(err.message)) {
    console.error(`Date does not exist on the calendar: ${err.message}`);
    process.exitCode = 2;
  } else throw err;
}

Prevention

When it happens

Trigger: Passing non-existent dates such as 2026-02-30, 2026-04-31, 2025-02-29 (non-leap year), or 2026-06-31 to depart/ret/date/checkin/checkout.

Common situations: End-of-month arithmetic computed by hand or by naive date math (adding 30 days to Jan 31); assuming all months have 31 days; generating dates in a script without calendar awareness; misremembering leap years.

Related errors


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