如何检查某个 locale 使用的日历或数字系统

使用 JavaScript 检测并验证任意 locale 的日历系统和数字格式

简介

当泰国用户在你的 Web 应用中查看日期时,他们期望看到的是佛历日期,而不是西方国家常用的公历。同样,阿拉伯语用户希望看到数字以 ١٢٣ 的形式显示,而不是 123。不同文化采用不同的日历系统和数字系统,JavaScript 提供了检测特定 locale 所用系统的工具。

Intl.Locale API 包含了一些属性和方法,可以揭示某个 locale 使用的是哪种日历和数字系统。这些信息有助于你在格式化日期和数字时避免硬编码假设,确保符合不同文化的偏好。

本指南将介绍如何检查 locale 的日历和数字系统,理解这些系统的含义,并利用这些信息正确地格式化内容。

了解日历系统

日历系统是一种将时间组织为年、月、日的方式。虽然公历(格里高利历)应用最广,但许多文化出于宗教、历史或文化原因,采用了不同的日历系统。

常见的日历系统包括:

  • gregory:公历,主要用于西方国家
  • buddhist:佛历,泰国、柬埔寨和缅甸使用
  • islamic:伊斯兰阴历,穆斯林国家用于宗教目的
  • hebrew:希伯来历,以色列及犹太宗教活动使用
  • japanese:日本历,采用天皇年号
  • chinese:中国农历,用于传统节日
  • persian:波斯太阳历,伊朗和阿富汗使用
  • indian:印度国历
  • coptic:科普特历,埃及科普特基督徒使用

不同地区默认使用的日历系统各不相同,有些地区还常常同时使用多种日历系统。

了解数字系统

数字系统是一组用于表示数字的符号。西方国家使用拉丁数字(0、1、2、3、4、5、6、7、8、9),而其他文化则用不同的符号来表示相同的数值。

常见的数字系统包括:

  • latn 表示拉丁数字:0 1 2 3 4 5 6 7 8 9
  • arab 表示阿拉伯-印度数字:٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩
  • arabext 表示东阿拉伯-印度数字:۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹
  • deva 表示天城文数字:० १ २ ३ ४ ५ ६ ७ ८ ९
  • beng 表示孟加拉数字:০ ১ ২ ৩ ৪ ৫ ৬ ৭ ৮ ৯
  • thai 表示泰文数字:๐ ๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙
  • hanidec 表示中文十进制数字
  • fullwide 表示东亚排版中使用的全角数字

所有数字系统都表示相同的数值,只是根据语言的书写系统采用了不同的视觉符号。

检查某个语言环境的默认日历

getCalendars() 方法会返回一个数组,列出某个语言环境常用的日历系统,并按优先级排序。第一个元素即为默认日历。

const locale = new Intl.Locale("th-TH");
const calendars = locale.getCalendars();
console.log(calendars);
// ["buddhist", "gregory"]

泰语地区默认使用佛历,但也常用公历。这意味着为泰国用户格式化日期时,优先采用佛历。

不同的本地化区域会返回不同的日历偏好:

const usLocale = new Intl.Locale("en-US");
console.log(usLocale.getCalendars());
// ["gregory"]

const saLocale = new Intl.Locale("ar-SA");
console.log(saLocale.getCalendars());
// ["gregory", "islamic", "islamic-civil"]

const jpLocale = new Intl.Locale("ja-JP");
console.log(jpLocale.getCalendars());
// ["gregory", "japanese"]

美式英语仅使用公历。沙特阿拉伯的阿拉伯语通常同时使用公历和伊斯兰历。日语区域同时使用公历和日本年号历。

该数组包含该区域常用的所有日历,便于在适当情况下为用户提供多种日历选项。

检查某个区域的默认数字系统

getNumberingSystems() 方法会返回一个数组,列出该区域常用的数字系统。第一个元素为默认数字系统。

const locale = new Intl.Locale("ar-EG");
const numberingSystems = locale.getNumberingSystems();
console.log(numberingSystems);
// ["arab"]

埃及阿拉伯语默认使用阿拉伯-印度数字。在为埃及阿拉伯语用户格式化数字时,除非用户另有指定,应使用阿拉伯-印度数字系统。

不同的本地化区域使用不同的默认数字系统:

