The Complete Guide to Timezone Conversion for Developers

17 Jun 2026 1,106 words

The Complete Guide to Timezone Conversion for Developers

Timezone conversion is one of the most notoriously tricky problems in software development. Dates and times are conceptually simple — a moment in time — but how we represent, store, and display them varies wildly across the globe. This guide covers everything a developer needs to know about timezone conversion: UTC offsets, IANA timezone identifiers, daylight saving time, ISO 8601 formatting, and how to avoid the most common bugs.

UTC: The Universal Reference Point

Coordinated Universal Time (UTC) is the primary time standard by which the world regulates clocks. It never changes for daylight saving and provides a fixed reference that every other timezone is expressed as an offset from. When storing timestamps in a database, UTC is the safest choice because it eliminates ambiguity.

UTC+0: London (winter), Reykjavik, Accra
UTC+1: Paris (winter), Berlin, Rome, Madrid
UTC+2: Athens, Cairo, Jerusalem
UTC-5: New York (winter), Bogota, Lima
UTC+8: Beijing, Singapore, Perth

UTC Offsets vs IANA Timezones

A UTC offset like +05:30 tells you the difference from UTC but does not account for daylight saving time. An IANA timezone identifier like Asia/Kolkata or America/New_York encodes the full history of UTC offsets, DST rules, and any political changes for a geographic region.

Offset IANA Timezone DST Observed
UTC+0 Europe/London Yes (BST in summer)
UTC+1 Europe/Paris Yes (CEST in summer)
UTC+5:30 Asia/Kolkata No
UTC-5 America/New_York Yes (EDT in summer)
UTC+8 Asia/Singapore No

Always use IANA timezone identifiers in your code, never hardcoded offsets. An offset like UTC-5 could refer to New York in winter, Chicago in winter, or any number of locations — and it does not tell you when DST starts or ends.

How Daylight Saving Time Complicates Everything

Daylight saving time introduces two kinds of ambiguity:

Spring forward: Clocks jump from 02:00 to 03:00. The hour from 02:00 to 02:59 simply does not exist. If a user enters 02:30 on that day, your application must decide whether to reject the input, push it to 03:00, or throw an error.

Fall back: Clocks fall from 03:00 back to 02:00. The hour from 01:00 to 01:59 occurs twice. If a user enters 01:30 on that day, your application must decide which occurrence — the first (DST) or the second (standard) — is intended.

// JavaScript — detecting DST ambiguity
function getAmbiguityInfo(dateStr, timezone) {
  const date = new Date(dateStr);
  const options = { timeZone: timezone, hour: 'numeric', minute: 'numeric' };
  const first = new Intl.DateTimeFormat('en-US', {
    ...options,
    hourCycle: 'h23'
  }).format(date);
  return { input: dateStr, formatted: first, timezone };
}

ISO 8601: The Standard Format

ISO 8601 is the international standard for date and time representation. It is unambiguous, sortable, and widely supported across programming languages.

2026-06-17T14:30:00Z           — UTC
2026-06-17T14:30:00+05:30      — With offset
2026-06-17T14:30:00            — Local time (ambiguous without context)
2026-06-17                     — Date only
14:30:00Z                      — Time only (UTC)

Always include the offset or the Z suffix when serializing timestamps. A bare 2026-06-17T14:30:00 is ambiguous without knowing the timezone context.

Storing Timestamps Best Practice

The industry standard for storing timestamps is:

  1. Store in UTC
  2. Use the TIMESTAMP WITH TIME ZONE column type (PostgreSQL) or equivalent
  3. Convert to the user's local timezone only at display time
  4. Store the user's IANA timezone preference (e.g. America/New_York) in their profile
-- PostgreSQL — correct way to store timestamps
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    occurs_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Always query in UTC
SELECT * FROM events WHERE occurs_at >= '2026-06-17T00:00:00Z';

-- Convert to user timezone at query time
SELECT name, occurs_at AT TIME ZONE 'America/New_York' AS local_time
FROM events;

Code Examples by Language

JavaScript / Node.js

