{"record":{"id":"db0a81dd86b02ce3","repo":"n8n-io/n8n","slug":"unknown-iana-timezone-json-stringify-schedule-t","errorCode":null,"errorMessage":"Unknown IANA timezone: ${JSON.stringify(schedule.timezone)}","messagePattern":"Unknown IANA timezone: (.+?)","errorType":"validation","errorClass":"InvalidScheduleError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/scheduler/src/core/recurrence/kinds/cron.ts","lineNumber":43,"sourceCode":"export function validateCron(schedule: CronSchedule | RecurringCronSchedule): void {\n\t// Raw DB rows may reach here untyped, so check the runtime type before use.\n\tconst expression: unknown = schedule.cronExpression;\n\tif (typeof expression !== 'string') {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`${schedule.kind}.cronExpression must be a string, got ${JSON.stringify(expression)}`,\n\t\t);\n\t}\n\n\tconst fieldCount = expression.trim().split(/\\s+/).length;\n\tif (fieldCount < MIN_CRON_FIELD_COUNT || fieldCount > MAX_CRON_FIELD_COUNT) {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`Cron expression must have ${MIN_CRON_FIELD_COUNT} or ${MAX_CRON_FIELD_COUNT} fields (seconds optional), got ${fieldCount}: ${JSON.stringify(expression)}`,\n\t\t);\n\t}\n\n\t// A null timezone is the instance default, resolved by the caller.\n\tif (schedule.timezone !== null && !IANAZone.isValidZone(schedule.timezone)) {\n\t\tthrow new InvalidScheduleError(`Unknown IANA timezone: ${JSON.stringify(schedule.timezone)}`);\n\t}\n\n\ttry {\n\t\tCronExpressionParser.parse(expression, { tz: schedule.timezone ?? 'UTC' });\n\t} catch (error) {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`Invalid cron expression ${JSON.stringify(expression)}: ${(error as Error).message}`,\n\t\t);\n\t}\n}\n\n/**\n * The schedule's timezone as a concrete IANA zone.\n * @param schedule The cron (or recurring_cron) schedule.\n * @returns The resolved timezone.\n * @throws {InvalidScheduleError} When the timezone is still `null` (the instance\n * default should have been resolved upstream).\n */","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/scheduler/src/core/recurrence/kinds/cron.ts#L25-L61","documentation":"Thrown by validateCron when schedule.timezone is not null AND IANAZone.isValidZone returns false. A null timezone is allowed (resolved upstream to the instance default); any non-null string must be a valid IANA zone (e.g. 'Europe/Berlin'), validated via Luxon.","triggerScenarios":"Passing timezone: 'CET', 'UTC+2', 'EST', 'Berlin', '' (empty string), or a typo'd zone name ('Europ/Berlin'). Abbreviations and fixed offsets are not IANA zones and are rejected.","commonSituations":"A user enters a timezone abbreviation instead of an IANA name; a legacy system stored 'GMT+1' style offsets; a typo in a zone name; an empty string from a form field treated as 'use default' (must be null instead).","solutions":["Use a full IANA zone name from the tz database ('Europe/Berlin', 'America/New_York', 'UTC').","Pass timezone: null (not '') to opt into the instance default.","If you have an offset, convert it to an IANA zone (e.g. UTC+01:00 in winter -> 'Europe/Berlin') or use 'UTC'.","Offer a picker seeded from Intl.supportedValuesOf('timeZone') in the UI."],"exampleFix":"// before\nvalidateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: 'CET' }); // not IANA -> throws\n\n// after - use a real IANA zone, or null for the instance default\nvalidateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: 'Europe/Berlin' });\n// or\nvalidateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: null });","handlingStrategy":"type-guard","validationCode":"import { DateTime } from 'luxon';\n\nfunction normalizeTimezone(raw: unknown): string | null {\n  if (raw === null || raw === undefined || raw === '') return null;\n  if (typeof raw !== 'string' || !DateTime.now().setZone(raw).isValid) {\n    throw new Error(`Not a valid IANA timezone: ${JSON.stringify(raw)}`);\n  }\n  return raw;\n}","typeGuard":"import { IANAZone } from 'luxon';\n\nfunction isValidIanaTimezone(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0 && IANAZone.isValidZone(v);\n}","tryCatchPattern":null,"preventionTips":["Use Intl.supportedValuesOf('timeZone') to seed a timezone picker so users cannot type invalid names.","Pass null (not '') when the user wants the instance default.","Reject abbreviations (CET, EST) and fixed offsets (GMT+1) at the boundary - require IANA names.","Store timezones verbatim; do not case-fold or truncate."],"tags":["scheduler","cron","timezone","validation"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}