如何获取有效时区标识符列表

在 JavaScript 中检索所有受支持的 IANA 时区标识符,用于构建下拉菜单和校验用户输入

引言

在开发允许用户选择时区的功能时,你需要知道哪些时区标识符是有效的。纽约的用户可能需要选择 America/New_York,而东京的用户则需要 Asia/Tokyo。如果你将时区列表硬编码,会带来一系列问题。

首先,这个列表会过时。每当政府调整夏令时规则或创建新时区时,时区定义就会发生变化。其次,不同的 JavaScript 运行环境支持的时区也不同。在 Chrome 中可用的时区,可能在旧版浏览器或 Node.js 版本中不可用。第三,手动维护数百个时区字符串容易出现拼写错误和不一致的问题。

JavaScript 提供了 Intl.supportedValuesOf() 方法,可以检索当前环境支持的所有时区标识符。这样可以确保你的应用只提供可用的时区,并且能够及时跟进时区数据库的变更。

什么是时区标识符

时区标识符是标准化的字符串,用于表示具有一致时间规则的地理区域。它们来自 IANA 时区数据库,这是由国际组织维护的、跟踪全球时区和夏令时变更的权威列表。

这些标识符采用特定格式,确保在不同系统间具有唯一性和稳定性。理解这种格式有助于你更高效地处理时区相关工作。

理解 IANA 时区标识符的格式

IANA 时区标识符遵循 Area/Location 的格式,其中 area 表示大陆或大洋,location 表示该区域内的城市或地区。

const examples = [
  'America/New_York',
  'Europe/London',
  'Asia/Tokyo',
  'Australia/Sydney',
  'Pacific/Auckland'
];

area 对应:

  • 北美和南美用 America
  • 欧洲地区用 Europe
  • 亚洲地区用 Asia
  • 非洲地区用 Africa
  • 澳大利亚地区用 Australia
  • 太平洋岛屿用 Pacific
  • 大西洋岛屿用 Atlantic
  • 印度洋岛屿用 Indian
  • 南极科考站用 Antarctica

location 通常代表该时区内最大或最具代表性的城市。例如,New York 代表美国东部时区,Tokyo 代表日本,Sydney 代表澳大利亚东部时区。

有些标识符包含三部分,用于表示某一大区域内的子区域:

const detailedExamples = [
  'America/Indiana/Indianapolis',
  'America/Kentucky/Louisville',
  'America/North_Dakota/Center'
];

这些多段标识符用于区分同一国家内采用不同时间规则的地区。

数据库在城市名称中使用下划线代替空格:

const underscoreExamples = [
  'America/New_York',    // Not "New York"
  'America/Los_Angeles', // Not "Los Angeles"
  'Asia/Ho_Chi_Minh'     // Not "Ho Chi Minh"
];

这种格式确保标识符作为单一标记使用时不会因特殊字符导致解析问题。

为什么标识符使用城市而不是缩写

你可能会期望用 EST 表示东部标准时间,或 PST 表示太平洋标准时间。但这些缩写存在歧义。EST 在北美表示东部标准时间,在澳大利亚则表示澳大利亚东部标准时间。CST 可能指中央标准时间、中国标准时间或古巴标准时间。

基于城市的标识符始终保持唯一性。America/New_York 无论上下文如何,总是指向相同的地点和时区规则。

此外,缩写无法反映夏令时的变化。EST 仅代表标准时间,不包括 EDT(东部夏令时)。标识符 America/New_York 会根据日期自动处理标准时间和夏令时。

获取所有支持的时区标识符

使用 Intl.supportedValuesOf() 方法和 'timeZone' 参数,可以返回 JavaScript 环境支持的所有时区标识符数组。

const timeZones = Intl.supportedValuesOf('timeZone');

console.log(timeZones.length);
// Output: over 400

