如何在非公历日历中显示年份和纪元?

使用 Intl.DateTimeFormat 的 calendar 和 era 选项,可以显示伊斯兰、希伯来、农历、波斯等多种日历系统的年份和纪元

引言

公历(格里历)从一个统一的起点开始计年,因此 2024 年就是一个简单的数字。而其他日历系统的计年方式各不相同。伊斯兰历从公元 622 年开始计年。希伯来历从传统的创世年份(距今数千年)开始计年。中国农历采用 60 年一个周期的干支纪年,而不是连续的数字年份。

这些不同的计年系统意味着同一时刻在不同日历中对应的年份各不相同。例如,公历 2024 年 10 月 15 日,在伊斯兰历中是 1446 年,在希伯来历中是 5785 年,在中国农历中是 2024 年(甲辰,jiǎ-chén)。

JavaScript 的 Intl.DateTimeFormat 提供了选项,可以显示任意日历系统的年份和纪元。calendar 选项用于选择使用哪种日历。yearera 选项控制年份和纪元的显示方式。对于采用纪年名称而非数字的日历,formatToParts() 方法可以同时获取纪年名称和对应的公历年份。

不同日历系统的年份差异

日历系统在三个基本方面存在差异:起始计年点、年份编号方式,以及是否使用纪元。

公历从公元 1 年开始顺序计年。伊斯兰历从公元 622 年的 1 AH(希吉来历)开始。希伯来历从公元前 3761 年的 1 年开始。每种日历都有自己的纪元(epoch),即年份计数的起点。

有些日历采用连续递增的年份编号,如公历、伊斯兰历、希伯来历和波斯历都是如此。其他日历则采用周期循环的方式,年份名称会重复。例如,农历(中国历法)采用 60 年为一个周期的纪年方式,60 年后循环一次。

纪元将时间划分为有名称的时期。公历使用公元前(BC)和公元(AD)来区分。日本历法采用天皇年号。伊斯兰历和希伯来历通常使用从各自纪元起始的单一纪元。中国农历则不以纪元划分,而是依靠周期内的年份名称来标记年份。

在伊斯兰历中显示年份

伊斯兰历的年份从希吉拉(Hijra,穆罕默德于公元 622 年从麦加迁徙到麦地那)开始计算。伊斯兰历 1 年对应公历 622 年。伊斯兰历采用阴历月份,因此其年份比公历短。这意味着伊斯兰历年份推进得更快,目前在公历 2024 年时已到 1446 年。

你可以通过 calendar 选项并设置为 islamic,或在区域标识符中添加 Unicode 扩展 -u-ca-islamic 来指定伊斯兰历。

const date = new Date('2024-10-15');

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

console.log(formatter.format(date));
// "Rabi' II 12, 1446 AH"

年份会显示为 1446,这是对应于 2024 年 10 月 15 日的伊斯兰历年份。在英文环境下显示伊斯兰日期时,纪元 "AH"(希吉拉后)会自动出现。

不同的本地化环境会根据各自的习惯格式化伊斯兰日期。

const date = new Date('2024-10-15');

const en = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(en.format(date));
// "Rabi' II 12, 1446 AH"

const ar = new Intl.DateTimeFormat('ar-SA', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(ar.format(date));
// "١٢ ربيع الآخر ١٤٤٦ هـ"

阿拉伯语环境下会使用阿拉伯-印度数字,并以阿拉伯语显示月份名称。纪元标识会变为 "هـ"(希吉拉的阿拉伯语缩写)。

控制伊斯兰历纪元显示

era 选项用于控制纪元指示器的显示方式。该选项接受三个值:long 表示完整纪元名称,short 表示缩写形式,narrow 表示最简形式。

const date = new Date('2024-10-15');

const long = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  era: 'long'
});
console.log(long.format(date));
// "1446 Anno Hegirae"

const short = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  era: 'short'
});
console.log(short.format(date));
// "1446 AH"

const narrow = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  era: 'narrow'
});
console.log(narrow.format(date));
// "1446 A"

long 值会显示 "Anno Hegirae"(拉丁语,意为“希吉拉之年”)。short 值会显示 "AH"。narrow 值只显示 "A"。

伊斯兰历变体

伊斯兰历有多种变体,采用不同的计算方法。JavaScript 支持五种变体:islamicislamic-civilislamic-tblaislamic-umalquraislamic-rgsa

islamic-umalqura 变体采用沙特阿拉伯的官方历法,基于天文观测。islamic-civil 变体采用固定算法,月份交替为 29 天和 30 天。

const date = new Date('2024-10-15');

const umalqura = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic-umalqura',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(umalqura.format(date));
// "Rabi' II 12, 1446 AH"

