timezones.ts3.5 KBView on GitHub
import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz';

export const getBrowserTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone;

/** A curated list of common IANA timezones for the calendar timezone picker. */
export const COMMON_TIMEZONES: { id: string; label: string }[] = [
  { id: 'Pacific/Honolulu', label: 'Honolulu' },
  { id: 'America/Anchorage', label: 'Anchorage' },
  { id: 'America/Los_Angeles', label: 'Los Angeles' },
  { id: 'America/Denver', label: 'Denver' },
  { id: 'America/Chicago', label: 'Chicago' },
  { id: 'America/New_York', label: 'New York' },
  { id: 'America/Sao_Paulo', label: 'São Paulo' },
  { id: 'Europe/London', label: 'London' },
  { id: 'Europe/Paris', label: 'Paris' },
  { id: 'Europe/Berlin', label: 'Berlin' },
  { id: 'Europe/Moscow', label: 'Moscow' },
  { id: 'Asia/Dubai', label: 'Dubai' },
  { id: 'Asia/Kolkata', label: 'Mumbai' },
  { id: 'Asia/Singapore', label: 'Singapore' },
  { id: 'Asia/Shanghai', label: 'Shanghai' },
  { id: 'Asia/Tokyo', label: 'Tokyo' },
  { id: 'Australia/Sydney', label: 'Sydney' },
  { id: 'Pacific/Auckland', label: 'Auckland' },
];

/** Short abbreviation for a timezone at a given instant, e.g. "EST", "PST", "GMT+2". */
export function getTimezoneAbbreviation(timezone: string, at: Date = new Date()): string {
  return (
    at
      .toLocaleString('en-US', { timeZoneName: 'short', timeZone: timezone })
      .split(' ')
      .pop() ?? ''
  );
}

/**
 * Compact hour label for a multi-timezone calendar gutter. Given `hour` (0–23) as the
 * wall-clock hour of the row in `primaryTimezone` on `refDateISO` (a 'yyyy-MM-dd' date in
 * that timezone), returns the corresponding wall-clock label in `targetTimezone` — e.g.
 * "12AM", "6AM", "12PM". Minutes are shown only when the target has a fractional offset
 * (e.g. "12:30AM" for India), so whole-hour zones stay compact.
 */
export function getHourLabelInTimezone(
  refDateISO: string,
  hour: number,
  primaryTimezone: string,
  targetTimezone: string,
): string {
  const instant = fromZonedTime(
    `${refDateISO}T${String(hour).padStart(2, '0')}:00:00`,
    primaryTimezone,
  );
  const minutes = formatInTimeZone(instant, targetTimezone, 'mm');
  const pattern = minutes === '00' ? 'ha' : 'h:mma';
  return formatInTimeZone(instant, targetTimezone, pattern).toUpperCase();
}

/**
 * Convert a date from one timezone to another.
 * 
 * This is useful when parsing dates like "3pm EST" and converting them to the user's timezone.
 * The function interprets the input date as being in the source timezone and returns
 * the equivalent date in the target timezone.
 * 
 * @param date - The date to convert (interpreted as being in fromTimezone)
 * @param fromTimezone - The source IANA timezone identifier (e.g., 'America/New_York')
 * @param toTimezone - The target IANA timezone identifier (e.g., 'America/Los_Angeles')
 * @returns A new Date object representing the same moment in the target timezone
 * 
 * @example
 * // User in PST types "friday 3pm est"
 * const date = new Date('2026-01-31T15:00:00'); // 3pm (no timezone)
 * const converted = convertTimezone(date, 'America/New_York', 'America/Los_Angeles');
 * // Result: 12pm PST (3 hours earlier)
 */
export function convertTimezone(
  date: Date,
  fromTimezone: string,
  toTimezone: string,
): Date {
  // Interpret the date as being in the source timezone
  const zonedTime = fromZonedTime(date, fromTimezone);
  
  // Convert to the target timezone
  return toZonedTime(zonedTime, toTimezone);
}