{"record":{"id":"a4101db1703c976c","repo":"can1357/oh-my-pi","slug":"invalid-iso-datetime-value-a4101d","errorCode":null,"errorMessage":"Invalid ISO datetime: ${value}","messagePattern":"Invalid ISO datetime: (.+?)","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"warning","filePath":"packages/mnemopi/src/util/datetime.ts","lineNumber":16,"sourceCode":"import { recencyHalflifeHours } from \"../config\";\nimport { LruCache } from \"./lru\";\n\nconst TZ_RE = /(?:Z|[+-]\\d\\d:?\\d\\d)$/;\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst TS_CACHE = new LruCache<string, Date>(2000);\n\nexport type QueryTime = string | Date | null | undefined;\n\nexport function parseIsoDateTimeUtc(value: string): Date {\n\tlet text = value.trim();\n\tif (!text) throw new RangeError(\"Invalid ISO datetime: empty string\");\n\tif (DATE_ONLY_RE.test(text)) text += \"T00:00:00Z\";\n\telse if (!TZ_RE.test(text)) text += \"Z\";\n\tconst date = new Date(text);\n\tif (Number.isNaN(date.getTime())) throw new RangeError(`Invalid ISO datetime: ${value}`);\n\treturn date;\n}\n\nexport function normalizeDateTimeUtc(value: Date): Date {\n\tconst time = value.getTime();\n\tif (Number.isNaN(time)) throw new RangeError(\"Invalid Date\");\n\treturn new Date(time);\n}\n\nexport function parseQueryTime(value: QueryTime): Date {\n\tif (value === null || value === undefined) return new Date();\n\treturn typeof value === \"string\" ? parseIsoDateTimeUtc(value) : normalizeDateTimeUtc(value);\n}\n\nexport function parseTsFast(value: string): Date | undefined {\n\tif (!value) return undefined;\n\tconst cached = TS_CACHE.get(value);\n\tif (cached !== undefined) return cached;","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/util/datetime.ts#L1-L34","documentation":"parseIsoDateTimeUtc() builds a Date from the (timezone-normalized) string and throws this RangeError when the result is NaN — meaning the text was non-empty but not a valid ISO 8601 datetime. The original input value is included in the message for diagnosis.","triggerScenarios":"Calling parseIsoDateTimeUtc('not-a-date'), '2026-13-45T99:00:00Z', 'Aug 31 2026', or any non-ISO format (timestamps, locale dates); also via parseQueryTime() with such strings.","commonSituations":"Free-form user input in time filters, clients sending epoch millis or RFC 2822 dates instead of ISO 8601, copy-pasted dates with locale formatting, or swapped day/month values that overflow.","solutions":["Send a valid ISO 8601 string: '2026-08-31' or '2026-08-31T14:30:00Z'.","Convert epoch-millis inputs with new Date(Number(value)) instead of passing them here.","Pre-validate with a regex/Date.parse check and return a 400-level error to the client before calling.","Normalize non-ISO formats at the API boundary (e.g. dayjs/Temporal) before parsing."],"exampleFix":"// before\nconst t = parseQueryTime(req.query.since); // '08/31/2026' -> RangeError\n// after\nconst raw = String(req.query.since ?? '');\nconst iso = /^\\d{4}-\\d{2}-\\d{2}(T[\\d:.]+Z?)?$/.test(raw) ? raw : null;\nif (!iso) throw new HttpError(400, 'since must be ISO 8601, e.g. 2026-08-31T00:00:00Z');\nconst t = parseQueryTime(iso);","handlingStrategy":"validation","validationCode":"const ISO_RE = /^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/;\nfunction isValidIso(value) {\n  return typeof value === 'string' && ISO_RE.test(value.trim()) && !Number.isNaN(Date.parse(value));\n}\nif (!isValidIso(input)) throw new HttpError(400, `since must be ISO 8601, got: ${input}`);\nconst t = parseQueryTime(input);","typeGuard":"function isIsoDatetimeString(v) {\n  return typeof v === 'string' && !Number.isNaN(Date.parse(v)) && /^\\d{4}-\\d{2}-\\d{2}/.test(v.trim());\n}","tryCatchPattern":"try {\n  const t = parseIsoDateTimeUtc(value);\n} catch (err) {\n  if (err instanceof RangeError && err.message.startsWith('Invalid ISO datetime')) {\n    throw new HttpError(400, `Timestamp must be ISO 8601 (e.g. 2026-08-31T00:00:00Z), got: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Always send ISO 8601 (YYYY-MM-DD or full RFC 3339 with Z) across APIs","Convert epoch numbers with new Date(Number(v)), not parseIsoDateTimeUtc","Pre-validate client-submitted dates and echo the expected format in errors","Add a form-level date picker instead of free-text date input"],"tags":["datetime","iso8601","validation","range-error","parsing"],"backgroundTag":"invalid-iso-datetime","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}