{"record":{"id":"c875fd3d189d8d2a","repo":"jackwener/OpenCLI","slug":"expiration-must-be-a-valid-calendar-date","errorCode":null,"errorMessage":"--expiration must be a valid calendar date","messagePattern":"--expiration must be a valid calendar date","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/barchart/greeks.js","lineNumber":27,"sourceCode":"const DEFAULT_LIMIT = 10;\nconst MIN_LIMIT = 1;\nconst MAX_LIMIT = 100;\n\nfunction normalizeSymbol(value) {\n    const symbol = String(value ?? '').trim().toUpperCase();\n    if (!symbol) throw new ArgumentError('symbol is required');\n    return symbol;\n}\n\nfunction normalizeExpiration(value) {\n    const expiration = String(value ?? '').trim();\n    if (!expiration) return '';\n    if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(expiration)) {\n        throw new ArgumentError('--expiration must use YYYY-MM-DD format');\n    }\n    const parsed = new Date(`${expiration}T00:00:00Z`);\n    if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== expiration) {\n        throw new ArgumentError('--expiration must be a valid calendar date');\n    }\n    return expiration;\n}\n\nfunction parseLimit(value) {\n    if (value === undefined || value === null || value === '') return DEFAULT_LIMIT;\n    const limit = Number(value);\n    if (!Number.isInteger(limit) || limit < MIN_LIMIT || limit > MAX_LIMIT) {\n        throw new ArgumentError(`--limit must be an integer between ${MIN_LIMIT} and ${MAX_LIMIT}`);\n    }\n    return limit;\n}\n\nfunction unwrapBrowserResult(value) {\n    if (value && typeof value === 'object' && 'session' in value && 'data' in value) {\n        return value.data;\n    }\n    return value;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/barchart/greeks.js#L9-L45","documentation":"After the YYYY-MM-DD format check passes, normalizeExpiration() parses the value as a UTC calendar date and verifies the round-trip (Date -> ISO string) reproduces the input. Values like 2025-02-30 or 2025-13-01 pass the regex but fail this check, so this ArgumentError is thrown for well-formatted but nonexistent calendar dates.","triggerScenarios":"Passing --expiration with a syntactically valid but impossible date such as 2025-02-30, 2025-04-31, 2023-02-29 (non-leap year), or 2025-00-10.","commonSituations":"Generating dates programmatically by incrementing day numbers without month rollover; typos in the day field (e.g. 31 in a 30-day month); constructing dates from user spreadsheets where the day exceeds the month's length.","solutions":["Correct the date to a real calendar date, e.g. --expiration 2025-02-28.","Build expiration dates via a Date object (setDate + ISO formatting) instead of manual string concatenation so rollover is handled.","Validate the date client-side with the same round-trip check before invoking the CLI."],"exampleFix":"// before\n--expiration 2025-02-30\n// after\n--expiration 2025-02-28","handlingStrategy":"validation","validationCode":"function isRealDate(iso) {\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(iso)) return false;\n  const parsed = new Date(`${iso}T00:00:00Z`);\n  return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === iso;\n}\n// call: isRealDate('2025-02-30') => false","typeGuard":"function isValidExpiration(v) {\n  return typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v) &&\n    new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;\n}","tryCatchPattern":"try {\n  await greeks({ symbol, expiration });\n} catch (e) {\n  if (e.message.includes('valid calendar date')) {\n    console.error(`${expiration} is not a real calendar date (e.g. Feb 30)`);\n    process.exitCode = 2;\n    return;\n  }\n  throw e;\n}","preventionTips":["Build dates with Date objects (setDate/setMonth) rather than manual string assembly","Run the regex + round-trip date check before invoking the CLI","Watch for day-of-month overflow when incrementing dates in loops"],"tags":["validation","argument-error","date","cli"],"backgroundTag":"invalid-calendar-date","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}