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
- Fix the offending date to an actual calendar day (e.g. 2026-02-28 instead of 2026-02-30)
- Generate dates with a date library or Date UTC methods instead of manual string building
- Add a pre-call validator that round-trips the date like normalizeDate does
- 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
- Never build dates by string concatenation; use Date UTC methods or a date library
- Remember JS Date auto-rolls invalid days (Feb 30 -> Mar 2), so validate with round-tripping
- Watch for month off-by-one errors (0-indexed months) in generated ranges
- Validate test fixtures with real calendar dates
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
- archive search sort must be one of ${SORT_OPTIONS.join(', ')
- archive search mediatype must be one of ${MEDIATYPES.join(',
- archive search limit must be a positive integer
- archive search limit must be <= 100
- archive search query must not be empty
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/c44928d00308466c.
Report an issue: GitHub.