Building a World Clock Feature for Firefox New Tab Extensions
One of the most-requested features for new tab extensions is a world clock — a quick way to see what time it is in other cities. I built this for the Weather & Clock Dashboard extension. Here's how it works.
The Core Problem: Time Zones
JavaScript's Date object is surprisingly powerful for this. The key is the Intl.DateTimeFormat API:
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());
}
// Usage:
getTimeInZone('America/New_York'); // "14:35:22"
getTimeInZone('Asia/Tokyo'); // "03:35:22"
getTimeInZone('Europe/London'); // "19:35:22"
No external libraries needed — pure browser API.
Getting the Full List of Timezones
Intl.supportedValuesOf('timeZone') returns all supported timezone identifiers:
const timezones = Intl.supportedValuesOf('timeZone');
// Returns: ["Africa/Abidjan", "Africa/Accra", ..., "US/Pacific", ...]
console.log(timezones.length); // ~500+ timezones
For a user-facing picker, you want to map these to friendly city names:
const popularCities = [
{ label: 'New York', timezone: 'America/New_York' },
{ label: 'Los Angeles', timezone: 'America/Los_Angeles' },
{ label: 'Chicago', timezone: 'America/Chicago' },
{ label: 'London', timezone: 'Europe/London' },
{ label: 'Paris', timezone: 'Europe/Paris' },
{ label: 'Berlin', timezone: 'Europe/Berlin' },
{ label: 'Dubai', timezone: 'Asia/Dubai' },
{ label: 'Mumbai', timezone: 'Asia/Kolkata' },
{ label: 'Singapore', timezone: 'Asia/Singapore' },
{ label: 'Tokyo', timezone: 'Asia/Tokyo' },
{ label: 'Sydney', timezone: 'Australia/Sydney' },
{ label: 'São Paulo', timezone: 'America/Sao_Paulo' },
];
The Clock Component
Here's a minimal working world clock:
class WorldClock {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.clocks = [];
this.interval = null;
}
addClock(label, timezone) {
this.clocks.push({ label, timezone });
this.render();
}
removeClock(timezone) {
this.clocks = this.clocks.filter(c => c.timezone !== timezone);
this.render();
}
formatTime(timezone) {
return new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit',
hour12: true
}).format(new Date());
}
formatDate(timezone) {
return new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
weekday: 'short',
month: 'short',
day: 'numeric'
}).format(new Date());
}
isNextDay(timezone) {
const localDate = new Date().toLocaleDateString();
const tzDate = new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
month: '2-digit',
day: '2-digit',
year: 'numeric'
}).format(new Date());
return localDate !== tzDate;
}
render() {
if (!this.container) return;
this.container.innerHTML = this.clocks.map(({ label, timezone }) => {
const time = this.formatTime(timezone);
const date = this.formatDate(timezone);
const isDifferentDay = this.isNextDay(timezone);
return `
<div class="world-clock" data-timezone="${timezone}">
<div class="clock-city">${label}</div>
<div class="clock-time">${time}</div>
<div class="clock-date">${date}${isDifferentDay ? ' <span class="next-day">+1</span>' : ''}</div>
</div>
`;
}).join('');
}
start() {
this.render();
this.interval = setInterval(() => this.render(), 1000);
}
stop() {
if (this.interval) clearInterval(this.interval);
}
}
Handling User Preferences
Store selected clocks in extension storage:
const DEFAULT_CLOCKS = [
{ label: 'New York', timezone: 'America/New_York' },
{ label: 'London', timezone: 'Europe/London' },
{ label: 'Tokyo', timezone: 'Asia/Tokyo' },
];
async function loadClocks() {
const stored = await browser.storage.local.get('worldClocks');
return stored.worldClocks || DEFAULT_CLOCKS;
}
async function saveClocks(clocks) {
await browser.storage.local.set({ worldClocks: clocks });
}
// Initialize
const savedClocks = await loadClocks();
const worldClock = new WorldClock('clock-container');
savedClocks.forEach(c => worldClock.addClock(c.label, c.timezone));
worldClock.start();
The CSS
.world-clocks-grid {
display: flex;
gap: 16px;
flex-wrap: wrap;
justify-content: center;
}
.world-clock {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 12px 16px;
text-align: center;
min-width: 120px;
backdrop-filter: blur(10px);
}
.clock-city {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.7;
margin-bottom: 4px;
}
.clock-time {
font-size: 24px;
font-weight: 600;
font-variant-numeric: tabular-nums; /* Prevents layout shift as numbers change */
}
.clock-date {
font-size: 11px;
opacity: 0.6;
margin-top: 2px;
}
.next-day {
color: #fbbf24;
font-size: 10px;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
.world-clock {
background: rgba(0, 0, 0, 0.2);
}
}
Performance Tip: font-variant-numeric: tabular-nums
This CSS property prevents the layout from shifting as clock digits change — 1 and 8 take up the same space. Without it, the clock container width bounces as time changes from 1:08 to 1:09.
The Searchable Timezone Picker
For letting users add clocks, a searchable dropdown is more practical than scrolling through 500 timezones:
function buildTimezonePicker(onSelect) {
const input = document.createElement('input');
input.placeholder = 'Search city...';
input.type = 'text';
const dropdown = document.createElement('ul');
dropdown.className = 'tz-dropdown hidden';
input.addEventListener('input', () => {
const query = input.value.toLowerCase();
const matches = popularCities.filter(c =>
c.label.toLowerCase().includes(query) ||
c.timezone.toLowerCase().includes(query)
).slice(0, 10);
dropdown.innerHTML = matches.map(c =>
`<li data-tz="${c.timezone}" data-label="${c.label}">${c.label}</li>`
).join('');
dropdown.classList.toggle('hidden', matches.length === 0);
});
dropdown.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (li) {
onSelect(li.dataset.label, li.dataset.tz);
input.value = '';
dropdown.classList.add('hidden');
}
});
return { input, dropdown };
}
Result
The Weather & Clock Dashboard uses this exact pattern — you can see it in action by installing the extension: Weather & Clock Dashboard on AMO
World clocks are one of the most useful features for remote workers and anyone who collaborates across time zones.
Part of a series on building Firefox browser extensions.
Top comments (0)