console.log(timeZones.slice(0, 10));
// Output: [
//   "Africa/Abidjan",
//   "Africa/Accra",
//   "Africa/Addis_Ababa",
//   "Africa/Algiers",
//   "Africa/Asmera",
//   "Africa/Bamako",
//   "Africa/Bangui",
//   "Africa/Banjul",
//   "Africa/Bissau",
//   "Africa/Blantyre"
// ]

该方法返回按字母顺序排列且无重复的标识符。具体列表取决于 JavaScript 环境,但现代浏览器和 Node.js 版本支持 400 多个时区。

数组中的每个标识符都可以与 Intl.DateTimeFormat 一起使用,以便为该时区格式化日期和时间:

const timeZone = timeZones[0]; // "Africa/Abidjan"
const date = new Date('2025-10-15T12:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: timeZone,
  dateStyle: 'long',
  timeStyle: 'short'
});

console.log(formatter.format(date));
// Output: "October 15, 2025 at 12:00 PM"

该方法保证返回的每个标识符都能与 Intl API 正确配合使用。

构建时区选择器

获取所有时区标识符最常见的用例是构建一个下拉菜单或选择元素,让用户选择自己的时区。

function buildTimeZoneSelector() {
  const timeZones = Intl.supportedValuesOf('timeZone');
  const select = document.createElement('select');
  select.name = 'timeZone';

  timeZones.forEach(timeZone => {
    const option = document.createElement('option');
    option.value = timeZone;
    option.textContent = timeZone;
    select.appendChild(option);
  });

  return select;
}

const selector = buildTimeZoneSelector();
document.body.appendChild(selector);

这会创建一个包含所有支持时区选项的选择元素。用户可以在列表中滚动并选择自己的位置。

然而,超过 400 个未排序的标识符列表会带来较差的用户体验。用户需要了解标识符的组织方式,并能快速找到自己的位置。

按区域分组时区

按洲(大陆)组织时区可以让列表更易于浏览。你可以从每个标识符中提取区域,并据此进行分组。

function groupTimeZonesByRegion() {
  const timeZones = Intl.supportedValuesOf('timeZone');
  const grouped = {};

  timeZones.forEach(timeZone => {
    const parts = timeZone.split('/');
    const region = parts[0];

    if (!grouped[region]) {
      grouped[region] = [];
    }

    grouped[region].push(timeZone);
  });

  return grouped;
}

const grouped = groupTimeZonesByRegion();

console.log(Object.keys(grouped));
// Output: [
//   "Africa", "America", "Antarctica", "Arctic",
//   "Asia", "Atlantic", "Australia", "Europe",
//   "Indian", "Pacific", "Etc"
// ]

console.log(grouped['America'].slice(0, 5));
// Output: [
//   "America/Adak",
//   "America/Anchorage",
//   "America/Anguilla",
//   "America/Antigua",
//   "America/Araguaina"
// ]

该函数会以斜杠分割每个标识符,并使用第一部分作为区域键。这样会创建一个对象,每个区域下包含一个时区数组。

你可以使用这种分组数据,通过 optgroup 元素构建更有层次的选择器:

function buildGroupedTimeZoneSelector() {
  const grouped = groupTimeZonesByRegion();
  const select = document.createElement('select');
  select.name = 'timeZone';

  Object.keys(grouped).sort().forEach(region => {
    const optgroup = document.createElement('optgroup');
    optgroup.label = region;

    grouped[region].forEach(timeZone => {
      const option = document.createElement('option');
      option.value = timeZone;
      option.textContent = timeZone.split('/').slice(1).join('/');
      optgroup.appendChild(option);
    });

    select.appendChild(optgroup);
  });

  return select;
}

const groupedSelector = buildGroupedTimeZoneSelector();
document.body.appendChild(groupedSelector);

这样会创建一个 select 元素,时区会显示在各自的区域标题下。选项文本只显示标识符中的城市部分,使列表更易于阅读。

按区域筛选时区