const civil = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic-civil',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(civil.format(date));
// "Rabi' II 11, 1446 AH"

由于采用不同的月界定计算方法,这些变体在同一公历日期下可能会产生不同的日序。

显示希伯来历年份

希伯来历的年份从公元前 3761 年的传统创世日期开始计数,因此希伯来历年份远大于公历年份。希伯来历 5785 年对应公历 2024 年。

你可以通过 calendar 选项并设置为 hebrew,或在区域标识符中添加 Unicode 扩展 -u-ca-hebrew 来指定希伯来历。

const date = new Date('2024-10-15');

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

console.log(formatter.format(date));
// "Tishrei 13, 5785"

年份会显示为 5785,即对应于 2024 年 10 月 15 日的希伯来历年份。希伯来历是阴阳合历,月份以月亮为准,但通过周期性闰月保持与太阳年同步。

希伯来语地区的日期会使用希伯来数字和月份名称。

const date = new Date('2024-10-15');

const he = new Intl.DateTimeFormat('he-IL', {
  calendar: 'hebrew',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(he.format(date));
// "י״ג בתשרי ה׳תשפ״ה"

希伯来语地区会将日期完全以希伯来语显示,包括用作数字的希伯来字母。

在中国农历中显示年份

中国农历采用六十年为一个周期的纪年方式,而不是顺序编号。每一年都有一个由两个汉字组成的名称:一个天干和一个地支。2024 年是甲辰(jiǎ-chén),在传统中国占星学中意为“木龙”。

由于中国农历采用纪年名称,显示时需要与顺序编号的日历不同的处理方式。

const date = new Date('2024-10-15');

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

console.log(formatter.format(date));
// "Ninth Month 13, 2024(jiǎ-chén)"

格式化输出会同时包含公历 2024 年和“甲辰”年名(括号内为 jiǎ-chén)。这种双重表示有助于用户理解纪年名称及其对应的公历年份。

中文地区的日期会以汉字显示。

const date = new Date('2024-10-15');

const zh = new Intl.DateTimeFormat('zh-CN', {
  calendar: 'chinese',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(zh.format(date));
// "2024甲辰年九月十三"

中文地区会用汉字显示年名,格式会将年名自然地融入日期字符串中。

提取年名及相关年份

采用纪年名称的日历会提供两个信息:周期内的年名和对应的公历年份。formatToParts() 方法会将这两部分分离。

const date = new Date('2024-10-15');

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

const parts = formatter.formatToParts(date);
console.log(parts);

parts 数组包含两个相关条目:

[
  { type: 'relatedYear', value: '2024' },
  { type: 'yearName', value: '甲辰' },
  // ... other parts
]

relatedYear 部分包含四位数的公历年份,yearName 部分包含纪年名称。这样分离后,您可以在自定义格式中单独或同时使用这两个值。

您可以提取这些部分以创建自定义日期显示。

const date = new Date('2024-10-15');

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

const parts = formatter.formatToParts(date);
const yearName = parts.find(p => p.type === 'yearName')?.value;
const relatedYear = parts.find(p => p.type === 'relatedYear')?.value;

console.log(`Year name: ${yearName}`);
// "Year name: 甲辰"

console.log(`Gregorian year: ${relatedYear}`);
// "Gregorian year: 2024"

此技术适用于任何使用命名年份或周期的日历系统。

在波斯历中显示年份

波斯历,也称为太阳希吉拉历(Solar Hijri calendar),从公元 622 年的希吉拉(Hijra)开始计年,与伊斯兰历采用相同的纪元。但波斯历采用太阳月而非阴历月,因此在结构上更接近公历(Gregorian calendar)。

您可以通过将 calendar 选项设置为 persian,或在区域标识符中添加 Unicode 扩展 -u-ca-persian 来指定波斯历。

const date = new Date('2024-10-15');

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

console.log(formatter.format(date));
// "Mehr 24, 1403 AP"

年份显示为 1403,这是对应于 2024 年 10 月 15 日的波斯历年份。纪元 "AP"(Anno Persico)会以英文格式显示。

波斯语区域的日期会使用波斯数字和月份名称。

const date = new Date('2024-10-15');

const fa = new Intl.DateTimeFormat('fa-IR', {
  calendar: 'persian',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(fa.format(date));
// "۲۴ مهر ۱۴۰۳ ه‍.ش."

波斯语区域会用波斯(法尔西)字符和数字显示日期。纪元标识 "ه‍.ش." 是太阳希吉拉纪元的波斯语缩写。

组合日历和纪元选项

您可以组合 calendarera 选项,以精确控制不同日历系统中年份和纪元的显示方式。这样可以让您对日期格式化有更精细的控制。

const date = new Date('2024-10-15');

const gregorian = new Intl.DateTimeFormat('en-US', {
  calendar: 'gregory',
  year: 'numeric',
  month: 'long',
  era: 'short'
});
console.log(gregorian.format(date));
// "October 2024 AD"

const islamic = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  era: 'short'
});
console.log(islamic.format(date));
// "Rabi' II 1446 AH"

const hebrew = new Intl.DateTimeFormat('en-US', {
  calendar: 'hebrew',
  year: 'numeric',
  month: 'long',
  era: 'short'
});
console.log(hebrew.format(date));
// "Tishrei 5785"

由于设置了 era 选项,公历和伊斯兰历的日期会显示纪元标识。而希伯来历在此格式下通常不显示纪元标识,因为希伯来日历格式一般省略该项。

您还可以混合不同的语言环境和日历系统,实现用一种语言显示日期,同时采用另一种文化的日历。

const date = new Date('2024-10-15');

const englishIslamic = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(englishIslamic.format(date));
// "Rabi' II 12, 1446 AH"

const arabicIslamic = new Intl.DateTimeFormat('ar-SA', {
  calendar: 'islamic',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
console.log(arabicIslamic.format(date));
// "١٢ ربيع الآخر ١٤٤٦ هـ"

这两种格式化器都使用伊斯兰历,但英文语言环境会生成英文月份名称和拉丁数字,而阿拉伯语环境则会生成阿拉伯语月份名称和阿拉伯-印度数字。

跨多种日历显示年份

面向国际用户的应用程序通常需要在多个日历系统中显示同一日期。您可以创建多个格式化器来并行展示不同的日期表示。

const date = new Date('2024-10-15');

const calendars = [
  { name: 'Gregorian', calendar: 'gregory' },
  { name: 'Islamic', calendar: 'islamic' },
  { name: 'Hebrew', calendar: 'hebrew' },
  { name: 'Persian', calendar: 'persian' }
];

calendars.forEach(cal => {
  const formatter = new Intl.DateTimeFormat('en-US', {
    calendar: cal.calendar,
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
  console.log(`${cal.name}: ${formatter.format(date)}`);
});

这将产生:

Gregorian: October 15, 2024
Islamic: Rabi' II 12, 1446 AH
Hebrew: Tishrei 13, 5785
Persian: Mehr 24, 1403 AP

每个日历都用自己的纪年系统和纪元标识来表示同一时刻。

常见用例

国际化应用需要以用户期望的日历系统显示日期。例如,伊斯兰银行应用会用伊斯兰历显示交易日期。

const transactionDate = new Date('2024-10-15');

const formatter = new Intl.DateTimeFormat('ar-SA', {
  calendar: 'islamic-umalqura',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  era: 'short'
});

console.log(`Transaction date: ${formatter.format(transactionDate)}`);
// "Transaction date: ١٢ ربيع الآخر ١٤٤٦ هـ"

宗教日历决定节日和纪念日的日期。犹太历应用会用希伯来日期显示节日。

const roshHashanah2024 = new Date('2024-10-03');

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

console.log(`Rosh Hashanah: ${formatter.format(roshHashanah2024)}`);
// "Rosh Hashanah: א׳ בתשרי ה׳תשפ״ה"

历史类应用会用当时采用的日历系统显示日期。例如,关于古波斯的应用会用波斯历显示日期。

const historicalDate = new Date('2024-03-20');

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

console.log(`Persian New Year: ${formatter.format(historicalDate)}`);
// "Persian New Year: Farvardin 1, 1403 Anno Persico"

总结

非公历日历的纪年起点和数字系统各不相同。伊斯兰历从公元 622 年开始,目前年份大约为 1446 年。希伯来历从公元前 3761 年开始,目前年份大约为 5785 年。中国农历采用 60 年一个周期的干支纪年,而不是顺序编号。

JavaScript 的 Intl.DateTimeFormat 通过 calendar 选项支持这些日历系统。可选值包括 islamichebrewchinesepersian 等。year 选项用于控制年份是否显示,era 选项用于控制纪元标识的显示方式。

使用命名年份的日历通过 formatToParts() 提供两项信息。yearName 部分包含循环年名称,relatedYear 部分包含对应的公历年份。这使应用程序能够显示任一或两者的数值。

不同的本地化环境会以不同方式格式化同一日历。例如,伊斯兰历在英文环境下使用拉丁数字显示,在阿拉伯语环境下则使用阿拉伯-印度数字。格式会自动适应本地化习惯,同时保持日历的年份编号系统不变。