{"record":{"id":"6179381d92826ba3","repo":"TryGhost/Ghost","slug":"invalid-filter-date-date","errorCode":null,"errorMessage":"Invalid filter date: ${date}","messagePattern":"Invalid filter date: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/admin/src/shared/filters/filter-normalization.ts","lineNumber":32,"sourceCode":"            : value;\n\n        return Temporal.Instant.from(instantValue).toZonedDateTimeISO(timezone).toPlainDate().toString();\n    } catch {\n        return null;\n    }\n}\n\nexport function getTodayInTimezone(timezone: string): string {\n    return Temporal.Now.zonedDateTimeISO(timezone).toPlainDate().toString();\n}\n\nexport function getDayBoundsInUtc(date: string, timezone: string): {start: string; end: string} {\n    let plainDate: Temporal.PlainDate;\n\n    try {\n        plainDate = Temporal.PlainDate.from(date);\n    } catch {\n        throw new Error(`Invalid filter date: ${date}`);\n    }\n\n    try {\n        const start = plainDate.toPlainDateTime(Temporal.PlainTime.from('00:00:00')).toZonedDateTime(timezone).toInstant();\n        const end = plainDate.toPlainDateTime(Temporal.PlainTime.from('23:59:59.999')).toZonedDateTime(timezone).toInstant();\n\n        return {\n            start: start.toString({fractionalSecondDigits: 3}),\n            end: end.toString({fractionalSecondDigits: 3})\n        };\n    } catch {\n        throw new Error(`Invalid timezone: ${timezone}`);\n    }\n}\n","sourceCodeStart":14,"sourceCodeEnd":47,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/shared/filters/filter-normalization.ts#L14-L47","documentation":"getDayBoundsInUtc converts a calendar date plus a timezone into UTC instant bounds (start of day 00:00:00.000, end of day 23:59:59.999). It calls Temporal.PlainDate.from(date) which throws if the string is not a valid ISO 8601 date (YYYY-MM-DD). The catch re-throws with a descriptive message including the offending date value.","triggerScenarios":"Calling getDayBoundsInUtc with a date string that is not strict ISO 8601 calendar date format. Examples: '2024-1-5' (non-zero-padded), '2024/01/05' (slashes), '01-05-2024' (date-first), '2024-01-05T00:00:00' (datetime, not date-only), empty string, or a locale-formatted date like 'Jan 5, 2024'.","commonSituations":"A date picker component emits a locale-specific or non-padded format. A backend returns a datetime string where a bare date was expected. A URL query parameter carries a user-typed date that was not normalized. Date-fns or moment formatting with a non-ISO token produces a string PlainDate.from rejects.","solutions":["Normalize the date to ISO 8601 YYYY-MM-DD before calling getDayBoundsInUtc; use Temporal.PlainDate.from(date) in a try/catch upstream or validate with /^\\d{4}-\\d{2}-\\d{2}$/.","If the input may be a datetime, extract the date portion first: value.slice(0, 10) for ISO datetime strings, or use formatDateInTimezone which accepts both date-only and legacy UTC datetime formats.","Validate at the form/API boundary and reject non-ISO dates with a user-facing message before they reach this function.","Use getTodayInTimezone(timezone) when you need 'today' rather than constructing a date string manually."],"exampleFix":"// before (throws — non-padded month)\ngetDayBoundsInUtc('2024-1-05', 'America/New_York')\n// after\ngetDayBoundsInUtc('2024-01-05', 'America/New_York')","handlingStrategy":"validation","validationCode":"const ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction isValidFilterDate(date: string): boolean {\n  if (!ISO_DATE.test(date)) return false;\n  try {\n    Temporal.PlainDate.from(date);\n    return true;\n  } catch {\n    return false;\n  }\n}\n// call: if (!isValidFilterDate(date)) { /* reject */ }","typeGuard":"function isIsoDateString(value: unknown): value is string {\n  return typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(value);\n}","tryCatchPattern":"try {\n  const bounds = getDayBoundsInUtc(date, timezone);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid filter date')) {\n    // return a 400 to the caller with the bad date\n  }\n  throw e;\n}","preventionTips":["Normalize all date inputs to ISO 8601 YYYY-MM-DD at the API boundary before they reach getDayBoundsInUtc.","Use getTodayInTimezone(timezone) instead of constructing today's date string manually.","When accepting datetime input, extract the first 10 chars (the date portion) or use formatDateInTimezone which accepts both formats."],"tags":["temporal","date-validation","filters","input-validation"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}