Timezone bugs are sneaky. Your app works perfectly in development, your tests pass, and then you ship to production — and suddenly timestamps are off by hours. Logs don't line up. Scheduled jobs fire at the wrong time. Users in different countries see dates that make no sense.

This isn't a rare edge case. It's one of the most common sources of subtle, hard-to-reproduce bugs in web applications. The good news: once you understand a handful of core concepts, most of these problems become avoidable.

Offsets vs. Named Time Zones

Before anything else, it's worth separating two things that often get conflated:

  • UTC offset: A fixed shift from UTC, like +09:00 or -05:00. It's just a number — no rules attached.
  • Named time zone: Something like America/New_York or Europe/London. These carry DST rules, historical transitions, and policy changes baked in.

If you store -05:00, you don't know whether that means Eastern Standard Time or Eastern Daylight Time. You've lost information. If you store America/New_York, the system can correctly interpret any timestamp regardless of when it was created. For user-facing features involving scheduling, always use named zones if you can.

UTC, GMT, and ISO 8601 in Practice

UTC and GMT — what's the difference?

In everyday use, UTC and GMT are interchangeable — both have an offset of ±00:00. Technically, UTC is defined by atomic clocks and is the modern standard; GMT is an older astronomical concept. When you're writing code, use UTC.

ISO 8601 — the format everyone should be using

ISO 8601 is the international standard for representing dates and times as strings. The key feature: it includes the offset, so there's no ambiguity about which point in time you mean.

2026-01-15T10:30:00+09:00   // 10:30 AM in Japan (JST)
2026-01-15T01:30:00Z        // The same moment, expressed in UTC
2026-01-15T10:30:00         // Ambiguous — avoid this in APIs

The trailing Z means UTC (±00:00). If you're returning timestamps from an API, always include the offset. A bare datetime string with no zone info is a liability.

Code examples

In JavaScript, new Date() uses the local system timezone — which on a server is probably UTC, but on a developer's Mac is wherever they live. Don't rely on it.

// JavaScript
const now = new Date();
console.log(now.toString());       // Local time — environment dependent
console.log(now.toISOString());    // Always UTC: "2026-01-15T01:30:00.000Z"

// Display in a specific timezone (Intl API)
const display = now.toLocaleString('en-US', { timeZone: 'America/New_York' });
console.log(display); // "1/14/2026, 8:30:00 PM"
# Python — always use timezone-aware datetimes
from datetime import datetime, timezone, timedelta

# naive datetime — no timezone info, leads to bugs
bad = datetime.now()

# UTC-aware datetime (recommended)
utc_now = datetime.now(timezone.utc)
print(utc_now.isoformat())  # 2026-01-15T01:30:00+00:00

# Convert to Eastern time
eastern = timezone(timedelta(hours=-5))
print(utc_now.astimezone(eastern).isoformat())  # 2026-01-14T20:30:00-05:00

Always store UTC in your database

This is the single most important rule: store timestamps in UTC, convert on display. It doesn't matter where your server is, where your users are, or whether DST is in effect. UTC is a single, unambiguous reference point. When a user in Tokyo and a user in New York both submit a form at the "same time," UTC makes that sortable and comparable.

-- PostgreSQL: use TIMESTAMPTZ (stores as UTC, converts on retrieval)
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  title TEXT,
  happens_at TIMESTAMPTZ NOT NULL
);

-- MySQL: use UTC_TIMESTAMP() for inserts
INSERT INTO events (title, happens_at) VALUES ('Deploy', UTC_TIMESTAMP());

Need to convert between zones quickly? The timezone converter handles it without any code. For working with Unix timestamps, the Unix timestamp converter is the fastest way to go back and forth. And if you need to find the duration between two moments, try the timestamp diff calculator.

Common Mistakes That Bite You in Production

  • Passing local time to an API: If your backend is in UTC and you send 2026-01-15T10:30:00 without an offset, the server interprets it as UTC — which may be 9 hours off from what you intended.
  • Forgetting DST during date arithmetic: Adding 24 hours to a timestamp is not the same as adding one calendar day during a DST transition. Use date libraries that understand named zones.
  • Mixing offsets and zone names: Storing -05:00 in one place and America/Chicago in another creates inconsistency. Pick one approach and stick to it.
  • Assuming the server timezone matches the user's: Cloud servers typically run in UTC. Never assume they share a timezone with your users or your local machine.

FAQ

Does JavaScript's new Date() use UTC internally?
Yes — JavaScript's Date object stores time as milliseconds since the Unix epoch (which is UTC-based). But toString(), getHours(), and similar methods return local time. Use toISOString() or UTC methods like getUTCHours() to work in UTC explicitly.
What's the actual difference between GMT and UTC?
GMT is based on the Earth's rotation relative to the Sun — it's an astronomical standard. UTC is based on atomic clocks and is maintained by international agreement. They're usually identical, but UTC is the one used in computing. When you see "GMT" in a browser or HTTP header, it's effectively UTC.
How do I handle DST correctly in my application?
The safest approach is to always store and transmit UTC, then convert to the user's named timezone (e.g., America/Chicago) only when displaying. Use a library that has up-to-date IANA timezone data — in JavaScript that's the built-in Intl API or libraries like date-fns-tz, in Python it's zoneinfo (Python 3.9+) or pytz. These handle DST transitions automatically.
Are Unix timestamps affected by timezones?
No. A Unix timestamp is the number of seconds since January 1, 1970, 00:00:00 UTC — it's a single global value with no timezone attached. Two systems in different parts of the world that capture the same moment will get the same Unix timestamp. The timezone only comes into play when you convert that number to a human-readable date.

Summary

  • Store all timestamps in UTC — convert to local time only for display
  • Use ISO 8601 with an explicit offset in API responses; never send bare datetime strings
  • Distinguish between UTC offsets (+09:00) and named zones (Asia/Tokyo) — they're not the same thing
  • Use DST-aware libraries with IANA timezone data, not manual offset math
  • In JavaScript, toISOString() is UTC; toString() is local — know which one you're using

For quick timezone conversions and calculations without writing any code: