n8n-io/n8n · error · InvalidScheduleError
Unknown IANA timezone: ${JSON.stringify(schedule.timezone)}
Error message
Unknown IANA timezone: ${JSON.stringify(schedule.timezone)} What it means
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.
Source
Thrown at packages/@n8n/scheduler/src/core/recurrence/kinds/cron.ts:43
export function validateCron(schedule: CronSchedule | RecurringCronSchedule): void {
// Raw DB rows may reach here untyped, so check the runtime type before use.
const expression: unknown = schedule.cronExpression;
if (typeof expression !== 'string') {
throw new InvalidScheduleError(
`${schedule.kind}.cronExpression must be a string, got ${JSON.stringify(expression)}`,
);
}
const fieldCount = expression.trim().split(/\s+/).length;
if (fieldCount < MIN_CRON_FIELD_COUNT || fieldCount > MAX_CRON_FIELD_COUNT) {
throw new InvalidScheduleError(
`Cron expression must have ${MIN_CRON_FIELD_COUNT} or ${MAX_CRON_FIELD_COUNT} fields (seconds optional), got ${fieldCount}: ${JSON.stringify(expression)}`,
);
}
// A null timezone is the instance default, resolved by the caller.
if (schedule.timezone !== null && !IANAZone.isValidZone(schedule.timezone)) {
throw new InvalidScheduleError(`Unknown IANA timezone: ${JSON.stringify(schedule.timezone)}`);
}
try {
CronExpressionParser.parse(expression, { tz: schedule.timezone ?? 'UTC' });
} catch (error) {
throw new InvalidScheduleError(
`Invalid cron expression ${JSON.stringify(expression)}: ${(error as Error).message}`,
);
}
}
/**
* The schedule's timezone as a concrete IANA zone.
* @param schedule The cron (or recurring_cron) schedule.
* @returns The resolved timezone.
* @throws {InvalidScheduleError} When the timezone is still `null` (the instance
* default should have been resolved upstream).
*/View on GitHub (pinned to 5ac6606e81)
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.
Example fix
// before
validateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: 'CET' }); // not IANA -> throws
// after - use a real IANA zone, or null for the instance default
validateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: 'Europe/Berlin' });
// or
validateCron({ kind: 'cron', cronExpression: '0 9 * * *', timezone: null }); Defensive patterns
Strategy: type-guard
Validate before calling
import { DateTime } from 'luxon';
function normalizeTimezone(raw: unknown): string | null {
if (raw === null || raw === undefined || raw === '') return null;
if (typeof raw !== 'string' || !DateTime.now().setZone(raw).isValid) {
throw new Error(`Not a valid IANA timezone: ${JSON.stringify(raw)}`);
}
return raw;
} Type guard
import { IANAZone } from 'luxon';
function isValidIanaTimezone(v: unknown): v is string {
return typeof v === 'string' && v.length > 0 && IANAZone.isValidZone(v);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Cron expression must have ${MIN_CRON_FIELD_COUNT} or ${MAX_C
- Invalid cron expression ${JSON.stringify(expression)}: ${(er
- Failed to evaluate cron expression ${JSON.stringify(cronExpr
- jitterRatio must be at least 0 and below 1, got ${lifecycleO
- ${key} must be a positive number of seconds, got ${value}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/db0a81dd86b02ce3.
Report an issue: GitHub.