{"record":{"id":"ef4ab60cdab0d689","repo":"TryGhost/Ghost","slug":"invalid-timezone-timezone","errorCode":null,"errorMessage":"Invalid timezone: ${timezone}","messagePattern":"Invalid timezone: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/admin/src/shared/filters/filter-normalization.ts","lineNumber":44,"sourceCode":"export 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":26,"sourceCodeEnd":47,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/shared/filters/filter-normalization.ts#L26-L47","documentation":"getDayBoundsInUtc converts a valid PlainDate into UTC instants by combining it with a timezone string via toZonedDateTime(timezone). If the timezone string is not a valid IANA timezone identifier (e.g. 'America/New_York') or a fixed offset, Temporal throws and the catch re-throws with the offending timezone value. This is separate from the date validity check so the two failure modes are distinguishable.","triggerScenarios":"Calling getDayBoundsInUtc with a timezone argument that is not a valid IANA timezone name. Examples: 'America/NY' (abbreviation, not full name), 'EST' (abbreviation), 'UTC+5' (wrong format — should be 'Etc/GMT-5' or offset syntax), 'Eastern Time', an empty string, or a typo like 'America/New_Yrok'.","commonSituations":"The site/publication timezone is misconfigured in Ghost settings and stored as an abbreviation or display name instead of an IANA identifier. A user-entered timezone from a custom integration is not validated. A DST abbreviation like 'PDT' is passed where 'America/Los_Angeles' is required.","solutions":["Use a valid IANA timezone identifier: 'America/New_York', 'Europe/London', 'Australia/Sydney', 'UTC'.","Validate the timezone at the source: Intl.supportedValuesOf('timeZone') (modern browsers) or a known list before persisting it.","If the timezone comes from Ghost settings, ensure the settings UI only offers IANA identifiers.","Default to 'UTC' when the configured timezone is missing or suspect, with a logged warning."],"exampleFix":"// before (throws — abbreviation)\ngetDayBoundsInUtc('2024-01-05', 'EST')\n// after\ngetDayBoundsInUtc('2024-01-05', 'America/New_York')","handlingStrategy":"validation","validationCode":"function isValidTimezone(tz: string): boolean {\n  try {\n    Temporal.TimeZone.from(tz);\n    return true;\n  } catch {\n    return false;\n  }\n}\n// or use Intl: Intl.supportedValuesOf?.('timeZone').includes(tz) in modern browsers","typeGuard":"function isIanaTimezone(value: unknown): value is string {\n  if (typeof value !== 'string') return false;\n  try {\n    Temporal.TimeZone.from(value);\n    return true;\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  const bounds = getDayBoundsInUtc(date, timezone);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid timezone')) {\n    // fall back to UTC and log\n    return getDayBoundsInUtc(date, 'UTC');\n  }\n  throw e;\n}","preventionTips":["Store and accept only IANA timezone identifiers (America/New_York, Europe/London, UTC).","Validate timezone at the settings form using Intl.supportedValuesOf('timeZone') or a known list.","Default to 'UTC' with a warning when the configured timezone fails validation."],"tags":["temporal","timezone","filters","input-validation"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}