JavaScript Timezone Conversion: From Unix Timestamps to IANA Identifiers
JavaScript Timezone Conversion: From Unix Timestamps to IANA Identifiers#
Recently built a cross-timezone meeting scheduler and stepped into quite a few timezone pitfalls. Let me share what I learned about JavaScript timezone handling.
The Essence: Unix Timestamps Have No Timezone#
Here’s a core concept: timestamps are timezone-agnostic.
const now = Date.now()
// 1714838400000 - milliseconds since UTC 1970-01-01
// Beijing: 2024-05-04 20:00:00
// New York: 2024-05-04 08:00:00
// Same timestamp, different displays
No matter where you are on Earth, Date.now() returns the same number. “Beijing time” and “New York time” are just different display formats of this number.
JavaScript Timezone Handling#
1. toLocaleString - The Simplest Approach#
const date = new Date('2024-05-04T12:00:00Z')
// Convert to Beijing time
date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
// "2024/5/4 20:00:00"
// Convert to New York time
date.toLocaleString('en-US', { timeZone: 'America/New_York' })
// "5/4/2024, 8:00:00 AM"
This is the core of our timezone converter tool. Simple, right? But there are details worth noting.
2. IANA Timezone Identifiers#
'Asia/Shanghai', 'America/New_York' are IANA timezone identifiers. They’re not randomly named:
const timezones = [
{ id: 'UTC', offset: 0 },
{ id: 'Asia/Shanghai', offset: 8 }, // UTC+8
{ id: 'America/New_York', offset: -5 }, // UTC-5 (winter)
{ id: 'Europe/London', offset: 0 }, // Greenwich time
{ id: 'Asia/Tokyo', offset: 9 }, // UTC+9
]
Why not use GMT+8 directly? Because of Daylight Saving Time.
DST: The Timezone Nightmare#
US Eastern Time (America/New_York):
- Standard Time: UTC-5 (November ~ March)
- Daylight Time: UTC-4 (March ~ November)
Using GMT-5 ignores DST adjustments:
const summerDate = new Date('2024-07-04T12:00:00Z')
const winterDate = new Date('2024-01-04T12:00:00Z')
// Using IANA timezone (handles DST correctly)
summerDate.toLocaleString('en-US', { timeZone: 'America/New_York' })
// "7/4/2024, 8:00:00 AM" (UTC-4, daylight time)
winterDate.toLocaleString('en-US', { timeZone: 'America/New_York' })
// "1/4/2024, 7:00:00 AM" (UTC-5, standard time)
// Using fixed offset (wrong)
// GMT-5 is always UTC-5, ignoring DST
That’s why toLocaleString + IANA timezone is the best practice—the browser handles DST automatically.
toLocaleString Parameters Explained#
date.toLocaleString(locales, options)
locales: Language + Region
'zh-CN': Chinese (China)'en-US': English (US)'ja-JP': Japanese (Japan)
options: Formatting options
const options = {
timeZone: 'America/New_York',
year: 'numeric', // "2024"
month: '2-digit', // "05"
day: '2-digit', // "04"
hour: '2-digit', // "08"
minute: '2-digit', // "00"
second: '2-digit', // "00"
hour12: false // 24-hour format
}
date.toLocaleString('zh-CN', options)
// "2024/05/04 08:00:00"
Common Pitfalls#
1. new Date() Timezone Issues#
// These create different Date objects!
new Date('2024-05-04') // Parsed as UTC
new Date('2024-05-04T00:00:00') // Parsed as local time
// Solution: Always specify timezone
new Date('2024-05-04T00:00:00+08:00') // Explicit timezone
2. getTimezoneOffset() Trap#
const date = new Date()
date.getTimezoneOffset() // Minutes between local time and UTC
// Beijing (UTC+8): returns -480
// New York winter (UTC-5): returns 300
// Note: This value changes with DST!
3. toISOString() Always Returns UTC#
const date = new Date('2024-05-04T20:00:00+08:00')
date.toISOString() // "2024-05-04T12:00:00.000Z"
// Z means UTC, not your local time
Complete Implementation: Timezone Converter#
Combining the above knowledge:
function convertTimezone(
timestamp: number | string | Date,
targetTimezone: string,
locale: string = 'en-US'
): string {
const date = new Date(timestamp)
if (isNaN(date.getTime())) {
throw new Error('Invalid date')
}
return date.toLocaleString(locale, {
timeZone: targetTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
}
// Usage
convertTimezone(Date.now(), 'America/New_York')
// "05/04/2024, 08:30:15"
convertTimezone('2024-05-04T20:00:00+08:00', 'Europe/London', 'en-GB')
// "04/05/2024, 12:00:00"
World Clock Implementation#
Displaying multiple timezones simultaneously:
function getWorldClock(timezones: string[]): Record<string, string> {
const now = new Date()
const result: Record<string, string> = {}
timezones.forEach(tz => {
result[tz] = now.toLocaleString('en-US', {
timeZone: tz,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
})
return result
}
// Update every second
setInterval(() => {
const clocks = getWorldClock([
'Asia/Shanghai',
'America/New_York',
'Europe/London',
'Asia/Tokyo'
])
console.log(clocks)
// { Asia/Shanghai: "20:30:15", America/New_York: "08:30:15", ... }
}, 1000)
Performance Optimization#
toLocaleString recalculates every call. For frequent updates (like refreshing a world clock every second), cache Intl.DateTimeFormat objects:
// Create formatters (reusable)
const formatters = new Map<string, Intl.DateTimeFormat>()
function getFormatter(timezone: string) {
if (!formatters.has(timezone)) {
formatters.set(timezone, new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}))
}
return formatters.get(timezone)!
}
// Usage
const formatter = getFormatter('America/New_York')
formatter.format(new Date()) // ~5x faster than toLocaleString
The Final Tool#
Based on these implementations: Timezone Converter
Features:
- Convert between 15 common timezones
- Automatic DST handling
- Real-time world clock
- One-click use current time
Timezone handling seems simple, but has many pitfalls. Remember one principle: Always use IANA timezone identifiers, let the browser handle DST.
Related: Timestamp Converter | Date Calculator