Handling Time Zones in Browser Extensions: Lessons from Building a World Clock
When I added world clock support to my Firefox new tab extension, I thought it would be simple — just use JavaScript's Intl API and call it a day. Turns out, there are several gotchas worth knowing about.
The JavaScript Intl.DateTimeFormat API
Modern browsers have excellent built-in time zone support:
function getTimeInZone(timezone) {
return new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(new Date());
}
console.log(getTimeInZone('America/New_York')); // 14:32:07
console.log(getTimeInZone('Europe/London')); // 19:32:07
console.log(getTimeInZone('Asia/Tokyo')); // 04:32:07
No external libraries needed. No time zone database to maintain. Just a string identifier from the IANA time zone database.
The IANA Time Zone Database
IANA (Internet Assigned Numbers Authority) maintains the canonical list of time zone identifiers. These look like:
America/New_YorkEurope/ParisAsia/KolkataPacific/AucklandUTC
Avoid using abbreviations like EST or PST — they're ambiguous and not universally supported. EST could mean Eastern Standard Time (UTC-5) or Eastern Summer Time (UTC+11 in Australia).
Gotcha #1: DST Changes
Daylight Saving Time transitions are automatic with the Intl API — but you need to be aware they happen:
// This will automatically adjust for DST
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/Los_Angeles',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Winter: UTC-8
// Summer: UTC-7 (PDT)
// The formatter handles this automatically
For display purposes, showing the UTC offset is more accurate than showing the abbreviation:
function getOffsetLabel(timezone) {
const now = new Date();
const formatter = new Intl.DateTimeFormat('en', {
timeZone: timezone,
timeZoneName: 'shortOffset'
});
const parts = formatter.formatToParts(now);
return parts.find(p => p.type === 'timeZoneName')?.value || '';
}
// Returns "GMT-4", "GMT+5:30", etc.
Gotcha #2: The Extension Runs in User's Local Time
Your extension background script runs in the user's system time zone. This is usually fine, but if you're doing any date math, be explicit:
// BAD: assumes local time
const hour = new Date().getHours(); // local hours
// GOOD: explicit about time zone
function getHourInZone(timezone) {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: 'numeric',
hour12: false
}).formatToParts(new Date());
return parseInt(parts.find(p => p.type === 'hour').value);
}
Gotcha #3: User Preferences for Time Format
Not everyone uses 24-hour time. Respect user preferences:
function formatTime(timezone, use24h = false) {
return new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit',
hour12: !use24h
}).format(new Date());
}
// 12h: "02:32 PM"
// 24h: "14:32"
Store this preference in browser.storage.sync so it follows the user across devices.
Live Clock Implementation
Here's a clean world clock implementation:
class WorldClock {
constructor(container) {
this.container = container;
this.clocks = [];
this.interval = null;
}
addClock(timezone, label) {
this.clocks.push({ timezone, label });
this.render();
}
removeClock(timezone) {
this.clocks = this.clocks.filter(c => c.timezone !== timezone);
this.render();
}
start() {
this.render();
this.interval = setInterval(() => this.render(), 1000);
}
stop() {
if (this.interval) clearInterval(this.interval);
}
render() {
const now = new Date();
this.container.innerHTML = this.clocks.map(({ timezone, label }) => {
const time = new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(now);
const offset = new Intl.DateTimeFormat('en', {
timeZone: timezone,
timeZoneName: 'shortOffset'
}).formatToParts(now).find(p => p.type === 'timeZoneName')?.value || '';
return `
<div class="clock-card">
<div class="clock-label">${label}</div>
<div class="clock-time">${time}</div>
<div class="clock-offset">${offset}</div>
</div>
`;
}).join('');
}
}
// Usage
const clockContainer = document.getElementById('world-clocks');
const worldClock = new WorldClock(clockContainer);
browser.storage.sync.get('worldClocks').then(({ worldClocks = [] }) => {
worldClocks.forEach(({ timezone, label }) => {
worldClock.addClock(timezone, label);
});
worldClock.start();
});
Performance: Don't Create a New Formatter Every Tick
Intl.DateTimeFormat instantiation is relatively expensive. Cache your formatters:
const formatterCache = new Map();
function getFormatter(timezone, options) {
const key = `${timezone}:${JSON.stringify(options)}`;
if (!formatterCache.has(key)) {
formatterCache.set(key, new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
...options
}));
}
return formatterCache.get(key);
}
With 5 world clocks updating every second, this saves creating 5 new formatter objects per second.
Time Zone Picker UX
Let users search by city name, not just IANA identifier:
const POPULAR_ZONES = [
{ label: 'New York', timezone: 'America/New_York' },
{ label: 'Los Angeles', timezone: 'America/Los_Angeles' },
{ label: 'London', timezone: 'Europe/London' },
{ label: 'Paris', timezone: 'Europe/Paris' },
{ label: 'Tokyo', timezone: 'Asia/Tokyo' },
{ label: 'Sydney', timezone: 'Australia/Sydney' },
{ label: 'Dubai', timezone: 'Asia/Dubai' },
{ label: 'Mumbai', timezone: 'Asia/Kolkata' },
{ label: 'Singapore', timezone: 'Asia/Singapore' },
{ label: 'São Paulo', timezone: 'America/Sao_Paulo' },
];
function searchZones(query) {
const q = query.toLowerCase();
return POPULAR_ZONES.filter(z =>
z.label.toLowerCase().includes(q) ||
z.timezone.toLowerCase().includes(q)
);
}
What I Learned
For the Weather & Clock Dashboard Firefox extension, implementing world clocks taught me:
-
Trust the platform —
Intlhandles DST automatically - Cache formatters — don't create new ones every second
- Use city labels — users think in cities, not IANA identifiers
-
Respect 12/24h preference — store in
browser.storage.sync - Show UTC offset — more accurate than abbreviations like "EST"
The browser's built-in Intl API is genuinely excellent. No dependencies, accurate DST handling, and it stays current with time zone database updates via browser updates.
Weather & Clock Dashboard is a free, open-source Firefox new tab extension with live weather, world clocks, and a search bar. No tracking, MIT licensed.
Top comments (0)