{"record":{"id":"c530ac983c056847","repo":"jackwener/OpenCLI","slug":"name-is-not-a-real-calendar-date-value-c530ac","errorCode":null,"errorMessage":"--${name} is not a real calendar date: ${value}","messagePattern":"--(.+?) is not a real calendar date: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/trip/utils.js","lineNumber":46,"sourceCode":"export function parseIsoDate(name, raw) {\n    if (raw === undefined || raw === null || raw === '') {\n        throw new ArgumentError(`--${name} is required (YYYY-MM-DD)`);\n    }\n    const value = String(raw).trim();\n    const m = ISO_DATE_RE.exec(value);\n    if (!m) {\n        throw new ArgumentError(`--${name} must be YYYY-MM-DD, got ${JSON.stringify(raw)}`);\n    }\n    const year = Number(m[1]);\n    const month = Number(m[2]);\n    const day = Number(m[3]);\n    if (month < 1 || month > 12 || day < 1 || day > 31) {\n        throw new ArgumentError(`--${name} has invalid month/day: ${value}`);\n    }\n    // Cross-check via UTC date math so 2026-02-30 doesn't pass.\n    const parsed = new Date(Date.UTC(year, month - 1, day));\n    if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {\n        throw new ArgumentError(`--${name} is not a real calendar date: ${value}`);\n    }\n    return value;\n}\n\nexport function parseListLimit(raw, fallback = 20) {\n    if (raw === undefined || raw === null || raw === '') return fallback;\n    const parsed = Number(raw);\n    if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {\n        throw new ArgumentError(`--limit must be an integer between ${MIN_LIMIT} and ${MAX_LIMIT}, got ${JSON.stringify(raw)}`);\n    }\n    if (parsed < MIN_LIMIT || parsed > MAX_LIMIT) {\n        throw new ArgumentError(`--limit must be between ${MIN_LIMIT} and ${MAX_LIMIT}, got ${parsed}`);\n    }\n    return parsed;\n}\n\nexport function buildFlightSearchUrl(fromCode, toCode, date) {\n    const params = new URLSearchParams({","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/utils.js#L28-L64","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst depart = `2026-02-${String(startDay + 5).padStart(2, '0')}`; // may yield 02-30\n// after\nconst d = new Date(Date.UTC(2026, 1, startDay + 5));\nconst depart = d.toISOString().slice(0, 10);","handlingStrategy":"validation","validationCode":"function isRealCalendarDate(v) {\n  const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(String(v).trim());\n  if (!m) return false;\n  const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];\n  const dt = new Date(Date.UTC(y, mo - 1, d));\n  return dt.getUTCFullYear() === y && dt.getUTCMonth() === mo - 1 && dt.getUTCDate() === d;\n}\nif (!isRealCalendarDate(depart)) throw new Error(`not a real date: ${depart}`);","typeGuard":"function isRealDate(v) {\n  const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(String(v).trim());\n  if (!m) return false;\n  const dt = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));\n  return dt.getUTCFullYear() === +m[1] && dt.getUTCMonth() === +m[2] - 1 && dt.getUTCDate() === +m[3];\n}","tryCatchPattern":"try {\n  runTripCli(['flights', '--depart', depart]);\n} catch (err) {\n  if (err instanceof ArgumentError && /is not a real calendar date/.test(err.message)) {\n    console.error(`Date does not exist on the calendar: ${err.message}`);\n    process.exitCode = 2;\n  } else throw err;\n}","preventionTips":["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"],"tags":["cli","argument-validation","calendar-date"],"backgroundTag":"invalid-calendar-date","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}