如何在不同历法中格式化日期?

使用 JavaScript 的 calendar 选项,为全球用户以伊斯兰、希伯来、日文、中文等多种历法显示日期

引言

大多数开发者认为日期在任何地方的处理方式都一样。你为 2025 年 3 月 15 日创建一个 Date 对象,格式化后展示给用户。然而,当你的用户使用不同的历法系统时,这种假设就不成立了。

在许多文化中,公历(格里高利历)下的 2025 年 3 月 15 日有着完全不同的表示方式。对于伊斯兰历的用户,同一时刻是 1447 年斋月 16 日。对于希伯来历的用户,则是 5785 年亚达尔二月 14 日。对于日本官方场合的用户,则显示为令和 7 年 3 月 15 日。

历法系统决定了社会如何计量时间。它们定义了年份的起始、月份的组织方式以及日期的编号规则。JavaScript 的 Intl.DateTimeFormat API 支持 17 种以上的历法系统,可以根据每位用户的文化和宗教背景显示相应的日期。

本课程将介绍什么是历法系统、它们为何存在,以及如何在 JavaScript 中使用不同历法格式化日期。

什么是历法系统

历法系统定义了组织和计量时间的规则。每种系统都规定了年份的起点、月份的数量、日期的编号方式,以及用于纪年起点的纪元。

广泛用于国际场合的格里高利历(公历)以耶稣基督的推定诞生年为纪元起点。它采用 12 个月,每月天数为 28 至 31 天不等,每四年有一次闰年,使一年为 365 天。

其他日历系统采用不同的结构和起始点。伊斯兰历使用 12 个阴历月,每年共 354 或 355 天。希伯来历结合了阴历月和阳历年的对齐。日本历法则采用随着天皇更替而变化的年号纪年方式。

这些系统的存在,是因为不同文化根据宗教意义、天文观测和历史事件发展出了各自的计时方法。许多文化至今仍在宗教活动、官方文件和文化活动中,与公历(格里历)并行使用传统历法。

为什么日历系统对应用程序很重要

为不同文化背景用户服务的应用程序,需要以用户能够理解和期望的格式显示日期。面向穆斯林用户的祷告时间应用应显示伊斯兰历日期。用于犹太宗教活动的应用需要显示希伯来历日期。日本政府相关应用则要求采用日本年号格式。

使用错误的日历系统会导致混淆,甚至让应用对目标用户无法使用。例如,将伊斯兰节日日期显示为公历,用户就需要手动换算日期。将希伯来历事件以公历格式展示,会掩盖这些日期的宗教意义。

同一个 Date 对象,无论采用哪种日历系统,都代表同一时刻。变化的是你如何将这一时刻格式化展示。JavaScript 允许你在任何受支持的日历系统中格式化日期,无需复杂的转换逻辑。

如何使用 calendar 选项

Intl.DateTimeFormat 构造函数接受一个 calendar 选项,用于指定格式化日期时所用的日历系统。将日历标识符作为字符串传递即可。

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

const gregorianFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'gregory',
  dateStyle: 'long'
});

const islamicFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(gregorianFormatter.format(date));
// Output: "March 15, 2025"

