Anúncios incomodam? Ir Sem anúncios Hoje

Time Zones Are a Lie (And How to Handle Them in Code)

Atualizado em

Time zones look like simple UTC offsets. They're not. This guide breaks down why time zones break code — DST gaps, half-hour offsets, naive datetimes — and how to handle them correctly in JavaScript, Python, PHP, and SQL.

Time Zones Are a Lie (And How to Handle Them in Code) 1
ANUNCIADO Remover?

You asked your server what time it is. It said 14:00. You stored it. You queried it back. Now it says 16:00. You didn’t change anything. Welcome to time zones.

Time zones are one of the most deceptively complex problems in software. On the surface, they look like a simple UTC offset — just add or subtract some hours. Under the hood, they’re a chaotic mix of political decisions, historical accidents, half-hour offsets, and daylight saving rules that change with little notice. This article breaks down why time zones are so painful and, more importantly, how to actually handle them correctly in code.

Why “Just Use UTC” Is Only Half the Answer

The most common advice you’ll hear is: store everything in UTC. That advice is correct — but it’s incomplete. Storing in UTC solves uma problem (consistent storage) while leaving a bigger one unsolved: display and input.

A user in Tokyo schedules a meeting at 9 AM their time. You store that as UTC. Later, a user in New York opens the same event. You render it in their local time. But whose local time applies when the user is travelling? When they update their system clock? When daylight saving kicks in? “Store UTC, display local” is sound policy — it’s the execution that breaks most teams.

The Actual Problems With Time Zones

Before we get to solutions, it helps to name what you’re actually fighting.

1. UTC Offsets Are Not Time Zones

UTC+5:30 is not “India time.” It’s a static offset. India Standard Time is Asia/Kolkata — a named time zone that happens to use +5:30 and has never observed daylight saving. These are different things. If you hardcode an offset, you’re storing a number. If you store a named zone, you’re storing intent.

Named zones live in the IANA Time Zone Database (also called tzdata or the Olson database). Every serious language and OS ships a copy of it. Use it. Always prefer America/New_York over UTC-5.

2. Daylight Saving Time Moves the Clocks (Unpredictably)

DST transitions create two genuinely dangerous edge cases:

  • The “spring forward” gap: Clocks jump from 2:00 AM to 3:00 AM. The time 2:30 AM literally doesn’t exist. If you try to schedule something at 2:30 AM on that day, you’ll get either an error or silent misbehavior depending on your library.
  • The “fall back” ambiguity: Clocks go from 2:00 AM back to 1:00 AM. The time 1:30 AM now happens twice. Without extra context (the fold flag), you can’t tell which occurrence you mean.

And DST rules change. Countries and US states have switched, abolished, or modified their rules in living memory. Your code’s behavior depends on having an up-to-date tzdata package — that’s an ops concern, not just a dev concern.

3. Not All Offsets Are Whole Hours

India is UTC+5:30. Nepal is UTC+5:45. Parts of Australia are UTC+9:30. Iran is UTC+3:30 in winter and UTC+4:30 in summer. If your system assumes time zones are always on the hour, it will silently corrupt timestamps for millions of users.

4. “Local Time” on a Server Is Meaningless

Servers have system clocks. Those clocks have a configured time zone — often UTC, sometimes whatever the hosting provider defaulted to, sometimes whatever a sysadmin set years ago. Code that calls new Date() ou datetime.now() without specifying a zone is implicitly relying on that server config. Different environments will give different results. This is a bug waiting to happen on every deployment.

The Right Approach, Language by Language

JavaScript / TypeScript

The native Date object in JavaScript is a thin wrapper around a UTC millisecond timestamp. It looks friendly; it isn’t. Avoid formatting it manually — use the Intl.DateTimeFormat API for display, or reach for a library.

The modern standard is Temporal — a TC39 proposal that shipped in Chrome 121 and is coming to all major runtimes. It has first-class support for time zones and is the right long-term answer:

// Store an instant (UTC-equivalent)
const meeting = Temporal.Instant.from("2025-06-15T14:00:00Z");

// Display in a specific zone
const nyTime = meeting.toZonedDateTimeISO("America/New_York");
console.log(nyTime.toString()); // 2025-06-15T10:00:00-04:00[America/New_York]

// Convert to Tokyo time
const tokyoTime = meeting.toZonedDateTimeISO("Asia/Tokyo");
console.log(tokyoTime.toString()); // 2025-06-16T23:00:00+09:00[Asia/Tokyo]

If you can’t use Temporal yet, date-fns-tz paired with date-fns is a reliable choice. Luxon is another solid option. Moment.js is feature-complete but no longer maintained — migrate away from it.

Pitão

