如何检查某个 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 9arab表示阿拉伯-印度数字:٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩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 版本开始支持。
calendar 和 numberingSystem 属性的支持范围更广,自 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属性返回显式设置的日历或undefinednumberingSystem属性返回显式设置的数字系统或undefined- 不同的 locale 默认使用不同的日历和数字系统
- 在创建格式化器时应用日历和数字系统信息
- 将用户偏好以带有 Unicode 扩展的 locale 字符串存储
- 在应用前验证 locale 是否支持特定的日历和数字系统
在构建语言选择器、存储用户偏好、校验格式化选项,或开发遵循日期和数字文化规范的国际化应用时,请使用这些方法。