Timezone API Guide for Developers: Convert Times Programmatically
Handling time zones correctly in code is infamously difficult. Between daylight saving transitions, historical offset changes, and half-hour time zones, naive implementations constantly fail. This guide covers everything you need to handle time zones reliably in your applications.
Need to test your timezone logic against real data?
Verify with GlobeTimeZone Converter →The Golden Rule: Never Store UTC Offsets
The most common timezone mistake developers make is storing UTC offsets (like "+8" or "-5") instead of IANA timezone identifiers. Offsets change with DST — New York is UTC-4 in summer but UTC-5 in winter. Worse, governments occasionally change DST rules (e.g., Egypt delaying DST in 2023 with 3 days notice).
user.timezone = "+08:00"✅ Correct: Storing
user.timezone = "Asia/Shanghai"
The IANA identifier automatically resolves to the correct offset for any given date, handles DST transitions, and accounts for historical changes. Your database, your API, and your UI should all use IANA identifiers.
JavaScript/TypeScript: Native Timezone Handling
Modern JavaScript (ES2020+) has excellent built-in timezone support through Intl.DateTimeFormat. No libraries needed for basic operations:
// Get current time in any timezone
const beijingTime = new Date().toLocaleString('en-US', {
timeZone: 'Asia/Shanghai',
dateStyle: 'full',
timeStyle: 'long'
});
// "Monday, May 26, 2026 at 10:24:18 PM GMT+8"
// Convert between timezones
const convertTime = (date, fromTZ, toTZ) => {
return new Date(date.toLocaleString('en-US', { timeZone: toTZ }));
};
// Check if a timezone currently observes DST
const isDST = (tz) => {
const jan = new Date(new Date().getFullYear(), 0, 1);
const jul = new Date(new Date().getFullYear(), 6, 1);
const janOffset = new Date(jan.toLocaleString('en-US', { timeZone: tz })).getTimezoneOffset();
const julOffset = new Date(jul.toLocaleString('en-US', { timeZone: tz })).getTimezoneOffset();
return janOffset !== julOffset;
};
// List common timezones
const commonTZs = Intl.supportedValuesOf('timeZone');
// ['Africa/Abidjan', 'Africa/Accra', ... 'Pacific/Wallis']
Using Luxon (Recommended for Complex Operations)
For date math, formatting, and cross-timezone operations, Luxon (successor to Moment.js) is the gold standard:
import { DateTime } from 'luxon';
// Create a datetime in a specific timezone
const meeting = DateTime.fromObject(
{ year: 2025, month: 6, day: 15, hour: 14, minute: 0 },
{ zone: 'America/New_York' }
);
// Convert to another timezone
const beijingView = meeting.setZone('Asia/Shanghai');
console.log(beijingView.toFormat('FF')); // "June 16, 2025 at 2:00 AM CST"
// Calculate the UTC offset at that moment
console.log(meeting.offset); // -240 (UTC-4, DST active)
// Format with timezone abbreviation
console.log(meeting.toFormat("h:mm a 'ET'")); // "2:00 PM ET"
Python: pytz, zoneinfo, and dateutil
Python 3.9+ includes the zoneinfo module (no dependencies needed). For older versions, use pytz.
# Python 3.9+ (recommended)
from zoneinfo import ZoneInfo
from datetime import datetime
# Current time in Shanghai
now_shanghai = datetime.now(ZoneInfo("Asia/Shanghai"))
print(now_shanghai) # 2026-05-26 22:24:18.123456+08:00
# Convert between timezones
ny_time = datetime.now(ZoneInfo("America/New_York"))
shanghai_time = ny_time.astimezone(ZoneInfo("Asia/Shanghai"))
print(f"NY: {ny_time:%H:%M} | Shanghai: {shanghai_time:%H:%M}")
# Python <3.9 (with pytz)
import pytz
utc = pytz.utc.localize(datetime.utcnow())
shanghai_tz = pytz.timezone('Asia/Shanghai')
shanghai_time = utc.astimezone(shanghai_tz)
Public Timezone APIs Compared
| API | Free Tier | Auth Required | Coverage | DST Data |
|---|---|---|---|---|
| WorldTimeAPI.org | Unlimited | No | 400+ timezones | ✅ Yes |
| TimeZoneDB | 1,000/mo | API Key | All IANA zones | ✅ Yes |
| Abstract Timezone API | 1,000/mo | API Key | Global | ✅ Yes |
| Google Timezone API | $200 credit | API Key | Global | ✅ Yes |
| IPGeolocation Timezone | 1,000/mo | API Key | Based on IP | ✅ Yes |
| Self-hosted IANA DB | Unlimited | No | All zones | ✅ Yes |
WorldTimeAPI.org Example
// GET https://worldtimeapi.org/api/timezone/Asia/Shanghai
{
"datetime": "2026-05-26T22:24:18.123456+08:00",
"timezone": "Asia/Shanghai",
"utc_offset": "+08:00",
"dst": false,
"abbreviation": "CST",
"utc_datetime": "2026-05-26T14:24:18.123456+00:00",
"day_of_week": 2,
"day_of_year": 146
}
// Fetch in JavaScript
const res = await fetch('https://worldtimeapi.org/api/timezone/Asia/Shanghai');
const { datetime, utc_offset, dst, abbreviation } = await res.json();
console.log(`Shanghai: ${datetime} (${abbreviation}, ${utc_offset})`);
Handling DST Transitions
DST transitions create "impossible" and "ambiguous" times. When clocks spring forward, 2:30 AM doesn't exist. When they fall back, 1:30 AM happens twice.
| Scenario | Problem | Solution |
|---|---|---|
| Spring Forward | 2:00-3:00 AM doesn't exist | Schedule meetings at safe times (avoid 1:00-3:00 AM during transitions) |
| Fall Back | 1:00-2:00 AM repeats | Use UTC internally, convert to local only for display |
| Sudden Policy Changes | Governments change rules | Keep IANA tzdata updated; use a service that auto-updates |
| Future Date Conversion | DST rules may change | Use latest tzdata; display "tentative" for far-future dates |
Database Best Practices
- Store timestamps as UTC (or TIMESTAMP WITH TIME ZONE in PostgreSQL)
- Store timezone as IANA identifier in a separate column (
VARCHAR(64)) - Convert to local time at the application/display layer, not in SQL queries
- Include timezone in API responses:
{"time": "2026-05-26T14:24:18Z", "tz": "America/New_York"} - Validate IANA identifiers against the official list before storing
- Never use TIMESTAMP WITHOUT TIME ZONE for events that involve multiple time zones
Testing Timezone Logic
Timezone bugs are subtle and often only appear during specific dates. Here's a testing checklist:
- Test with DST transition dates (March 9 and November 2 for US, March 30 and October 26 for Europe)
- Test with time zones that have 30-minute or 45-minute offsets (India +05:30, Nepal +05:45, Chatham Islands +12:45)
- Test with extreme cases: Samoa (UTC-11 to UTC+13 after the 2011 date line shift), Kiribati (UTC+14)
- Test dates before 1970 (Unix epoch start — many implementations have bugs here)
- Use a dedicated timezone testing library: freezegun (Python), sinon fake timers (JavaScript)
// JavaScript: Mock time for testing
import FakeTimers from '@sinonjs/fake-timers';
const clock = FakeTimers.install({
now: new Date('2025-03-09T07:30:00Z'), // US DST spring forward
toFake: ['Date']
});
// Now test your timezone conversion logic
clock.uninstall();
Mastering timezone handling in your codebase prevents the kind of bugs that crash billing systems, miss flight connections, and confuse global teams. Use IANA identifiers everywhere, always store UTC, and convert to local time only at the display layer. When in doubt, verify with our free GlobeTimeZone converter or check your results against time.is.
Verify Your Timezone Implementation
Use our converter to cross-check your API results against real-time timezone data.
Open Converter →