Python’s datetime module distinguishes between “naive” datetimes (no time zone info) and “aware” datetimes (with tzinfo). Naive datetimes are a trap — they look like valid times but carry no meaning across systems.

Always use aware datetimes. Use the zoneinfo module (Python 3.9+) for IANA time zone support:

from datetime import datetime
from zoneinfo import ZoneInfo

# Aware datetime — always do this
utc_time = datetime(2025, 6, 15, 14, 0, 0, tzinfo=ZoneInfo("UTC"))

# Convert to New York time
ny_time = utc_time.astimezone(ZoneInfo("America/New_York"))
print(ny_time)  # 2025-06-15 10:00:00-04:00

# Never do this — naive datetime, meaningless
bad = datetime(2025, 6, 15, 14, 0, 0)  # what zone is this?

For Python 3.8 and earlier, use the pytz library — but be careful with pytz‘s localize/normalize API, which has footguns that zoneinfo avoids.

PHP

PHP’s DateTime e DateTimeImmutable classes support IANA zones natively via DateTimeZone. Prefer DateTimeImmutable — it’s safer because mutations return new objects rather than modifying in place.

$utc = new DateTimeImmutable('2025-06-15T14:00:00', new DateTimeZone('UTC'));

// Convert to Sydney time
$sydney = $utc->setTimezone(new DateTimeZone('Australia/Sydney'));
echo $sydney->format('Y-m-d H:i:s T'); // 2025-06-16 00:00:00 AEST

// Store timestamps as ISO 8601 strings or Unix timestamps
echo $utc->getTimestamp(); // 1749996000

SQL / Databases

Database time zone handling is its own minefield.

  • PostgreSQL: Use o TIMESTAMPTZ (timestamp with time zone) — it stores everything as UTC and converts on output. Never use TIMESTAMP (without time zone) for user-facing data; it stores whatever you give it with no conversion.
  • MySQL: O DATETIME type is naive (no zone). Use TIMESTAMP if you want UTC storage, but note its range is limited to 2038. For new schemas, storing as VARCHAR in ISO 8601 format or as a Unix timestamp in BIGINT is often safer.
  • SQLite: Has no native time zone type. Store as ISO 8601 text (2025-06-15T14:00:00Z) or as a Unix integer. Handle conversion in application code.

Whatever database you use, set the server’s session time zone explicitly rather than relying on defaults.

Practical Rules That Actually Work

After working through the theory, here are the concrete rules that prevent most time zone bugs:

  1. Store UTC everywhere. Every timestamp in your database should be UTC. No exceptions for “it’s only used internally.”
  2. Use IANA zone names, not offsets. Store America/Chicago alongside a UTC timestamp when you need to reconstruct the original local time. The offset alone is not recoverable after a DST transition.
  3. Never call “now” without a zone. datetime.now(), new Date(), time() — always pass an explicit timezone argument or immediately attach one.
  4. Convert to local time at the last moment. Do all date math in UTC. Only convert to a user’s local time zone immediately before display.
  5. Keep tzdata up to date. Time zone rules change. Make tzdata package updates part of your regular dependency management.
  6. Test around DST transitions. The two dangerous dates each year (spring forward, fall back) should be in your test suite if you handle scheduling or time-sensitive data.
  7. Ask users for their time zone explicitly. Don’t rely on IP geolocation or browser guessing for anything important. Show a time zone picker; store the result.

A Word on Unix Timestamps

Unix timestamps — seconds since 1970-01-01T00:00:00Z — are time-zone-agnostic by definition. They’re a perfectly valid storage format and are especially useful in logs, APIs, and caches where you want an unambiguous single number.

The catch: Unix timestamps don’t carry a user’s original intent. If someone books a flight for “Friday morning London time” and you store only a Unix timestamp, you’ve lost the fact that they meant London. When the flight is rebooked three months later and London has shifted in or out of BST, you may display the wrong local time. Store the zone alongside the timestamp whenever user intent matters.

The Hardest Case: Recurring Events

Recurring events — weekly stand-ups, monthly billing cycles, daily reminders — expose every time zone edge case at once.

Consider a “every Monday at 9 AM” rule for a user in Los Angeles. Do you store 9 AM Pacific? What happens when they travel to Tokyo? What happens when clocks spring forward and that Monday falls on the transition date?

The only correct model is:

  • Store the recurrence rule as wall-clock time + IANA zone: “Monday 09:00 America/Los_Angeles”
  • Compute the next UTC occurrence at scheduling time, not at rule creation time
  • Recompute after any DST transition that falls within the recurrence window

Libraries like rrule.js (JS) and dateutil.rrule (Python) handle this correctly when given proper time zone context. Hand-rolling this logic is a reliable path to subtle bugs that surface months later.

Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

O placar chegou!

Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?