Javascript Intl.DateTimeFormat() 'islamic' 和 'ar-SA' 之间的伊斯兰(Hijri)日历输出存在差异
Discrepancy in Javascript Intl.DateTimeFormat() Outputs for the Islamic (Hijri) Calendar between 'islamic' and 'ar-SA'
2022 年 3 月 3 日是回历月(今年回历 1443 AH)的结束日;即 30 Rajab 1443 AH.
根据所有网站、应用程序、MS Office 和 Windows 日历,回历 1443 年回历为 30 天。
当使用 javascript Intl.DateTimeFormat()
使用 islamic
日历选项显示 3 March 2022 的伊斯兰(回历)日期时, 它将给出伊斯兰回历日期 (1 Shaʻban 1443 AH)。此结果是 Rajab 月后的一天(即下个月的 1 号),它计算出 Rajab 月为 29 天而不是 30 天。
但是,如果传递给 Intl.DateTimeFormat()
的选项是 ar-SA
(即阿拉伯-沙特阿拉伯),它将给出正确的结果。 这很奇怪,因为 ar-SA
语言环境默认使用伊斯兰 (Hijri) 日历。
这是 error/bug 还是 javascript 的正确内部工作原理?
除了使用 'ar-SA' 语言环境(但不使用外部库)之外,是否有更可靠的方法来获取 Javascript 中的伊斯兰日期?
参见下面的代码示例:
我已经在节点和 chrome 中对此进行了测试,它给出了相同的结果差异。
let date = new Date("2022-03-03");
let options = {year:'numeric', month:'long',day:'numeric'};
let format = new Intl.DateTimeFormat('ar-SA-u-nu-latn', options);
console.log("3 March 2022 with 'ar-SA' :"+format.format(date)+ " ==> means: 30 Rajab 1443 AH");
format = new Intl.DateTimeFormat('en-u-ca-islamic-nu-latn', options);
console.log("3 March 2022 with Islamic Calendar: "+format.format(date));
您看到的“相差一个”日期问题可能有以下三个原因:
- 日期初始化和日期格式化之间的时区不匹配
- 使用错误的伊斯兰日历(JS 实现通常提供 5 种不同的伊斯兰日历!)
- 用于 JS 日历计算的 ICU 库中的错误
我将在下面逐一介绍。
1.日期初始化和日期格式之间的时区不匹配
off-by-one-day 错误的最常见原因是(如 @RobG 在他上面的评论中指出的那样)声明 Date
值时使用的时区与时区不匹配在您想要的日历中格式化时使用。
当您使用 ISO 8601 字符串初始化 Date 实例时,Date 实例中存储的实际值是自 1970 年 1 月 1 日 UTC 以来的毫秒数。根据您的系统时区,new Date('2022-02-03')
可以是您系统时区的 2 月 3 日或 2 月 2 日。避免此问题的一种方法是在格式化时也使用 UTC:
new Date('2022-03-03').toLocaleDateString('en-US');
// outputs '3/2/2022' when run in San Francisco
new Date('2022-03-03').toLocaleDateString('en-US', { timeZone: 'UTC' });
// outputs '3/3/2022' as expected. UTC is used both for declaring and formatting.
new Date('2022-03-03').toLocaleDateString(
'en-SA-u-ca-islamic-umalqura',
{ timeZone: 'UTC', month: 'long', day: 'numeric', year: 'numeric' }
);
// outputs 'Rajab 30, 1443 AH' as expected
请注意,我在上面使用了 Date.toLocaleDateString
,但结果与您使用 Intl.DateTimeFormat.format
时的结果相同。两种方法的参数和实现相同。
2。使用错误的伊斯兰日历(JS 实现通常提供 5 种不同的伊斯兰日历!)
第二个更微妙的问题是 JavaScript 使用了多种伊斯兰历法。支持的日历列表在这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendars。摘自该页面,以下是支持的伊斯兰日历变体:
islamic
- Islamic calendar
islamic-umalqura
- Islamic calendar, Umm al-Qura
islamic-tbla
- Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch)
islamic-civil
- Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch)
islamic-rgsa
- Islamic calendar, Saudi Arabia sighting
(还有一个已弃用的 islamicc
日历,但应改用 islamic-civil
。)
ar-SA
语言环境的默认日历是 islamic-umalqura
日历,而不是 islamic
日历。验证:
new Intl.DateTimeFormat('ar-SA').resolvedOptions();
// {
// calendar: "islamic-umalqura"
// day: "numeric"
// locale: "ar-SA"
// month: "numeric"
// numberingSystem: "arab"
// timeZone: "America/Los_Angeles"
// year: "numeric"
// }
不同的伊斯兰历变化会产生不同的日历日期。例如:
calendars = ["islamic", "islamic-umalqura", "islamic-tbla", "islamic-civil", "islamic-rgsa"];
options = { timeZone: 'UTC', month: 'long', day: 'numeric', year: 'numeric' };
date = new Date('2022-03-03');
calendars.forEach(calendar => {
formatted = date.toLocaleDateString(`en-SA-u-ca-${calendar}`, options);
console.log(`${calendar}: ${formatted}`);
});
// The code above outputs the following:
// islamic: Shaʻban 1, 1443 AH
// islamic-umalqura: Rajab 30, 1443 AH
// islamic-tbla: Rajab 30, 1443 AH
// islamic-civil: Rajab 29, 1443 AH
// islamic-rgsa: Shaʻban 1, 1443 AH
3。用于JS的日历计算的ICU库中的错误
出现意外日期的第三个可能原因是 JS 引擎中的日历计算代码存在错误。据我所知,所有主流浏览器都将日历计算委托给一个名为 ICU
. If you're using the correct time zone and calendar variation and there's still a problem with the calculation, then you may want to try filing an issue in the ICU JIRA site: https://unicode-org.atlassian.net/jira/software/c/projects/ICU/issues/.
的库
顺便说一句,在回答这个问题时,我注意到 Intl.DateTimeFormat constructor where the list of supported calendars is wrong. I filed https://github.com/mdn/content/pull/12764 的 MDN 文档中有一个错误来修复内容。此 PR 已合并,但生产 MDN 站点可能需要一段时间才能使用固定内容更新。
根据Justin Grant下面提供的优秀信息和数据,我对五(5)个伊斯兰历的输出进行了比较:
- 伊斯兰
- islamic-umalqura
- islamic-tbla
- islamic-civil
- islamic-rgsa
输出比较是在 50 年(从 1980 年 1 月 1 日到 2030 年 12 月 31 日)期间进行的。
结果输出如下:
============================================================
islamic diff from islamic-tbla :6369 days (34.19%)
islamic diff from islamic-rgsa :0 days ( 0.00%)
islamic diff from islamic-umalqura :11425 days (61.33%)
islamic diff from islamic-civil :15919 days (85.46%)
------------------------------------------------------------
islamic-tbla diff from islamic-rgsa :6369 days (34.19%)
islamic-tbla diff from islamic-umalqura :10777 days (57.85%)
islamic-tbla diff from islamic-civil :18628 days (100.00%)
-------------------------------------------------------------
islamic-rgsa diff from islamic-umalqura :11425 days (61.33%)
islamic-rgsa diff from islamic-civil :15919 days (85.46%)
-------------------------------------------------------------
islamic-umalqura diff from islamic-civil :9049 days (48.58%)
=============================================================
结果给出了以下一般信息:
默认 islamic
日历下的日期始终与 islamic-rgsa
一致。不确定默认 islamic
使用与 islamic rgsa
相同的代码还是根据 region
.
选择其他类型
islamic-civil
日历下的日期与所有其他日历格式的差异最大(在 85% 到 100% 之间)。
新 islamic-umalqura
日历中的日期与其他日历相差大约 50% 到 62%。
根据对一些伊斯兰网站和应用程序的观察,新的 islamic-umalqura
日历是最常用的伊斯兰日历。在欧洲和北美也是首选。
因为区域设置 ar-SA
默认使用 islamic-umalqura
日历,ar-SA
的日期输出(和月份长度)将不同于islamic
超过 61% 的时间使用日历。
下面提供了我使用过的测试代码,但请注意它需要大约25秒才能给出输出;敬请期待.
console.log("Comparing 50 years results under the 5 islamic calendar formats (from year 1980 to 2030)");
console.log("=".repeat(60));
let startDate = new Date("1980-01-01"); // date to start test from
let options = {year: 'numeric', month: 'long', day: 'numeric'};
let d = startDate;
let diff = [0,0,0,0,0,0,0,0,0,0]; // array to hold the diff results
let totalDays = 18628; // total days approx 50 Gregorian years
for (let i=0; i<totalDays; i++) {
let dateIslamic = new Intl.DateTimeFormat('en-u-ca-islamic' ,options).format(d),
dateIslamicTBLA = new Intl.DateTimeFormat('en-u-ca-islamic-tbla' ,options).format(d),
dateIslamicRGSA = new Intl.DateTimeFormat('en-u-ca-islamic-rgsa' ,options).format(d),
dateIslamicUMQ = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura',options).format(d),
dateIslamicCIVL = new Intl.DateTimeFormat('en-u-ca-islamic-civil' ,options).format(d);
if (dateIslamic != dateIslamicTBLA) diff[0]++;
if (dateIslamic != dateIslamicRGSA) diff[1]++;
if (dateIslamic != dateIslamicUMQ) diff[2]++;
if (dateIslamic != dateIslamicCIVL) diff[3]++;
if (dateIslamicTBLA != dateIslamicRGSA) diff[4]++;
if (dateIslamicTBLA != dateIslamicUMQ) diff[5]++;
if (dateIslamicTBLA != dateIslamicCIVL) diff[6]++;
if (dateIslamicRGSA != dateIslamicUMQ) diff[7]++;
if (dateIslamicRGSA != dateIslamicCIVL) diff[8]++;
if (dateIslamicUMQ != dateIslamicCIVL) diff[9]++;
d = new Date(d.setDate(d.getDate() + 1)); // next day
}
console.log("islamic diff from islamic-tbla :"+perc(0));
console.log("islamic diff from islamic-rgsa :"+perc(1));
console.log("islamic diff from islamic-umalqura :"+perc(2));
console.log("islamic diff from islamic-civil :"+perc(3));
console.log("-".repeat(50));
console.log("islamic-tbla diff from islamic-rgsa :"+perc(4));
console.log("islamic-tbla diff from islamic-umalqura :"+perc(5));
console.log("islamic-tbla diff from islamic-civil :"+perc(6));
console.log("-".repeat(50));
console.log("islamic-rgsa diff from islamic-umalqura :"+perc(7));
console.log("islamic-rgsa diff from islamic-civil :"+perc(8));
console.log("-".repeat(50));
console.log("islamic-umalqura diff from islamic-civil :"+perc(9));
function perc(n) {return + diff[n]+" days (" +((diff[n]/totalDays)*100).toFixed(2)+"%)";}
2022 年 3 月 3 日是回历月(今年回历 1443 AH)的结束日;即 30 Rajab 1443 AH.
根据所有网站、应用程序、MS Office 和 Windows 日历,回历 1443 年回历为 30 天。
当使用 javascript Intl.DateTimeFormat()
使用 islamic
日历选项显示 3 March 2022 的伊斯兰(回历)日期时, 它将给出伊斯兰回历日期 (1 Shaʻban 1443 AH)。此结果是 Rajab 月后的一天(即下个月的 1 号),它计算出 Rajab 月为 29 天而不是 30 天。
但是,如果传递给 Intl.DateTimeFormat()
的选项是 ar-SA
(即阿拉伯-沙特阿拉伯),它将给出正确的结果。 这很奇怪,因为 ar-SA
语言环境默认使用伊斯兰 (Hijri) 日历。
这是 error/bug 还是 javascript 的正确内部工作原理?
除了使用 'ar-SA' 语言环境(但不使用外部库)之外,是否有更可靠的方法来获取 Javascript 中的伊斯兰日期?
参见下面的代码示例:
我已经在节点和 chrome 中对此进行了测试,它给出了相同的结果差异。
let date = new Date("2022-03-03");
let options = {year:'numeric', month:'long',day:'numeric'};
let format = new Intl.DateTimeFormat('ar-SA-u-nu-latn', options);
console.log("3 March 2022 with 'ar-SA' :"+format.format(date)+ " ==> means: 30 Rajab 1443 AH");
format = new Intl.DateTimeFormat('en-u-ca-islamic-nu-latn', options);
console.log("3 March 2022 with Islamic Calendar: "+format.format(date));
您看到的“相差一个”日期问题可能有以下三个原因:
- 日期初始化和日期格式化之间的时区不匹配
- 使用错误的伊斯兰日历(JS 实现通常提供 5 种不同的伊斯兰日历!)
- 用于 JS 日历计算的 ICU 库中的错误
我将在下面逐一介绍。
1.日期初始化和日期格式之间的时区不匹配
off-by-one-day 错误的最常见原因是(如 @RobG 在他上面的评论中指出的那样)声明 Date
值时使用的时区与时区不匹配在您想要的日历中格式化时使用。
当您使用 ISO 8601 字符串初始化 Date 实例时,Date 实例中存储的实际值是自 1970 年 1 月 1 日 UTC 以来的毫秒数。根据您的系统时区,new Date('2022-02-03')
可以是您系统时区的 2 月 3 日或 2 月 2 日。避免此问题的一种方法是在格式化时也使用 UTC:
new Date('2022-03-03').toLocaleDateString('en-US');
// outputs '3/2/2022' when run in San Francisco
new Date('2022-03-03').toLocaleDateString('en-US', { timeZone: 'UTC' });
// outputs '3/3/2022' as expected. UTC is used both for declaring and formatting.
new Date('2022-03-03').toLocaleDateString(
'en-SA-u-ca-islamic-umalqura',
{ timeZone: 'UTC', month: 'long', day: 'numeric', year: 'numeric' }
);
// outputs 'Rajab 30, 1443 AH' as expected
请注意,我在上面使用了 Date.toLocaleDateString
,但结果与您使用 Intl.DateTimeFormat.format
时的结果相同。两种方法的参数和实现相同。
2。使用错误的伊斯兰日历(JS 实现通常提供 5 种不同的伊斯兰日历!)
第二个更微妙的问题是 JavaScript 使用了多种伊斯兰历法。支持的日历列表在这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendars。摘自该页面,以下是支持的伊斯兰日历变体:
islamic
- Islamic calendar
islamic-umalqura
- Islamic calendar, Umm al-Qura
islamic-tbla
- Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch)
islamic-civil
- Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch)
islamic-rgsa
- Islamic calendar, Saudi Arabia sighting
(还有一个已弃用的 islamicc
日历,但应改用 islamic-civil
。)
ar-SA
语言环境的默认日历是 islamic-umalqura
日历,而不是 islamic
日历。验证:
new Intl.DateTimeFormat('ar-SA').resolvedOptions();
// {
// calendar: "islamic-umalqura"
// day: "numeric"
// locale: "ar-SA"
// month: "numeric"
// numberingSystem: "arab"
// timeZone: "America/Los_Angeles"
// year: "numeric"
// }
不同的伊斯兰历变化会产生不同的日历日期。例如:
calendars = ["islamic", "islamic-umalqura", "islamic-tbla", "islamic-civil", "islamic-rgsa"];
options = { timeZone: 'UTC', month: 'long', day: 'numeric', year: 'numeric' };
date = new Date('2022-03-03');
calendars.forEach(calendar => {
formatted = date.toLocaleDateString(`en-SA-u-ca-${calendar}`, options);
console.log(`${calendar}: ${formatted}`);
});
// The code above outputs the following:
// islamic: Shaʻban 1, 1443 AH
// islamic-umalqura: Rajab 30, 1443 AH
// islamic-tbla: Rajab 30, 1443 AH
// islamic-civil: Rajab 29, 1443 AH
// islamic-rgsa: Shaʻban 1, 1443 AH
3。用于JS的日历计算的ICU库中的错误
出现意外日期的第三个可能原因是 JS 引擎中的日历计算代码存在错误。据我所知,所有主流浏览器都将日历计算委托给一个名为 ICU
. If you're using the correct time zone and calendar variation and there's still a problem with the calculation, then you may want to try filing an issue in the ICU JIRA site: https://unicode-org.atlassian.net/jira/software/c/projects/ICU/issues/.
顺便说一句,在回答这个问题时,我注意到 Intl.DateTimeFormat constructor where the list of supported calendars is wrong. I filed https://github.com/mdn/content/pull/12764 的 MDN 文档中有一个错误来修复内容。此 PR 已合并,但生产 MDN 站点可能需要一段时间才能使用固定内容更新。
根据Justin Grant下面提供的优秀信息和数据,我对五(5)个伊斯兰历的输出进行了比较:
- 伊斯兰
- islamic-umalqura
- islamic-tbla
- islamic-civil
- islamic-rgsa
输出比较是在 50 年(从 1980 年 1 月 1 日到 2030 年 12 月 31 日)期间进行的。
结果输出如下:
============================================================
islamic diff from islamic-tbla :6369 days (34.19%)
islamic diff from islamic-rgsa :0 days ( 0.00%)
islamic diff from islamic-umalqura :11425 days (61.33%)
islamic diff from islamic-civil :15919 days (85.46%)
------------------------------------------------------------
islamic-tbla diff from islamic-rgsa :6369 days (34.19%)
islamic-tbla diff from islamic-umalqura :10777 days (57.85%)
islamic-tbla diff from islamic-civil :18628 days (100.00%)
-------------------------------------------------------------
islamic-rgsa diff from islamic-umalqura :11425 days (61.33%)
islamic-rgsa diff from islamic-civil :15919 days (85.46%)
-------------------------------------------------------------
islamic-umalqura diff from islamic-civil :9049 days (48.58%)
=============================================================
结果给出了以下一般信息:
默认 islamic
日历下的日期始终与 islamic-rgsa
一致。不确定默认 islamic
使用与 islamic rgsa
相同的代码还是根据 region
.
islamic-civil
日历下的日期与所有其他日历格式的差异最大(在 85% 到 100% 之间)。
新 islamic-umalqura
日历中的日期与其他日历相差大约 50% 到 62%。
根据对一些伊斯兰网站和应用程序的观察,新的 islamic-umalqura
日历是最常用的伊斯兰日历。在欧洲和北美也是首选。
因为区域设置 ar-SA
默认使用 islamic-umalqura
日历,ar-SA
的日期输出(和月份长度)将不同于islamic
超过 61% 的时间使用日历。
下面提供了我使用过的测试代码,但请注意它需要大约25秒才能给出输出;敬请期待.
console.log("Comparing 50 years results under the 5 islamic calendar formats (from year 1980 to 2030)");
console.log("=".repeat(60));
let startDate = new Date("1980-01-01"); // date to start test from
let options = {year: 'numeric', month: 'long', day: 'numeric'};
let d = startDate;
let diff = [0,0,0,0,0,0,0,0,0,0]; // array to hold the diff results
let totalDays = 18628; // total days approx 50 Gregorian years
for (let i=0; i<totalDays; i++) {
let dateIslamic = new Intl.DateTimeFormat('en-u-ca-islamic' ,options).format(d),
dateIslamicTBLA = new Intl.DateTimeFormat('en-u-ca-islamic-tbla' ,options).format(d),
dateIslamicRGSA = new Intl.DateTimeFormat('en-u-ca-islamic-rgsa' ,options).format(d),
dateIslamicUMQ = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura',options).format(d),
dateIslamicCIVL = new Intl.DateTimeFormat('en-u-ca-islamic-civil' ,options).format(d);
if (dateIslamic != dateIslamicTBLA) diff[0]++;
if (dateIslamic != dateIslamicRGSA) diff[1]++;
if (dateIslamic != dateIslamicUMQ) diff[2]++;
if (dateIslamic != dateIslamicCIVL) diff[3]++;
if (dateIslamicTBLA != dateIslamicRGSA) diff[4]++;
if (dateIslamicTBLA != dateIslamicUMQ) diff[5]++;
if (dateIslamicTBLA != dateIslamicCIVL) diff[6]++;
if (dateIslamicRGSA != dateIslamicUMQ) diff[7]++;
if (dateIslamicRGSA != dateIslamicCIVL) diff[8]++;
if (dateIslamicUMQ != dateIslamicCIVL) diff[9]++;
d = new Date(d.setDate(d.getDate() + 1)); // next day
}
console.log("islamic diff from islamic-tbla :"+perc(0));
console.log("islamic diff from islamic-rgsa :"+perc(1));
console.log("islamic diff from islamic-umalqura :"+perc(2));
console.log("islamic diff from islamic-civil :"+perc(3));
console.log("-".repeat(50));
console.log("islamic-tbla diff from islamic-rgsa :"+perc(4));
console.log("islamic-tbla diff from islamic-umalqura :"+perc(5));
console.log("islamic-tbla diff from islamic-civil :"+perc(6));
console.log("-".repeat(50));
console.log("islamic-rgsa diff from islamic-umalqura :"+perc(7));
console.log("islamic-rgsa diff from islamic-civil :"+perc(8));
console.log("-".repeat(50));
console.log("islamic-umalqura diff from islamic-civil :"+perc(9));
function perc(n) {return + diff[n]+" days (" +((diff[n]/totalDays)*100).toFixed(2)+"%)";}