console.log(islamicFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

同一个 Date 对象根据不同的历法系统会生成不同的格式化字符串。公历显示为 2025 年 3 月 15 日。伊斯兰历显示为 1447 年斋月 16 日。

calendar 选项可以独立于 locale 使用。通过将 calendar 选项与相应的 locale 结合,可以用英文、阿拉伯文、法文或其他任何语言格式化伊斯兰日期。

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

const arabicFormatter = new Intl.DateTimeFormat('ar-SA', {
  calendar: 'islamic',
  dateStyle: 'long'
});

const englishFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(arabicFormatter.format(date));
// Output: "١٦ رمضان ١٤٤٧ هـ"

console.log(englishFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

历法系统决定显示哪一天,而 locale 决定语言和格式规范。

使用伊斯兰历格式化日期

伊斯兰历是一种阴历,共有 12 个月,每月 29 或 30 天。完整的阴历年约为 354 天,比阳历公历年短 10 到 11 天。这导致伊斯兰日期会随着时间在公历中逐渐提前。

JavaScript 支持多种伊斯兰历变体。islamic 标识符采用基于算法的计算方式。islamic-umalqura 标识符采用沙特阿拉伯使用的 Umm al-Qura 历。islamic-civil 标识符采用可预测的表格计算方式。

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

const islamicFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

const umalquraFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic-umalqura',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(islamicFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

console.log(umalquraFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

对于大多数面向穆斯林用户的应用,任何一种伊斯兰历变体都能正确工作。它们之间的差异很小,主要影响宗教节日的精确日期判定。

使用希伯来历格式化日期

希伯来历是一种用于犹太宗教活动的阴阳合历。它通过在特定年份增加闰月,将阴历月份与阳历年同步,从而使节日与季节保持一致。

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

const hebrewFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'hebrew',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(hebrewFormatter.format(date));
// Output: "14 Adar II 5785"

希伯来历使用希伯来语的月份名称,如 Nisan、Iyar、Sivan 和 Tammuz。在闰年,日历中会包含 Adar I 和 Adar II。年份计数表示自犹太传统创世以来的年数。

您可以用希伯来语格式化希伯来日期,方便母语者阅读。

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

const hebrewFormatter = new Intl.DateTimeFormat('he-IL', {
  calendar: 'hebrew',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(hebrewFormatter.format(date));
// Output: "י״ד באדר ב׳ ה׳תשפ״ה"

希伯来语输出会用希伯来字母表示数字,月份名称也采用希伯来文。

使用日本历格式化日期

日本历采用以在位天皇命名的年号。现行年号为令和,自 2019 年 5 月 1 日德仁天皇即位时开始。年份从每个年号的起始年算起。

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

const japaneseFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'japanese',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(japaneseFormatter.format(date));
// Output: "March 15, 7 Reiwa"

年份显示为 令和7年,表示令和时代的第七年。这种格式用于日本官方文件、政府表格和正式场合。

使用日语格式化时,会生成传统的日本日期格式。

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

const japaneseFormatter = new Intl.DateTimeFormat('ja-JP', {
  calendar: 'japanese',
  era: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(japaneseFormatter.format(date));
// Output: "令和7年3月15日"

输出内容包括年号(令和)、年份(7年)、月份(3月)和日期(15日),均使用日文字符。

使用中国农历格式化日期

中国农历是一种阴阳合历,用于确定中国传统节日,如春节和中秋节。该历法结合了农历月份和二十四节气。

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

const chineseFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'chinese',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(chineseFormatter.format(date));
// Output: "Second Month 16, 2025(yi-si)"

中国农历年份包含一个循环纪年(如本例中的乙巳),以及数字年份。月份名称采用数字称谓,如“正月”“二月”。

中文格式化会用汉字显示日期。

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

const chineseFormatter = new Intl.DateTimeFormat('zh-CN', {
  calendar: 'chinese',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(chineseFormatter.format(date));
// Output: "2025乙巳年二月十六"

使用波斯历格式化日期

波斯历,也称为太阳希吉来历,是伊朗和阿富汗的官方历法。它采用太阳年制,共有 12 个月,结构上类似于公历(格里高利历),但月份长度和纪元不同。

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

const persianFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'persian',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(persianFormatter.format(date));
// Output: "Esfand 24, 1403 AP"

波斯历 1403 年对应公历 2025 年。缩写 AP 代表 Anno Persico(波斯纪年)。

波斯语格式化会使用波斯数字和月份名称。

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

const persianFormatter = new Intl.DateTimeFormat('fa-IR', {
  calendar: 'persian',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(persianFormatter.format(date));
// Output: "۲۴ اسفند ۱۴۰۳ ه‍.ش."

其他支持的历法系统

JavaScript 支持多种历法系统,适用于不同的文化和宗教场景。

buddhist 历法在公历年份基础上加 543 年,主要用于泰国及其他上座部佛教国家。

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

const buddhistFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'buddhist',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(buddhistFormatter.format(date));
// Output: "March 15, 2568 BE"

indian 历法是印度的官方民用历法。

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

const indianFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'indian',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(indianFormatter.format(date));
// Output: "Phalguna 24, 1946 Saka"

coptic 历法由科普特正教会使用。ethiopic 历法用于埃塞俄比亚。

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

const copticFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'coptic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

const ethiopicFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'ethiopic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(copticFormatter.format(date));
// Output: "Amshir 6, 1741 ERA1"

console.log(ethiopicFormatter.format(date));
// Output: "Yekatit 6, 2017 ERA1"

探索支持的历法

Intl.supportedValuesOf() 方法会返回 JavaScript 实现支持的所有历法标识符数组。

const calendars = Intl.supportedValuesOf('calendar');
console.log(calendars);
// Output: ["buddhist", "chinese", "coptic", "dangi", "ethioaa", "ethiopic",
//          "gregory", "hebrew", "indian", "islamic", "islamic-civil",
//          "islamic-tbla", "islamic-umalqura", "japanese", "persian", "roc"]

具体列表会因 JavaScript 引擎和浏览器版本而异。该方法始终按字母顺序返回历法。

你可以在使用前检查某个历法是否受支持。

const calendars = Intl.supportedValuesOf('calendar');
const supportsIslamic = calendars.includes('islamic');

if (supportsIslamic) {
  const formatter = new Intl.DateTimeFormat('en-US', {
    calendar: 'islamic',
    dateStyle: 'long'
  });
  console.log(formatter.format(new Date()));
}

此检查可防止在不支持所有历法系统的环境中出现错误。

使用 Unicode 扩展指定历法

你可以在区域标识符中使用 Unicode 扩展键来指定历法系统,而不是通过 options 参数。只需在区域字符串中添加 -u-ca-,后跟历法标识符。

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

const formatter = new Intl.DateTimeFormat('en-US-u-ca-islamic', {
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output: "Ramadan 16, 1447 AH"

-u-ca-islamic 扩展名指示格式化程序使用伊斯兰历。这与在 options 对象中传递 calendar: 'islamic' 的效果相同。

当你在 locale 字符串和 options 对象中都指定了日历时,以 options 对象为准。

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

const formatter = new Intl.DateTimeFormat('en-US-u-ca-islamic', {
  calendar: 'hebrew',
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output: "14 Adar II 5785"

options 对象中的希伯来历会覆盖 locale 字符串中的伊斯兰历。当你需要以编程方式控制日历系统时,请使用 options 参数。当你需要根据用户偏好或配置使用 locale 标识符时,请使用 Unicode 扩展。

各地语言环境如何确定默认日历

如果未指定日历,格式化程序会使用该语言环境的默认日历。大多数语言环境默认使用公历(格里高利历),但有些语言环境会使用不同的默认值。

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

const usFormatter = new Intl.DateTimeFormat('en-US', {
  dateStyle: 'long'
});

const saFormatter = new Intl.DateTimeFormat('ar-SA', {
  dateStyle: 'long'
});

const ilFormatter = new Intl.DateTimeFormat('he-IL', {
  dateStyle: 'long'
});

console.log(usFormatter.format(date));
// Output: "March 15, 2025"

console.log(saFormatter.format(date));
// Output: "١٦ رمضان ١٤٤٧ هـ"

console.log(ilFormatter.format(date));
// Output: "15 במרץ 2025"

美国英语环境默认使用公历。沙特阿拉伯的阿拉伯语环境默认使用伊斯兰历。以色列的希伯来语环境虽然有深厚的希伯来历传统,但默认仍为公历。

你可以通过调用 resolvedOptions() 方法来查询格式化程序正在使用哪种日历。

const formatter = new Intl.DateTimeFormat('ar-SA', {
  dateStyle: 'long'
});

const options = formatter.resolvedOptions();
console.log(options.calendar);
// Output: "islamic-umalqura"

解析后的选项显示 ar-SA 语言环境默认采用 islamic-umalqura 日历变体。

何时需要显式设置日历

在为用户以其本地语言环境格式化日期时,建议让语言环境自动决定日历系统。例如,沙特阿拉伯的用户期望看到伊斯兰历日期,日本用户在官方场合期望看到日本年号格式。语言环境的默认设置会自动满足这些需求。

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

const formatter = new Intl.DateTimeFormat(navigator.language, {
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output varies by user's locale and default calendar

当你需要在特定日历系统下显示日期,而不受用户本地化设置影响时,应显式设置 calendar。比如,祈祷时间应用应始终显示伊斯兰历日期,希伯来日历应用应始终显示希伯来历日期。

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

const formatter = new Intl.DateTimeFormat(navigator.language, {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output shows Islamic calendar date in user's language

这样可以确保日历系统与应用的功能相匹配,同时仍然尊重用户的语言偏好。

当需要显示与本地默认日历系统不同的日期时,应显式设置 calendar。例如,你可能希望向希伯来用户显示公历日期,或向英语用户显示伊斯兰历日期。

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

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

console.log(formatter.format(date));
// Output: "14 Adar II 5785"

calendar 与其他选项的组合使用

calendar 选项可与所有其他 Intl.DateTimeFormat 选项配合使用。你可以将其与 dateStyletimeStyle、组件选项如 weekdaymonth,以及其他选项如 timeZone 结合使用。

const date = new Date('2025-03-15T14:30:00');

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'full',
  timeStyle: 'long',
  timeZone: 'America/New_York'
});

console.log(formatter.format(date));
// Output: "Saturday, Ramadan 16, 1447 AH at 2:30:00 PM EST"

格式化器会对日期应用伊斯兰历,同时采用完整日期样式、长时间样式和指定时区。

你也可以将 calendar 选项与单独的组件选项结合使用。

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

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'japanese',
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  era: 'long'
});

console.log(formatter.format(date));
// Output: "Saturday, March 15, 7 Reiwa"

在使用如日本历或佛历等带有纪元名称的日历时,era 选项尤为重要。

使用不同日历格式化日期区间

formatRange() 方法可与 calendar 选项配合,格式化任意日历系统下的日期区间。

const startDate = new Date('2025-03-15');
const endDate = new Date('2025-03-25');

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(formatter.formatRange(startDate, endDate));
// Output: "Ramadan 16 – 26, 1447 AH"

格式化器会对两个日期都应用伊斯兰历,并将其作为区间格式化,智能省略重复信息。

复用格式化器以提升性能

创建 Intl.DateTimeFormat 实例时会处理本地化数据和日历系统信息。当需要用相同的日历和本地化设置格式化多个日期时,应只创建一次格式化器并复用。

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

const dates = [
  new Date('2025-03-15'),
  new Date('2025-04-20'),
  new Date('2025-06-10')
];

dates.forEach(date => {
  console.log(formatter.format(date));
});
// Output:
// "Ramadan 16, 1447 AH"
// "Dhuʻl-Qiʻdah 22, 1447 AH"
// "Dhuʻl-Hijjah 15, 1447 AH"

这种方法在格式化日期数组或在应用程序中显示大量时间戳时可以提升性能。

需要注意的事项

日历系统定义了不同文化如何组织和计量时间。JavaScript 通过 Intl.DateTimeFormat API 支持 17 种以上的日历系统,包括伊斯兰历、希伯来历、和历、农历、波斯历、佛历和科普特历。

可以在选项对象中通过 calendar 选项,或在 locale 字符串中添加 Unicode 扩展来指定日历系统。calendar 选项决定使用哪种日历系统来格式化日期,而 locale 决定语言和格式规范。

在一般情况下,建议让 locale 自动决定默认日历系统来显示日期。如果应用需要特定的日历系统,无论用户的 locale 偏好如何,都应显式设置 calendar。

calendar 选项可与所有其他日期格式化选项配合使用,包括样式、组件选项、时区和日期范围。格式化多个日期时,建议复用 formatter 实例以获得更好的性能。