有时你只需要特定区域的时区。例如,一个只为北美用户服务的应用可能只显示美洲时区。

function getTimeZonesForRegion(region) {
  const timeZones = Intl.supportedValuesOf('timeZone');
  return timeZones.filter(timeZone => timeZone.startsWith(`${region}/`));
}

const americanTimeZones = getTimeZonesForRegion('America');
console.log(americanTimeZones.length);
// Output: over 150

console.log(americanTimeZones.slice(0, 5));
// Output: [
//   "America/Adak",
//   "America/Anchorage",
//   "America/Anguilla",
//   "America/Antigua",
//   "America/Araguaina"
// ]

该函数会过滤完整列表,只包含以指定区域开头的标识符。你可以用任意区域名称调用它,以获得聚焦的列表。

你也可以筛选多个区域:

function getTimeZonesForRegions(regions) {
  const timeZones = Intl.supportedValuesOf('timeZone');
  return timeZones.filter(timeZone => {
    return regions.some(region => timeZone.startsWith(`${region}/`));
  });
}

const europeanAndAsianTimeZones = getTimeZonesForRegions(['Europe', 'Asia']);
console.log(europeanAndAsianTimeZones.length);
// Output: over 200

这为服务多个(但不是全部)区域的应用提供了灵活性。

搜索特定时区

当用户知道要查找的城市时,通过名称搜索可以帮助他们快速定位。

function searchTimeZones(query) {
  const timeZones = Intl.supportedValuesOf('timeZone');
  const lowerQuery = query.toLowerCase();

  return timeZones.filter(timeZone => {
    return timeZone.toLowerCase().includes(lowerQuery);
  });
}

const newYorkResults = searchTimeZones('new_york');
console.log(newYorkResults);
// Output: ["America/New_York"]

const londonResults = searchTimeZones('london');
console.log(londonResults);
// Output: ["Europe/London"]

const tokyoResults = searchTimeZones('tokyo');
console.log(tokyoResults);
// Output: ["Asia/Tokyo"]

该函数会对所有时区标识符进行不区分大小写的搜索。用户可以输入部分城市名称来查找匹配的时区。

为了提升用户体验,你可以支持部分匹配并处理空格:

function searchTimeZonesFlexible(query) {
  const timeZones = Intl.supportedValuesOf('timeZone');
  const normalizedQuery = query.toLowerCase().replace(/\s+/g, '_');

  return timeZones.filter(timeZone => {
    return timeZone.toLowerCase().includes(normalizedQuery);
  });
}

const results = searchTimeZonesFlexible('new york');
console.log(results);
// Output: ["America/New_York"]

此版本会将查询中的空格转换为下划线,以匹配时区标识符的格式。

验证时区标识符

当用户输入时区标识符时,需要在与 Intl.DateTimeFormat 一起使用之前,先验证该标识符是否有效。

function isValidTimeZone(timeZone) {
  const supportedTimeZones = Intl.supportedValuesOf('timeZone');
  return supportedTimeZones.includes(timeZone);
}

console.log(isValidTimeZone('America/New_York'));
// Output: true

console.log(isValidTimeZone('Europe/London'));
// Output: true

console.log(isValidTimeZone('Invalid/TimeZone'));
// Output: false

console.log(isValidTimeZone('EST'));
// Output: false

此函数用于检查给定字符串是否出现在支持的时区列表中。如果标识符无效,可以拒绝该输入或提示用户从有效选项中选择。

你也可以尝试直接使用该标识符并捕获错误:

function validateTimeZoneByCatch(timeZone) {
  try {
    new Intl.DateTimeFormat('en-US', { timeZone });
    return true;
  } catch (error) {
    return false;
  }
}

console.log(validateTimeZoneByCatch('America/New_York'));
// Output: true

console.log(validateTimeZoneByCatch('Invalid/TimeZone'));
// Output: false

