{"record":{"id":"c44928d00308466c","repo":"jackwener/OpenCLI","slug":"label-is-not-a-valid-calendar-date-v","errorCode":null,"errorMessage":"${label} is not a valid calendar date: ${v}","messagePattern":"(.+?) is not a valid calendar date: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/booking/search.js","lineNumber":50,"sourceCode":"}\n\nfunction normalizeDate(value, label) {\n  const v = String(value || '').trim();\n  if (!v) {\n    throw new ArgumentError(`${label} is required (YYYY-MM-DD)`);\n  }\n  if (!DATE_RE.test(v)) {\n    throw new ArgumentError(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);\n  }\n  const [year, month, day] = v.split('-').map(Number);\n  const d = new Date(Date.UTC(year, month - 1, day));\n  if (\n    Number.isNaN(d.getTime()) ||\n    d.getUTCFullYear() !== year ||\n    d.getUTCMonth() !== month - 1 ||\n    d.getUTCDate() !== day\n  ) {\n    throw new ArgumentError(`${label} is not a valid calendar date: ${v}`);\n  }\n  return v;\n}\n\nfunction normalizeCurrency(value) {\n  if (value == null || value === '') return '';\n  const v = String(value).trim().toUpperCase();\n  if (!/^[A-Z]{3}$/.test(v)) {\n    throw new ArgumentError(`currency must be a 3-letter ISO code (e.g. USD, JPY, CNY), got ${JSON.stringify(value)}`);\n  }\n  return v;\n}\n\nconst ALLOWED_LANGS = new Set([\n  'en-us', 'en-gb', 'zh-cn', 'zh-tw', 'ja', 'ko', 'de', 'fr', 'es', 'it',\n  'pt-br', 'pt-pt', 'ru', 'th', 'vi', 'tr', 'pl', 'nl', 'ar',\n]);\n","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/booking/search.js#L32-L68","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst checkout = `${year}-02-${startDay + 30}`; // 2026-02-35\n// after\nconst checkout = new Date(Date.UTC(year, 1, startDay + 30)).toISOString().slice(0, 10);","handlingStrategy":"validation","validationCode":"function isRealCalendarDate(v) {\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(v)) return false;\n  const [y, m, d] = v.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 (!isRealCalendarDate(checkin) || !isRealCalendarDate(checkout)) {\n  throw new Error('dates must be real calendar dates');\n}","typeGuard":"function isRealCalendarDate(v) {\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(v)) return false;\n  const [y, m, d] = v.split('-').map(Number);\n  const dt = new Date(Date.UTC(y, m - 1, d));\n  return !Number.isNaN(dt.getTime()) && dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;\n}","tryCatchPattern":"try {\n  await search(page, { destination, checkin, checkout });\n} catch (e) {\n  if (/not a valid calendar date/.test(e.message)) {\n    throw new Error(`Fix the date argument: ${e.message}`);\n  } else throw e;\n}","preventionTips":["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"],"tags":["validation","argument-error","calendar-date"],"backgroundTag":"invalid-calendar-date","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}