A lightweight and robust Zod schema for validating IANA timezone strings, with flexible handling of canonical and non-canonical names.
This library leverages the native Intl API, ensuring that timezone validation is always up-to-date with the user's environment without needing to bundle large timezone databases.
- Three Validation Strategies:
- Strictly canonical names only.
- Allow valid, but non-canonical names.
- Transform non-canonical names to their canonical equivalents (e.g.,
Asia/Calcutta->Asia/Kolkata).
- Factory-based API: Create isolated schema instances with
createTimezoneSchemas(options?)— no global state, safe for concurrent use with different configs. - Configurable Canonical Mode: Choose between runtime-based (
Intl) or strict IANA canonical names (solves runtime-related discrepancies) for consistent cross-environment behavior. - Custom Mappings: Override or extend timezone mappings for edge cases.
- Zero Dependencies: Relies only on
zod(v4+) as a peer dependency. - Lightweight: No bundled timezone data, uses the environment's native
IntlAPI. - Fully Typed: Written in TypeScript with branded types for extra type safety.
createTimezoneSchemas(options?): Factory function that returns an object with three schemas:CoercedCanonicalTimezoneSchema: Validates a timezone and transforms it to its canonical form (e.g.,US/EasternbecomesAmerica/New_York). Infers branded typeCanonicalTimezone.CanonicalTimezoneSchema: Ensures a timezone is strictly in its canonical form. Rejects valid but non-canonical names. Infers branded typeCanonicalTimezone.TimezoneSchema: Validates any valid timezone, allowing both canonical and non-canonical names without transformation. Infers branded typeTimezone.
CanonicalMode: Type for the canonical mode option ('runtime'|'iana').TimezoneSchemaOptions: Type for the configuration options object.TimezoneMapping: Type for a custom mapping tuple[nonCanonical, canonical].TimezoneSchemas: Interface describing the object returned bycreateTimezoneSchemas.CanonicalTimezone: Brandedstringtype for a canonical timezone.Timezone: Brandedstringtype for any valid timezone.
npm install zod-timezone-validationor
yarn add zod-timezone-validationNote: zod v4+ is a peer dependency and must be installed in your project.
Create your schemas with createTimezoneSchemas() and destructure the ones you need:
import { createTimezoneSchemas } from 'zod-timezone-validation';
const {
CoercedCanonicalTimezoneSchema,
CanonicalTimezoneSchema,
TimezoneSchema,
} = createTimezoneSchemas();This schema validates that a string is a valid IANA timezone. If a non-canonical name is provided, it automatically transforms it into its canonical equivalent.
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { CoercedCanonicalTimezoneSchema } = createTimezoneSchemas();
// Transforms non-canonical to canonical
const result1 = CoercedCanonicalTimezoneSchema.parse('Asia/Calcutta');
console.log(result1); // => 'Asia/Kolkata'
// Keeps canonical names as they are
const result2 = CoercedCanonicalTimezoneSchema.parse('America/New_York');
console.log(result2); // => 'America/New_York'
// Throws an error for invalid timezones
try {
CoercedCanonicalTimezoneSchema.parse('Invalid/Timezone');
} catch (e) {
console.error(e); // ZodError
}This schema ensures the provided string is a strictly canonical IANA timezone name. It will reject any valid but non-canonical names.
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { CanonicalTimezoneSchema } = createTimezoneSchemas();
// Accepts canonical names
const result = CanonicalTimezoneSchema.parse('Europe/London');
console.log(result); // => 'Europe/London'
// Rejects non-canonical names
try {
CanonicalTimezoneSchema.parse('GB'); // 'GB' is a non-canonical alias for 'Europe/London'
} catch (e) {
console.error(e); // ZodError
}This schema is more lenient and validates that a string is a valid IANA timezone, allowing both canonical and non-canonical names without transformation.
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { TimezoneSchema } = createTimezoneSchemas();
// Accepts canonical names
const result1 = TimezoneSchema.parse('Australia/Sydney');
console.log(result1); // => 'Australia/Sydney'
// Also accepts non-canonical names
const result2 = TimezoneSchema.parse('US/Eastern');
console.log(result2); // => 'US/Eastern'
// Throws an error for invalid timezones
try {
TimezoneSchema.parse('Mars/Olympus_Mons');
} catch (e) {
console.error(e); // ZodError
}Pass options to createTimezoneSchemas() to customize behavior. Each call returns an independent set of schemas, so you can use different configurations side by side.
Different JavaScript runtimes (browsers, Node.js) may disagree on what constitutes a "canonical" timezone name. For example, Chromium-based browsers may consider Asia/Calcutta canonical, while Node.js and Firefox use Asia/Kolkata.
By default, the library uses strict IANA canonical names for consistent cross-environment behavior. You can opt into runtime mode if you prefer to follow the environment's Intl implementation:
import { createTimezoneSchemas } from 'zod-timezone-validation';
// Default: strict IANA canonical names (consistent across environments)
const { CoercedCanonicalTimezoneSchema } = createTimezoneSchemas();
// Asia/Calcutta will always become Asia/Kolkata, regardless of the runtime
const result = CoercedCanonicalTimezoneSchema.parse('Asia/Calcutta');
console.log(result); // => 'Asia/Kolkata'
// Opt into runtime's Intl implementation (may vary by environment)
const { CoercedCanonicalTimezoneSchema: RuntimeSchema } = createTimezoneSchemas({
canonicalMode: 'runtime',
});Available modes:
'iana'(default): Uses strict IANA-compliant canonical names (Intlwith conflicts resolution layer). Provides consistent behavior across all environments.'runtime': Uses the runtime'sIntlimplementation. Results may vary between environments.
You can provide custom timezone mappings that override the built-in behavior. Mappings are defined as an array of case-insensitive tuples [nonCanonicalName, canonicalName]:
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { CoercedCanonicalTimezoneSchema } = createTimezoneSchemas({
// case-insensitive keys and values
customMappings: [
// Force a specific mapping
['Asia/Calcutta', 'Asia/Kolkata'],
// Add a custom alias
['my/customzone', 'America/New_York'],
// Introduce a new timezone name (will be assumed valid and canonical)
['legacy/zone', 'My/New/Canonical'],
],
});
const result1 = CoercedCanonicalTimezoneSchema.parse('My/CustomZone');
console.log(result1); // => 'America/New_York'
const result2 = CoercedCanonicalTimezoneSchema.parse('Legacy/Zone');
console.log(result2); // => 'My/New/Canonical'Key features:
- Keys (non-canonical names) are matched case-insensitively.
- Values (canonical names) are treated as valid canonical names. If the value doesn't exist in the built-in canonical sets, it becomes a new valid canonical name.
- Custom mappings take precedence over both IANA and runtime mappings.
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { CoercedCanonicalTimezoneSchema, CanonicalTimezoneSchema } = createTimezoneSchemas({
canonicalMode: 'runtime',
customMappings: [
['legacy/internal', 'America/Chicago'],
],
});While this library focuses on validating timezone strings, it doesn't provide an exhaustive list of them. The environment already provides a way to get a list of all supported IANA timezone names through the native Intl API.
You can get an array of all available timezones like this:
const availableTimezones = Intl.supportedValuesOf('timeZone');
console.log(availableTimezones);
// => ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ..., 'Zulu']This approach ensures that you are working with the timezones supported by the user's runtime environment (browser or server), without needing to bundle a large, static list.
v2 replaces global mutable schemas with a factory function:
// Before (v1.x)
import {
CoercedCanonicalTimezoneSchema,
CanonicalTimezoneSchema,
TimezoneSchema,
configureTimezoneSchema,
} from 'zod-timezone-validation';
configureTimezoneSchema({ canonicalMode: 'iana' });
CoercedCanonicalTimezoneSchema.parse('Asia/Calcutta');
// After (v2)
import { createTimezoneSchemas } from 'zod-timezone-validation';
const { CoercedCanonicalTimezoneSchema } = createTimezoneSchemas();
CoercedCanonicalTimezoneSchema.parse('Asia/Calcutta');Breaking changes:
configureTimezoneSchema()is removed — pass options tocreateTimezoneSchemas()instead.- Schemas are no longer global singletons — each
createTimezoneSchemas()call returns independent instances. - Default canonical mode changed from
'runtime'to'iana'for consistent cross-environment behavior. - Requires
zodv4+.
This project is licensed under the MIT License.
