Skip to content

Roman86/zod-timezone-validation

Repository files navigation

Zod Timezone Validation

npm version Buy Me A Coffee GitHub Sponsors

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.

Key Features

  • 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 Intl API.
  • Fully Typed: Written in TypeScript with branded types for extra type safety.

Exports

  • 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/Eastern becomes America/New_York). Infers branded type CanonicalTimezone.
    • CanonicalTimezoneSchema: Ensures a timezone is strictly in its canonical form. Rejects valid but non-canonical names. Infers branded type CanonicalTimezone.
    • TimezoneSchema: Validates any valid timezone, allowing both canonical and non-canonical names without transformation. Infers branded type Timezone.
  • 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 by createTimezoneSchemas.
  • CanonicalTimezone: Branded string type for a canonical timezone.
  • Timezone: Branded string type for any valid timezone.

❤️ Enjoying this package? Consider buying me a coffee as a token of appreciation!

Buy Me A Coffee

Installation

npm install zod-timezone-validation

or

yarn add zod-timezone-validation

Note: zod v4+ is a peer dependency and must be installed in your project.

Usage

Create your schemas with createTimezoneSchemas() and destructure the ones you need:

import { createTimezoneSchemas } from 'zod-timezone-validation';

const {
  CoercedCanonicalTimezoneSchema,
  CanonicalTimezoneSchema,
  TimezoneSchema,
} = createTimezoneSchemas();

CoercedCanonicalTimezoneSchema

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
}

CanonicalTimezoneSchema

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
}

TimezoneSchema

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
}

Configuration

Pass options to createTimezoneSchemas() to customize behavior. Each call returns an independent set of schemas, so you can use different configurations side by side.

Canonical Mode

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 (Intl with conflicts resolution layer). Provides consistent behavior across all environments.
  • 'runtime': Uses the runtime's Intl implementation. Results may vary between environments.

Custom Mappings

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.

Combining Options

import { createTimezoneSchemas } from 'zod-timezone-validation';

const { CoercedCanonicalTimezoneSchema, CanonicalTimezoneSchema } = createTimezoneSchemas({
  canonicalMode: 'runtime',
  customMappings: [
    ['legacy/internal', 'America/Chicago'],
  ],
});

Getting a List of Timezones

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.

Migration from v1.x

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 to createTimezoneSchemas() 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 zod v4+.

License

This project is licensed under the MIT License.

About

Zod schemas for validating timezone strings, including non-canonical names.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors