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

在 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 通常表示时区内最大或最具代表性的城市。纽约代表美国东部时区,东京代表日本,悉尼代表澳大利亚东部时区。

一些标识符包括三个部分,用于区分同一国家内遵循不同时间规则的地区:

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

这些多部分标识符区分了同一国家内遵循不同时间规则的地区。

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

const underscoreExamples = [
  'America/New_York',    // 不是 "New York"
  'America/Los_Angeles', // 不是 "Los Angeles"
  'Asia/Ho_Chi_Minh'     // 不是 "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);
// 输出:超过 400

console.log(timeZones.slice(0, 10));
// 输出:[
//   "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));
// 输出:"2025年10月15日 下午12:00"

该方法保证它返回的每个标识符都可以与 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));
// 输出: [
//   "Africa", "America", "Antarctica", "Arctic",
//   "Asia", "Atlantic", "Australia", "Europe",
//   "Indian", "Pacific", "Etc"
// ]

console.log(grouped['America'].slice(0, 5));
// 输出: [
//   "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);

这将创建一个选择元素,其中时区出现在其区域标题下。选项文本仅显示标识符的城市部分,使列表更易于阅读。

按区域筛选时区

有时您只需要特定区域的时区。例如,仅为北美用户服务的应用程序可能只显示美洲的时区。

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

const americanTimeZones = getTimeZonesForRegion('America');
console.log(americanTimeZones.length);
// 输出:超过 150

console.log(americanTimeZones.slice(0, 5));
// 输出:[
//   "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);
// 输出:超过 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);
// 输出:["America/New_York"]

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

const tokyoResults = searchTimeZones('tokyo');
console.log(tokyoResults);
// 输出:["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);
// 输出:["America/New_York"]

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

验证时区标识符

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

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

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

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

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

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

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

您还可以尝试使用标识符并捕获错误:

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

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

console.log(validateTimeZoneByCatch('Invalid/TimeZone'));
// 输出: 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}`);
});

// 输出:
// 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');
// 一月: GMT-5

const newYorkSummer = getTimeZoneOffset('America/New_York');
// 七月: 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);
// 输出: "America/New_York"(或用户的实际时区)

这将返回用户系统时区的 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);
// 输出: ["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);
// 输出: 超过 400

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

理解时区别名

IANA 数据库中包含多个指向相同时区规则的标识符。例如,Asia/CalcuttaAsia/Kolkata 都指向印度标准时间(Indian Standard Time),但 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));
// 输出: 印度标准时间的时间

console.log(aliasFormatter.format(date));
// 输出: 印度标准时间的时间

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

然而,支持的值列表仅包含规范标识符。如果您正在验证用户输入,请考虑将别名规范化为其规范形式:

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'));
// 输出: "Asia/Kolkata"

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

console.log(normalizeTimeZone('Invalid/Zone'));
// 输出: 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 是否存在,如果存在则返回完整列表。如果不存在,则返回一个较小的常见时区的硬编码列表。该回退方案确保您的应用程序在较旧的环境中也能正常工作,同时在现代环境中提供完整列表。