这种方法虽然可行,但效率低于检查支持值列表。当性能很重要时,建议优先使用支持值检查。

显示时区当前偏移量

用户通常通过与 UTC 的偏移量来理解时区。在每个时区名称旁显示当前偏移量,有助于用户了解时差。

function getTimeZoneOffset(timeZone) {
  const date = new Date();
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone,
    timeZoneName: 'shortOffset'
  });

  const parts = formatter.formatToParts(date);
  const offsetPart = parts.find(part => part.type === 'timeZoneName');

  return offsetPart ? offsetPart.value : '';
}

const timeZones = [
  'America/New_York',
  'Europe/London',
  'Asia/Tokyo',
  'Australia/Sydney'
];

timeZones.forEach(timeZone => {
  const offset = getTimeZoneOffset(timeZone);
  console.log(`${timeZone}: ${offset}`);
});

// Output:
// America/New_York: GMT-4
// Europe/London: GMT+1
// Asia/Tokyo: GMT+9
// Australia/Sydney: GMT+11

该函数使用 shortOffset 的时区名称样式格式化日期,然后从格式化结果中提取偏移部分。这样可以显示每个时区相对于 UTC 的时差。

请注意,偏移量会随着夏令时变化。同一时区在冬季和夏季会显示不同的偏移量:

const newYorkWinter = getTimeZoneOffset('America/New_York');
// In January: GMT-5

const newYorkSummer = getTimeZoneOffset('America/New_York');
// In July: GMT-4

该函数返回的偏移量反映当前日期,因此会随着全年夏令时规则的变化自动更新。

构建带偏移量的完整时区选择器

结合分组、筛选和偏移量显示,可以创建一个功能完善的时区选择器:

function buildCompleteTimeZoneSelector() {
  const timeZones = Intl.supportedValuesOf('timeZone');
  const select = document.createElement('select');
  select.name = 'timeZone';

  const grouped = {};
  timeZones.forEach(timeZone => {
    const region = timeZone.split('/')[0];
    if (!grouped[region]) {
      grouped[region] = [];
    }
    grouped[region].push(timeZone);
  });

  Object.keys(grouped).sort().forEach(region => {
    const optgroup = document.createElement('optgroup');
    optgroup.label = region;

    grouped[region].forEach(timeZone => {
      const offset = getTimeZoneOffset(timeZone);
      const location = timeZone.split('/').slice(1).join('/');

      const option = document.createElement('option');
      option.value = timeZone;
      option.textContent = `${location} (${offset})`;
      optgroup.appendChild(option);
    });

    select.appendChild(optgroup);
  });

  return select;
}

const completeSelector = buildCompleteTimeZoneSelector();
document.body.appendChild(completeSelector);

该选择器按区域分组时区,显示可读的地名,并包含当前 UTC 偏移量。用户可以按区域快速查找自己的时区,并确认偏移量是否符合预期。

获取用户当前的时区

虽然获取所有支持的时区对于选择界面很有用,但通常你会希望知道用户当前的时区,以便作为默认值使用。

function getUserTimeZone() {
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
}

const userTimeZone = getUserTimeZone();
console.log(userTimeZone);
// Output: "America/New_York" (or user's actual time zone)

这将返回用户系统时区的 IANA 标识符。你可以用它在时区选择器中预先选中正确的选项:

function buildTimeZoneSelectorWithDefault() {
  const selector = buildCompleteTimeZoneSelector();
  const userTimeZone = getUserTimeZone();

  const options = selector.querySelectorAll('option');
  options.forEach(option => {
    if (option.value === userTimeZone) {
      option.selected = true;
    }
  });

  return selector;
}

const selectorWithDefault = buildTimeZoneSelectorWithDefault();
document.body.appendChild(selectorWithDefault);

这样可以创建一个已选中用户当前时区的选择器,减少用户确认位置时的操作步骤。

处理特殊时区

