Skip to main content
Technology

UTC Conversion in Programming - The Right Patterns for Time Zones

Store in UTC, Display in Local

The single most important rule in handling timestamps is: store everything in UTC, convert to local time only when displaying to a user. Systems that violate this rule produce subtle bugs at daylight saving boundaries, and they break entirely when expanded to a second time zone. Every team eventually relearns this lesson, often after a production incident.

UTC has no daylight saving and is monotonically increasing, which is exactly what you want for stored data. Storing in local time creates the "two 1:30 AMs" problem at fall DST transitions: in New York, the wall-clock time 1:30 AM occurs once as EDT (UTC-4) and once as EST (UTC-5), and a stored 1:30 AM cannot tell you which one it was. UTC sidesteps this entirely by representing the moment, not the wall reading.

ISO 8601 - The String Format That Actually Tells You Time Zone

When timestamps must be serialized as text, use ISO 8601 (e.g., 2026-05-15T07:00:00Z). The trailing Z means UTC, while explicit offsets like +09:00 indicate a specific local zone. The crucial part is including time zone information at all. A bare 2026-05-15T16:00:00 with no offset is ambiguous and will trigger time zone assumptions in any consumer that parses it.

For database storage, PostgreSQL's TIMESTAMP WITH TIME ZONE (timestamptz) is the cleanest choice; it always stores UTC internally and converts to the session's zone on read. MySQL's TIMESTAMP also stores in UTC under the hood. Plain TIMESTAMP WITHOUT TIME ZONE columns require the application to enforce a UTC convention by discipline alone, which inevitably fails when teams change or new code is added.

JavaScript and TypeScript Patterns

JavaScript's Date object internally represents a UTC milliseconds-from-epoch value. Methods like toString() and toLocaleString() display in the runtime's local time zone, which is set by the browser or by the TZ environment variable in Node.js. For server applications, setting TZ=UTC in production removes guesswork from log timestamps and makes server behavior identical regardless of the host's location.

Use Intl.DateTimeFormat with a timeZone option for arbitrary-zone formatting. Specifying an IANA name like America/New_York automatically handles DST transitions. Hand-rolling offset arithmetic is unwise because DST rules change often; relying on the OS or runtime's bundled IANA database is the only sustainable approach.

The Most Common Time Zone Bugs

The most common bug is mishandling date-only fields. Storing a birthday as 1990-03-15 and parsing it with new Date('1990-03-15') causes the value to be interpreted as midnight UTC by some browsers, displaying as March 14 to a UTC-5 user. Date-only data should either include a noon UTC time component (T12:00:00Z) to avoid border crossings, or stay as a string until format-time.

A close second is configuring cron jobs in local time. On the spring DST transition, jobs scheduled for 2:30 AM are skipped because 2:30 does not exist that day. On the fall transition, jobs at 1:30 AM run twice. Defining schedules in UTC and logging timestamps in UTC eliminates the entire problem, even at the cost of occasional mental conversion when reading logs.

Testing Strategy for Time Zone Code

Tests for time zone code should cover at minimum: UTC+0 (London winter), positive offset (Tokyo UTC+9), negative offset (New York UTC-5), 30-minute offset (India UTC+5:30), DST transition boundaries, International Date Line crossings, and year-end date rollovers. Each of these can independently break code that looks correct in casual testing.

Watch out for the CI environment's time zone too. If developers run on UTC+9 locally and CI runs on UTC, tests can pass locally and fail in CI without obvious cause. The robust approach is to make tested functions accept time zone as a parameter (or freeze it via a constant in tests), removing dependence on the runtime environment. Property-based testing with libraries like fast-check or hypothesis can generate the boundary cases you would otherwise miss.

XB!LINE

Was this article helpful?

Related Articles