How to Build a World Clock or Time Zone Converter

Developer
scheduling

A world clock shows the current time in multiple cities simultaneously. A time zone converter takes a specific datetime and shows what it is in other zones. Both are built on the same foundation: a UTC timestamp and the Intl.DateTimeFormat API.

The core: one moment, many representations

const now = new Date(); // UTC instant

const cities = [
  { name: "New York", zone: "America/New_York" },
  { name: "London", zone: "Europe/London" },
  { name: "Singapore", zone: "Asia/Singapore" },
  { name: "Tokyo", zone: "Asia/Tokyo" },
];

cities.forEach(({ name, zone }) => {
  const time = new Intl.DateTimeFormat("en-US", {
    timeZone: zone,
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  }).format(now);
  console.log(`${name}: ${time}`);
});

// New York:  15:00
// London:    20:00
// Singapore: 03:00  (next day)
// Tokyo:     04:00  (next day)

DST is handled automatically — Intl.DateTimeFormat uses the IANA zone rules for each date.

Adding a live clock

Update every second using setInterval. Always update from new Date() rather than incrementing a stored value — this avoids drift and stays correct across DST transitions.

function updateClocks() {
  const now = new Date();
  cities.forEach(({ name, zone, elementId }) => {
    document.getElementById(elementId).textContent = new Intl.DateTimeFormat(
      "en-US",
      {
        timeZone: zone,
        hour: "2-digit",
        minute: "2-digit",
        second: "2-digit",
        hour12: false,
      },
    ).format(now);
  });
}

setInterval(updateClocks, 1000);
updateClocks(); // run immediately on load

Building a time zone converter

A converter takes a user-specified datetime and a source zone, converts to UTC, then displays the result in target zones.

import { fromZonedTime, toZonedTime, format } from "date-fns-tz";

function convert(localDateTimeString, sourceZone, targetZones) {
  // 1. Parse local time in source zone → UTC
  const utcDate = fromZonedTime(localDateTimeString, sourceZone);

  // 2. Display UTC in each target zone
  return targetZones.map((zone) => ({
    zone,
    display: format(toZonedTime(utcDate, zone), "yyyy-MM-dd HH:mm zzz", {
      timeZone: zone,
    }),
  }));
}

convert("2026-06-20 15:00", "America/New_York", [
  "Europe/London",
  "Asia/Tokyo",
  "Australia/Sydney",
]);
// [
//   { zone: 'Europe/London',    display: '2026-06-20 20:00 BST'  },
//   { zone: 'Asia/Tokyo',       display: '2026-06-21 04:00 JST'  },
//   { zone: 'Australia/Sydney', display: '2026-06-21 05:00 AEST' },
// ]

Sourcing the list of IANA zones

The full IANA zone list is available via Intl.supportedValuesOf('timeZone') in modern browsers and Node:

const allZones = Intl.supportedValuesOf("timeZone");
// → ['Africa/Abidjan', 'Africa/Accra', ... 'Pacific/Wake']

For a world clock UI, you don’t want 600 zones in a dropdown. Use a curated list of major cities, or group by region and show one representative zone per region.

Handling “next day” display

When a UTC timestamp is June 20 but displays as June 21 in Tokyo, your UI needs to make that visible. Show the date alongside the time for any zone that crosses midnight:

new Intl.DateTimeFormat("en-US", {
  timeZone: "Asia/Tokyo",
  weekday: "short",
  month: "short",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  hour12: false,
}).format(now);
// → "Sun, Jun 21, 04:00"

Testing

The cases most likely to catch bugs:

  • A time during the DST transition hour (1–2am on the transition day for the target zone)
  • A time that falls on different calendar dates in different zones (e.g. 11pm UTC on December 31)
  • Fractional offset zones: Asia/Kolkata (UTC+5:30), Asia/Kathmandu (UTC+5:45)
  • Zones without DST: Asia/Tokyo, Asia/Singapore — verify these stay fixed when other zones shift
Buy me a coffe