🕐Developer

Timezones in Software: Why They're Hard and How to Handle Them

Timezone bugs are embarrassing, hard to debug, and ruin user trust. Here's a practical guide to handling time correctly in web applications.

5 min readFebruary 25, 2026By FreeToolKit TeamFree to read

Every developer hits the timezone wall eventually. The calendar event that shows up an hour early. The 'yesterday's report' that includes today's data. The birthday reminder that fires at 2am for users in Tokyo. Timezone bugs are rarely caught in testing because developers test in their own timezone.

The Rules (Follow These Without Exception)

  • Store all timestamps in UTC — no exceptions
  • Use Unix timestamps (milliseconds) for storage and API communication
  • Convert to local time only at display time
  • Never use system local time in server code (server timezone ≠ user timezone)
  • Use IANA timezone names ('America/New_York'), not abbreviations ('EST' — ambiguous)
  • Let timezone libraries handle DST — don't implement DST logic yourself

Common Timezone Bugs

The 'off by one day' bug: a user creates an event at 11pm on January 5th in New York. In UTC, that's 4am January 6th. If you query for 'events on January 5th' using UTC midnight boundaries, you miss the event. Fix: query for events in the user's timezone, or use timezone-aware date boundaries.

The server timezone bug: your Node.js server runs in UTC. Your PostgreSQL database runs in UTC. Your development Mac runs in Pacific time. When you write new Date().toLocaleDateString() in server code, it uses the server's timezone — UTC, not the user's. Server-side code must never use local time functions.

The Database Column Type

PostgreSQL has TIMESTAMP and TIMESTAMPTZ (timestamp with time zone). Always use TIMESTAMPTZ — it stores everything in UTC and handles DST-aware conversions. TIMESTAMP stores exactly what you give it without timezone information, which sounds flexible but creates ambiguity. MySQL: DATETIME stores without timezone; TIMESTAMP stores in UTC and converts. Use TIMESTAMP (UTC) in MySQL.

Frequently Asked Questions

Should I store timestamps in UTC?+
Yes, always. Store timestamps in UTC, display them in the user's local timezone. This is the universal rule. Storing local times creates a maze of edge cases: a meeting scheduled for 2pm PST looks like it's at 10pm UTC in your database. When you query for 'events today,' which timezone does 'today' mean? UTC is the single source of truth that all other timezones derive from. Convert to local time only at display time, using the user's known timezone. Everything in the database stays UTC.
What's the difference between a timestamp and a datetime?+
A Unix timestamp is a single number: seconds (or milliseconds) since January 1, 1970 UTC. It's unambiguous, timezone-agnostic, and universally understood by every programming language. A datetime is a human-readable representation (2026-02-25 14:30:00) that, without a timezone annotation, is ambiguous. Always prefer Unix timestamps for storage and API communication. Use ISO 8601 with timezone offset (2026-02-25T14:30:00Z or 2026-02-25T14:30:00+05:30) when human-readable format is required.
How do I convert between timezones in JavaScript?+
The Intl API handles this natively: new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', ... }).format(date). For applications requiring timezone arithmetic (not just display), use the date-fns-tz library or Temporal (the newer, still-emerging date API). Avoid moment.js for new projects — it's legacy and has a large bundle size. The critical mistake to avoid: using JavaScript's getHours() without specifying a timezone — it always uses the local system timezone, which is the developer's machine timezone, not the user's.
How do I handle daylight saving time (DST) in code?+
The answer is: don't handle DST yourself. Use established timezone libraries (date-fns-tz, Luxon, or the platform's built-in Intl support) that have DST rules built in. DST rules change — countries add, remove, or modify DST transitions, and timezone databases (the IANA timezone database) are updated accordingly. If you're doing timestamp arithmetic ('add 30 days'), always convert to UTC first, do the arithmetic in UTC, then convert back to display timezone. Never add 86400 seconds to a timestamp assuming that's exactly one day — during DST transitions, some days are 23 or 25 hours.

🔧 Free Tools Used in This Guide

FT

FreeToolKit Team

FreeToolKit Team

We build free browser-based tools and write practical guides that skip the fluff.

Tags:

developerdatesbackendjavascript