const usLocale = new Intl.Locale("en-US");
console.log(usLocale.getNumberingSystems());
// ["latn"]

const inLocale = new Intl.Locale("hi-IN");
console.log(inLocale.getNumberingSystems());
// ["latn"]

const thLocale = new Intl.Locale("th-TH");
console.log(thLocale.getNumberingSystems());
// ["latn"]

美式英语使用拉丁数字。印度的印地语在现代环境下也默认使用拉丁数字,尽管存在天城文数字。泰语在大多数现代场景下也默认使用拉丁数字。

许多现代本地化区域即使存在传统数字系统,也默认采用拉丁数字,反映了当前的使用习惯。

读取区域的活动日历

calendar 属性返回为某个区域明确设置的日历系统。如果未指定日历,则返回 undefined

const locale = new Intl.Locale("en-US");
console.log(locale.calendar);
// undefined

没有日历扩展的基础区域会返回 undefined。该区域在格式化日期时会使用其默认日历。

你可以通过 Unicode 扩展为区域显式指定日历偏好:

const locale = new Intl.Locale("en-US-u-ca-buddhist");
console.log(locale.calendar);
// "buddhist"

-u-ca-buddhist 扩展指定了佛历。calendar 属性返回 "buddhist"

您还可以在创建 locale 时设置 calendar:

const locale = new Intl.Locale("en-US", { calendar: "islamic" });
console.log(locale.calendar);
// "islamic"

options 对象的优先级高于 locale 字符串中指定的任何 calendar。

从 locale 读取当前使用的数字系统

numberingSystem 属性返回为 locale 明确设置的数字系统。如果未指定数字系统,则返回 undefined

const locale = new Intl.Locale("en-US");
console.log(locale.numberingSystem);
// undefined

没有数字系统扩展的基础 locale 会返回 undefined。该 locale 在格式化数字时将使用其默认数字系统。

您可以创建带有明确数字系统偏好的 locale:

const locale = new Intl.Locale("en-US-u-nu-arab");
console.log(locale.numberingSystem);
// "arab"

-u-nu-arab 扩展指定了阿拉伯-印度数字。numberingSystem 属性返回 "arab"

您还可以在创建 locale 时设置数字系统:

const locale = new Intl.Locale("ar-SA", { numberingSystem: "latn" });
console.log(locale.numberingSystem);
// "latn"

这会创建一个使用拉丁数字(而不是默认阿拉伯-印度数字)的阿拉伯语 locale。

检测 locale 是否有明确的 calendar

要检查 locale 是否设置了明确的 calendar(而不是使用默认值),请检查 calendar 属性是否已定义:

function hasExplicitCalendar(localeString) {
  const locale = new Intl.Locale(localeString);
  return locale.calendar !== undefined;
}

console.log(hasExplicitCalendar("en-US"));
// false

console.log(hasExplicitCalendar("en-US-u-ca-buddhist"));
// true

当您需要判断用户是否明确选择了 calendar 偏好,或是否应使用 locale 默认值时,这一区别很重要。

检测 locale 是否有明确的数字系统

同样,检查 numberingSystem 属性是否已定义,以检测明确的数字系统偏好:

function hasExplicitNumberingSystem(localeString) {
  const locale = new Intl.Locale(localeString);
  return locale.numberingSystem !== undefined;
}

console.log(hasExplicitNumberingSystem("ar-EG"));
// false

console.log(hasExplicitNumberingSystem("ar-EG-u-nu-latn"));
// true

第一个 locale 将使用埃及阿拉伯语的默认数字系统。第二个 locale 明确请求使用拉丁数字。

使用日历信息格式化日期

确定某个 locale 使用的日历后,在使用 Intl.DateTimeFormat 格式化日期时应用该日历:

const date = new Date("2025-03-15");

