jackwener/OpenCLI · error · ArgumentError
date "${value}" is not a real calendar date
Error message
date "${value}" is not a real calendar date What it means
validateDate() second stage: the string matched YYYY-MM-DD but does not represent a real calendar date. It constructs a UTC Date from the parts and checks for rollover — e.g. 2026-02-30 rolls to March 2, which fails the round-trip check — and throws ArgumentError.
Source
Thrown at clis/12306/utils.js:99
}
const lower = trimmed.toLowerCase();
const exactName = stations.find((s) => s.name === trimmed);
if (exactName) return exactName;
const exactPinyin = stations.find((s) => s.pinyin === lower);
if (exactPinyin) return exactPinyin;
const exactAbbr = stations.find((s) => s.abbr === lower || s.short === lower);
if (exactAbbr) return exactAbbr;
throw new ArgumentError(`Unknown 12306 station "${trimmed}"`, 'Try the Chinese name (上海虹桥), the 3-4 letter telecode (AOH), or full pinyin (shanghaihongqiao).');
}
export function validateDate(value) {
if (!DATE_RE.test(String(value ?? ''))) {
throw new ArgumentError(`date must be YYYY-MM-DD, got "${value}"`);
}
const [y, m, d] = value.split('-').map(Number);
const date = new Date(Date.UTC(y, m - 1, d));
if (date.getUTCFullYear() !== y || date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {
throw new ArgumentError(`date "${value}" is not a real calendar date`);
}
return value;
}
export function normalizeLimit(value, defaultValue, max) {
if (value === undefined || value === null || value === '') return defaultValue;
const n = Number(value);
if (!Number.isInteger(n) || n < 1) {
throw new ArgumentError(`limit must be a positive integer (1-${max})`);
}
if (n > max) {
throw new ArgumentError(`limit must be <= ${max}`);
}
return n;
}
/** Extract Set-Cookie header values into a single `Cookie:` header string. */
export function buildCookieHeader(setCookieHeaders) {View on GitHub (pinned to 49907e53dc)
Solutions
- Fix the date value to a real calendar date, checking month lengths and leap years
- Generate dates programmatically (new Date(y, m-1, d).toISOString().slice(0,10)) instead of string concatenation
- Add client-side validation with the same round-trip check before calling the library
Example fix
// before
await query({ date: '2026-02-30' });
// after
await query({ date: '2026-02-28' }); // real calendar date Defensive patterns
Strategy: validation
Validate before calling
function isRealDate(v) {
const s = String(v ?? '');
if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return false;
const [y, m, d] = s.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 (!isRealDate('2026-02-30')) throw new Error('not a real calendar date'); Try / catch
try {
await query({ date });
} catch (e) {
if (e instanceof ArgumentError && e.message.includes('not a real calendar date')) {
console.error(`"${date}" does not exist on the calendar; check month lengths and leap years.`);
} else throw e;
} Prevention
- Build dates with Date arithmetic, never string concatenation
- Use libraries like date-fns isValid() to check constructed dates
- Test date-generation code for month boundaries and leap years
When it happens
Trigger: Passing syntactically valid but impossible dates: '2026-02-30', '2025-02-29' (non-leap year), '2026-13-01' if the regex permits months >12, or day 31 in a 30-day month.
Common situations: Naive date arithmetic that produced invalid dates (adding 30 days by incrementing the day field), manual string assembly from separate year/month/day inputs, or user typos like 02-30.
Related errors
- date must be YYYY-MM-DD, got "${value}"
- Unknown 12306 station telecode "${trimmed}"
- Unknown 12306 station "${trimmed}"
- limit must be a positive integer (1-${max})
- limit must be <= ${max}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/5a4ad158449c99e7.
Report an issue: GitHub.