Why timezones are hard, how to avoid the most common bugs, and the right patterns for storing, displaying, and comparing dates.
Always store and transmit dates in UTC. Convert to local time only for display.
If you store a date in a user's local timezone, you'll have bugs the moment they travel, change their device timezone, or your system processes dates on a different server.
const now = new Date();
now.toISOString(); // '2025-02-22T14:30:00.000Z' — UTC, unambiguous
now.toLocaleString(); // '2/22/2025, 10:30:00 PM' — depends on OS timezone
-- Use TIMESTAMPTZ in PostgreSQL — stores UTC, converts for display
created_at TIMESTAMPTZ DEFAULT NOW()
-- Avoid TIMESTAMP without timezone — ambiguous when reading
const utcDate = new Date('2025-02-22T14:30:00Z');
// Display in Tokyo
utcDate.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
// '2025/2/22 23:30:00'
// Display in New York
utcDate.toLocaleString('en-US', { timeZone: 'America/New_York' });
// '2/22/2025, 9:30:00 AM'
Daylight Saving Time causes clocks to jump forward/backward, creating ambiguous or non-existent times.
If you schedule recurring events (e.g. 'every day at 9am'), store the timezone name (America/New_York), not the UTC offset (+05:00), since the offset changes with DST.