const gregorianLocale = new Intl.Locale("en-US");
const gregorianFormatter = new Intl.DateTimeFormat(gregorianLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(gregorianFormatter.format(date));
// "March 15, 2025"

const buddhistLocale = new Intl.Locale("th-TH");
const buddhistFormatter = new Intl.DateTimeFormat(buddhistLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(buddhistFormatter.format(date));
// "15 มีนาคม 2568"

泰国佛历显示的同一天为 2568 年,比公历早 543 年。

你也可以显式覆盖日历类型:

const date = new Date("2025-03-15");

const locale = new Intl.Locale("en-US", { calendar: "hebrew" });
const formatter = new Intl.DateTimeFormat(locale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(formatter.format(date));
// "15 Adar II 5785"

这会以希伯来历格式化日期,显示对应的希伯来年份和月份。

使用数字系统信息格式化数字

在使用 Intl.NumberFormat 格式化数字时,应用数字系统信息:

const number = 123456;

const latinLocale = new Intl.Locale("ar-EG", { numberingSystem: "latn" });
const latinFormatter = new Intl.NumberFormat(latinLocale);
console.log(latinFormatter.format(number));
// "123,456"

const arabicLocale = new Intl.Locale("ar-EG", { numberingSystem: "arab" });
const arabicFormatter = new Intl.NumberFormat(arabicLocale);
console.log(arabicFormatter.format(number));
// "١٢٣٬٤٥٦"

同一个数字根据数字系统的不同会显示为不同的数字符号。

构建日历选择器

利用日历信息构建用户界面,让用户选择其偏好的日历:

function getCalendarOptions(localeString) {
  const locale = new Intl.Locale(localeString);
  const calendars = locale.getCalendars();

  return calendars.map(calendar => ({
    value: calendar,
    label: calendar.charAt(0).toUpperCase() + calendar.slice(1)
  }));
}

const options = getCalendarOptions("ar-SA");
console.log(options);
// [
//   { value: "gregory", label: "Gregory" },
//   { value: "islamic", label: "Islamic" },
//   { value: "islamic-civil", label: "Islamic-civil" }
// ]

这会为沙特阿拉伯阿拉伯语用户创建一个适合的日历选项列表,他们通常会使用多种日历。

构建数字系统选择器

为数字系统偏好创建用户界面:

function getNumberingSystemOptions(localeString) {
  const locale = new Intl.Locale(localeString);
  const systems = locale.getNumberingSystems();

  const labels = {
    latn: "Western (0-9)",
    arab: "Arabic-Indic (٠-٩)",
    arabext: "Eastern Arabic (۰-۹)",
    deva: "Devanagari (०-९)",
    beng: "Bengali (০-৯)"
  };

  return systems.map(system => ({
    value: system,
    label: labels[system] || system
  }));
}

const options = getNumberingSystemOptions("ar-EG");
console.log(options);
// [{ value: "arab", label: "Arabic-Indic (٠-٩)" }]

这会为数字系统选项提供清晰的标签,向用户展示每个选项的实际效果。

校验日历兼容性

在将日历应用于 locale 之前,需验证该 locale 是否支持该日历:

function supportsCalendar(localeString, calendar) {
  const locale = new Intl.Locale(localeString);
  const supportedCalendars = locale.getCalendars();
  return supportedCalendars.includes(calendar);
}

console.log(supportsCalendar("th-TH", "buddhist"));
// true

console.log(supportsCalendar("en-US", "buddhist"));
// false

泰语 locale 支持佛历,但美式英语 locale 通常不使用该日历。

校验数字系统兼容性

检查某个 locale 是否支持特定数字系统:

function supportsNumberingSystem(localeString, numberingSystem) {
  const locale = new Intl.Locale(localeString);
  const supportedSystems = locale.getNumberingSystems();
  return supportedSystems.includes(numberingSystem);
}

console.log(supportsNumberingSystem("ar-EG", "arab"));
// true

console.log(supportsNumberingSystem("en-US", "arab"));
// false

埃及阿拉伯语支持阿拉伯-印度数字,但美式英语不使用这些数字。

确定用于格式化的有效日历

在格式化日期时,需要确定实际使用的是哪种日历:

function getEffectiveCalendar(localeString) {
  const locale = new Intl.Locale(localeString);

  if (locale.calendar) {
    return locale.calendar;
  }

  const defaultCalendars = locale.getCalendars();
  return defaultCalendars[0];
}

console.log(getEffectiveCalendar("th-TH"));
// "buddhist"

console.log(getEffectiveCalendar("en-US-u-ca-islamic"));
// "islamic"

此函数会返回显式设置的日历(如果有),否则返回该语言环境的默认日历。

确定用于格式化的有效数字系统

在格式化数字时,需要确定实际使用的是哪种数字系统:

function getEffectiveNumberingSystem(localeString) {
  const locale = new Intl.Locale(localeString);

  if (locale.numberingSystem) {
    return locale.numberingSystem;
  }

  const defaultSystems = locale.getNumberingSystems();
  return defaultSystems[0];
}

console.log(getEffectiveNumberingSystem("ar-EG"));
// "arab"

console.log(getEffectiveNumberingSystem("ar-EG-u-nu-latn"));
// "latn"

如果有显式设置的数字系统,则返回该系统,否则返回该语言环境的默认数字系统。

存储用户的日历偏好设置

当用户选择日历偏好时,将其作为带有扩展的 locale 字符串进行存储:

function setUserCalendarPreference(baseLocale, calendar) {
  const locale = new Intl.Locale(baseLocale, { calendar });
  return locale.toString();
}

const preference = setUserCalendarPreference("en-US", "buddhist");
console.log(preference);
// "en-US-u-ca-buddhist"

这会创建一个完整的 locale 字符串,保留了日历偏好。请将此字符串存储在用户设置或 cookies 中。

存储用户的数字系统偏好设置

数字系统偏好也以相同方式存储:

function setUserNumberingPreference(baseLocale, numberingSystem) {
  const locale = new Intl.Locale(baseLocale, { numberingSystem });
  return locale.toString();
}

const preference = setUserNumberingPreference("ar-SA", "latn");
console.log(preference);
// "ar-SA-u-nu-latn"

locale 字符串包含数字系统偏好,可直接用于格式化 API。

同时处理多项偏好设置

用户可以同时拥有日历和数字系统的偏好设置:

function createLocaleWithPreferences(baseLocale, options) {
  const locale = new Intl.Locale(baseLocale, {
    calendar: options.calendar,
    numberingSystem: options.numberingSystem
  });
  return locale.toString();
}

const preference = createLocaleWithPreferences("ar-SA", {
  calendar: "islamic",
  numberingSystem: "latn"
});
console.log(preference);
// "ar-SA-u-ca-islamic-nu-latn"

这会将多个格式化偏好合并为一个 locale 字符串。

检查已解析的格式化选项

创建格式化器后,需验证其实际使用的是哪种日历和数字系统:

const locale = new Intl.Locale("th-TH");
const formatter = new Intl.DateTimeFormat(locale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const options = formatter.resolvedOptions();
console.log(options.calendar);
// "buddhist"
console.log(options.numberingSystem);
// "latn"

resolvedOptions() 方法显示了解析默认值和用户偏好后,格式化器实际使用的日历和数字系统。

浏览器支持

Intl.Locale API 在所有现代浏览器中均受支持。getCalendars()getNumberingSystems() 方法需要较新的浏览器版本。Chrome 99、Firefox 99、Safari 15.4 和 Edge 99 支持这些方法。Node.js 从 18 版本开始支持。

calendarnumberingSystem 属性的支持范围更广,自 Intl.Locale 在 Chrome 74、Firefox 75、Safari 14 和 Node.js 12 引入以来就已可用。

在使用前请检查方法支持情况:

const locale = new Intl.Locale("th-TH");

if (typeof locale.getCalendars === "function") {
  const calendars = locale.getCalendars();
  console.log(calendars);
}

这样可以确保您的代码在支持 Intl.Locale 但缺少新方法的环境中也能正常运行。

总结

JavaScript 提供了工具,通过 Intl.Locale API 检测某个 locale 使用的日历和数字系统。这些工具帮助您为不同文化正确格式化日期和数字,无需硬编码假设。

关键概念:

  • 使用 getCalendars() 获取某个 locale 常用日历的数组
  • 使用 getNumberingSystems() 获取某个 locale 常用数字系统的数组
  • calendar 属性返回显式设置的日历或 undefined
  • numberingSystem 属性返回显式设置的数字系统或 undefined
  • 不同的 locale 默认使用不同的日历和数字系统
  • 在创建格式化器时应用日历和数字系统信息
  • 将用户偏好以带有 Unicode 扩展的 locale 字符串存储
  • 在应用前验证 locale 是否支持特定的日历和数字系统

在构建语言选择器、存储用户偏好、校验格式化选项,或开发遵循日期和数字文化规范的国际化应用时,请使用这些方法。