{"record":{"id":"5a4ad158449c99e7","repo":"jackwener/OpenCLI","slug":"date-value-is-not-a-real-calendar-date","errorCode":null,"errorMessage":"date \"${value}\" is not a real calendar date","messagePattern":"date \"(.+?)\" is not a real calendar date","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/12306/utils.js","lineNumber":99,"sourceCode":"    }\n    const lower = trimmed.toLowerCase();\n    const exactName = stations.find((s) => s.name === trimmed);\n    if (exactName) return exactName;\n    const exactPinyin = stations.find((s) => s.pinyin === lower);\n    if (exactPinyin) return exactPinyin;\n    const exactAbbr = stations.find((s) => s.abbr === lower || s.short === lower);\n    if (exactAbbr) return exactAbbr;\n    throw new ArgumentError(`Unknown 12306 station \"${trimmed}\"`, 'Try the Chinese name (上海虹桥), the 3-4 letter telecode (AOH), or full pinyin (shanghaihongqiao).');\n}\n\nexport function validateDate(value) {\n    if (!DATE_RE.test(String(value ?? ''))) {\n        throw new ArgumentError(`date must be YYYY-MM-DD, got \"${value}\"`);\n    }\n    const [y, m, d] = value.split('-').map(Number);\n    const date = new Date(Date.UTC(y, m - 1, d));\n    if (date.getUTCFullYear() !== y || date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {\n        throw new ArgumentError(`date \"${value}\" is not a real calendar date`);\n    }\n    return value;\n}\n\nexport function normalizeLimit(value, defaultValue, max) {\n    if (value === undefined || value === null || value === '') return defaultValue;\n    const n = Number(value);\n    if (!Number.isInteger(n) || n < 1) {\n        throw new ArgumentError(`limit must be a positive integer (1-${max})`);\n    }\n    if (n > max) {\n        throw new ArgumentError(`limit must be <= ${max}`);\n    }\n    return n;\n}\n\n/** Extract Set-Cookie header values into a single `Cookie:` header string. */\nexport function buildCookieHeader(setCookieHeaders) {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/utils.js#L81-L117","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait query({ date: '2026-02-30' });\n// after\nawait query({ date: '2026-02-28' }); // real calendar date","handlingStrategy":"validation","validationCode":"function isRealDate(v) {\n  const s = String(v ?? '');\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) return false;\n  const [y, m, d] = s.split('-').map(Number);\n  const dt = new Date(Date.UTC(y, m - 1, d));\n  return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;\n}\nif (!isRealDate('2026-02-30')) throw new Error('not a real calendar date');","typeGuard":null,"tryCatchPattern":"try {\n  await query({ date });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('not a real calendar date')) {\n    console.error(`\"${date}\" does not exist on the calendar; check month lengths and leap years.`);\n  } else throw e;\n}","preventionTips":["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"],"tags":["input-validation","date-format","argument-error"],"backgroundTag":"invalid-date-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}