jackwener/OpenCLI · error · ArgumentError

${label} is not a valid calendar date: ${v}

Error message

${label} is not a valid calendar date: ${v}

What it means

After the format check passes, normalizeDate constructs a UTC Date from the parsed year/month/day and verifies the components round-trip exactly. It throws ArgumentError when the string looks like a date but is not a real calendar date (e.g. 2026-02-30) or is out of range. This prevents invalid dates from silently rolling over (Date auto-normalizes 2026-02-30 to 2026-03-02).

Source

Thrown at clis/booking/search.js:50

}

function normalizeDate(value, label) {
  const v = String(value || '').trim();
  if (!v) {
    throw new ArgumentError(`${label} is required (YYYY-MM-DD)`);
  }
  if (!DATE_RE.test(v)) {
    throw new ArgumentError(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);
  }
  const [year, month, day] = v.split('-').map(Number);
  const d = new Date(Date.UTC(year, month - 1, day));
  if (
    Number.isNaN(d.getTime()) ||
    d.getUTCFullYear() !== year ||
    d.getUTCMonth() !== month - 1 ||
    d.getUTCDate() !== day
  ) {
    throw new ArgumentError(`${label} is not a valid calendar date: ${v}`);
  }
  return v;
}

function normalizeCurrency(value) {
  if (value == null || value === '') return '';
  const v = String(value).trim().toUpperCase();
  if (!/^[A-Z]{3}$/.test(v)) {
    throw new ArgumentError(`currency must be a 3-letter ISO code (e.g. USD, JPY, CNY), got ${JSON.stringify(value)}`);
  }
  return v;
}

const ALLOWED_LANGS = new Set([
  'en-us', 'en-gb', 'zh-cn', 'zh-tw', 'ja', 'ko', 'de', 'fr', 'es', 'it',
  'pt-br', 'pt-pt', 'ru', 'th', 'vi', 'tr', 'pl', 'nl', 'ar',
]);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Fix the offending date to an actual calendar day (e.g. 2026-02-28 instead of 2026-02-30)
  2. Generate dates with a date library or Date UTC methods instead of manual string building
  3. Add a pre-call validator that round-trips the date like normalizeDate does
  4. Check for off-by-one errors in month/day computation in your date-generation code

Example fix

// before
const checkout = `${year}-02-${startDay + 30}`; // 2026-02-35
// after
const checkout = new Date(Date.UTC(year, 1, startDay + 30)).toISOString().slice(0, 10);
Defensive patterns

Strategy: validation

Validate before calling

function isRealCalendarDate(v) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(v)) return false;
  const [y, m, d] = v.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d));
  return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;
}
if (!isRealCalendarDate(checkin) || !isRealCalendarDate(checkout)) {
  throw new Error('dates must be real calendar dates');
}

Type guard

function isRealCalendarDate(v) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(v)) return false;
  const [y, m, d] = v.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d));
  return !Number.isNaN(dt.getTime()) && dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;
}

Try / catch

try {
  await search(page, { destination, checkin, checkout });
} catch (e) {
  if (/not a valid calendar date/.test(e.message)) {
    throw new Error(`Fix the date argument: ${e.message}`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing checkin/checkout values like '2026-02-30', '2026-13-01', '2026-00-10', or '2026-04-31' — syntactically YYYY-MM-DD but not real calendar dates.

Common situations: Hand-computed date arithmetic (adding 30 days to February); typo'd month/day digits; generating dates with custom string concatenation instead of a date library; test fixtures with placeholder dates.

Related errors


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