// Intl.DateTimeFormat — the standard way
function convertTime(date, fromTz, toTz) {
  const utc = new Date(date.toLocaleString('en-US', { timeZone: fromTz }));
  return new Intl.DateTimeFormat('en-US', {
    timeZone: toTz,
    dateStyle: 'full',
    timeStyle: 'long',
  }).format(utc);
}

console.log(convertTime(new Date(), 'America/New_York', 'Asia/Tokyo'));
// Wednesday, June 17, 2026 at 3:30:00 AM GMT+9

PHP

// PHP DateTimeZone — the standard way
function convertTime(string $datetime, string $fromTz, string $toTz): string {
    $date = new DateTime($datetime, new DateTimeZone($fromTz));
    $date->setTimezone(new DateTimeZone($toTz));
    return $date->format('Y-m-d H:i:s T');
}

echo convertTime('2026-06-17 14:00:00', 'America/New_York', 'Asia/Tokyo');
// 2026-06-18 03:00:00 JST

Python

from datetime import datetime
import pytz

def convert_time(dt_str: str, from_tz: str, to_tz: str) -> str:
    utc_dt = datetime.fromisoformat(dt_str).replace(tzinfo=pytz.UTC)
    local_dt = utc_dt.astimezone(pytz.timezone(to_tz))
    return local_dt.strftime('%Y-%m-%d %H:%M:%S %Z')

print(convert_time('2026-06-17T14:00:00', 'America/New_York', 'Asia/Tokyo'))
# 2026-06-18 03:00:00 JST

Ruby

require 'time'
require 'tzinfo'

def convert_time(time_str, to_tz)
  tz = TZInfo::Timezone.get(to_tz)
  time = Time.parse(time_str).utc
  tz.to_local(time).strftime('%Y-%m-%d %H:%M:%S %Z')
end

puts convert_time('2026-06-17 14:00:00', 'Asia/Tokyo')
# 2026-06-17 23:00:00 JST

Common Timezone Pitfalls

Pitfall 1: Using fixed offsets instead of IANA timezones

// ❌ Wrong — hardcoded offset does not account for DST
$date = new DateTime('now', new DateTimeZone('-0500'));

// ✅ Correct — IANA identifier handles DST automatically
$date = new DateTime('now', new DateTimeZone('America/New_York'));

Pitfall 2: Assuming the server timezone matches the user's timezone

// ❌ Wrong — uses the server's configured timezone
echo date('Y-m-d H:i:s');

// ✅ Correct — explicitly use UTC
echo gmdate('Y-m-d H:i:s');

Pitfall 3: Storing local time without the offset

-- ❌ Wrong — 14:30 is ambiguous without offset
INSERT INTO events (occurs_at) VALUES ('2026-06-17 14:30:00');

-- ✅ Correct — store with offset or use TIMESTAMPTZ
INSERT INTO events (occurs_at) VALUES ('2026-06-17 14:30:00+05:30');

Pitfall 4: Performing arithmetic across DST boundaries

// ❌ Wrong — adding hours does not account for DST shifts
const date = new Date('2026-03-08T01:00:00-05:00');
date.setHours(date.getHours() + 24);

// ✅ Correct — use UTC for arithmetic
const utcDate = new Date('2026-03-08T06:00:00Z');
utcDate.setDate(utcDate.getDate() + 1);

Timezone Converter Tool

The Timezone Converter tool on Help2Code provides an interactive SVG UTC offset band strip for visual timezone browsing, bidirectional conversion between any two IANA timezones, and a world clock view of popular cities. It runs entirely client-side using the browser's Intl.DateTimeFormat API, so no data ever leaves your machine. You can also use the Epoch / Unix Timestamp Converter for converting between human-readable dates and Unix timestamps.

Conclusion

Timezone conversion is difficult because it mixes geography, politics, and physics, but the principles are straightforward: store everything in UTC, use IANA timezone identifiers, convert only at the display layer, and always include the offset in serialized timestamps. Use the Timezone Converter tool to test conversions interactively, and refer to the code examples above for integrating timezone handling into your applications.


About this article

Master timezone conversion as a developer. Learn how UTC offsets, IANA timezones, DST, and ISO 8601 work, and avoid common pitfalls.


Related Articles


Related Tools

Help2Code Logo
Menu