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
- Pick an existing calendar date (e.g. 2026-02-28 instead of 2026-02-30)
- Compute dates with a calendar-aware method: new Date(Date.UTC(y,m,d)) then format via toISOString().slice(0,10)
- Clamp day-of-month after month arithmetic rather than assuming 31 days for every month
- 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
- Compute dates with calendar-aware arithmetic (Date UTC math or a date library), never string assembly
- Clamp day-of-month when doing month arithmetic (end-of-month + N days)
- Remember leap-year rules for Feb 29
- Unit-test any date-generating code against month boundaries
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
- weibo user-posts ${name} must be a valid calendar date
- <train-no> "${trainNo}" does not look like a 12306 internal
- --from station must not be empty
- --to station must not be empty
- --seat-types must contain only 12306 seat letters/digits (A-
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/c530ac983c056847.
Report an issue: GitHub.