DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Building a World Clock Feature for Firefox New Tab Extensions

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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' },
];
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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.

firefox #javascript #webdev #browserextension

Top comments (0)