支持的时区列表中包含一些不遵循标准 Area/Location 格式的特殊标识符。

const timeZones = Intl.supportedValuesOf('timeZone');

const specialTimeZones = timeZones.filter(tz => !tz.includes('/'));
console.log(specialTimeZones);
// Output: ["UTC"]

UTC 标识符表示协调世界时(Coordinated Universal Time),没有时差,也没有夏令时变化。当你希望以通用参考时间而非本地时区显示时间时,这个标识符非常有用。

某些环境还包含其他特殊标识符,如 GMT,或像 Etc/GMT+5 这样的时区偏移量。如果你的应用只需要标准地理时区,可以将这些过滤掉:

function getGeographicTimeZones() {
  const timeZones = Intl.supportedValuesOf('timeZone');
  return timeZones.filter(timeZone => {
    return timeZone.includes('/') && !timeZone.startsWith('Etc/');
  });
}

const geographicTimeZones = getGeographicTimeZones();
console.log(geographicTimeZones.length);
// Output: over 400

这样可以过滤掉非地理标识符,仅保留基于城市的标准时区。

理解时区别名

IANA 数据库中包含多个指向相同时区规则的标识符。例如,Asia/CalcuttaAsia/Kolkata 都指向印度标准时间,但 Kolkata 是现代名称。

Intl.supportedValuesOf() 方法会返回规范标识符。如果用户提供了别名,配合 Intl.DateTimeFormat 也能正常使用:

const canonicalFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Kolkata',
  timeZoneName: 'long'
});

const aliasFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Calcutta',
  timeZoneName: 'long'
});

const date = new Date('2025-10-15T12:00:00Z');

console.log(canonicalFormatter.format(date));
// Output: time in Indian Standard Time

console.log(aliasFormatter.format(date));
// Output: time in Indian Standard Time

这两种格式化器生成的结果相同,因为别名在内部会映射到规范标识符。

但是,受支持的值列表只包含规范标识符。如果你需要校验用户输入,建议将别名规范化为其对应的规范形式:

function normalizeTimeZone(timeZone) {
  try {
    const formatter = new Intl.DateTimeFormat('en-US', { timeZone });
    return formatter.resolvedOptions().timeZone;
  } catch (error) {
    return null;
  }
}

console.log(normalizeTimeZone('Asia/Calcutta'));
// Output: "Asia/Kolkata"

console.log(normalizeTimeZone('America/New_York'));
// Output: "America/New_York"

console.log(normalizeTimeZone('Invalid/Zone'));
// Output: null

此函数使用提供的时区创建格式化器,并从解析选项中提取规范标识符。如果标识符无效,格式化器会抛出错误,函数将返回 null。

浏览器支持与兼容性

Intl.supportedValuesOf() 方法可在现代浏览器和 Node.js 版本中使用:

  • Chrome 99 及更高版本
  • Firefox 93 及更高版本
  • Safari 15.4 及更高版本
  • Edge 99 及更高版本
  • Node.js 18.0.0 及更高版本

对于较旧的环境,你可以通过特性检测该方法并提供降级方案:

function getSupportedTimeZones() {
  if (typeof Intl.supportedValuesOf === 'function') {
    return Intl.supportedValuesOf('timeZone');
  }

  return [
    'Africa/Cairo',
    'America/New_York',
    'America/Chicago',
    'America/Denver',
    'America/Los_Angeles',
    'Asia/Dubai',
    'Asia/Kolkata',
    'Asia/Tokyo',
    'Australia/Sydney',
    'Europe/London',
    'Europe/Paris',
    'Pacific/Auckland',
    'UTC'
  ];
}

const timeZones = getSupportedTimeZones();

此函数会检查 Intl.supportedValuesOf 是否存在并返回完整列表。如果不存在,则返回一个较小的常用时区硬编码列表。该降级方案可确保你的应用在旧环境下也能正常工作,并在现代环境中提供完整列表。