For Developers

Timezones in Node.js

Node's Date object quietly assumes the server's local timezone the moment you ask it to format anything — here's how to stop it from doing that.

Node's built-in Date object is the source of most timezone bugs in JavaScript backends — not because it's badly designed, but because it only really knows two timezones: UTC and whatever timezone the server happens to be running in. Anything else requires deliberate handling.

The core problem: Date has no real timezone concept

A JavaScript Date internally stores a single instant (milliseconds since the Unix epoch, UTC) — that part is fine and unambiguous. The trouble starts with methods like getHours() or toLocaleString(), which silently use the server's local timezone. That's invisible in local development, where "the server" is your laptop, and becomes a live bug the moment the app is deployed somewhere with a different system timezone, or needs to display a time correctly for a user in a third timezone entirely.

What actually works

Store and pass timestamps as UTC everywhere in your backend logic — as epoch milliseconds or an ISO 8601 string with a Z, never as a "local" string. Only convert to a specific timezone at the final point of display, and do that conversion explicitly rather than relying on the server's default: Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York' }).format(date) asks for a specific zone by name instead of trusting whatever the machine happens to be set to. This is built into modern Node and doesn't require a library for straightforward formatting.

For anything beyond formatting — calculating "3 months from now" across a DST boundary, or comparing durations across zones — reach for a proper library rather than hand-rolling the math. date-fns-tz and Luxon are the two most commonly reached for in current Node projects; both make the timezone an explicit, visible parameter instead of an ambient assumption.

A concrete failure mode worth watching for

Storing a user's "preferred meeting time" as a plain 24-hour string like "14:00" without a timezone attached is a common shortcut that works fine until daylight saving time shifts, or until the user travels, or until a second user in a different zone needs to see the same value. Store the timezone identifier ("America/Chicago", not a fixed offset like -05:00) alongside the time — offsets change twice a year in DST-observing regions, but timezone names don't.

Related

Related tools & reading