最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

date - Discrepancy in Javascript Intl.DateTimeFormat() Outputs for the Islamic (Hijri) Calendar between 'islamic&#39

programmeradmin2浏览0评论

The 3rd March 2022 is the end of this Hijri Month (month of Rajab for this year 1443 AH); i.e. 30 Rajab 1443 AH.

The month of Rajab for the year 1443 AH is 30 days in accordance with the Islamic (Hijri) Calendar in accordance with all websites, applications, MS Office, and Windows calendars.

When using the javascript Intl.DateTimeFormat() to display the Islamic (Hijri) date for the 3 March 2022 using the islamic calendar option, it will give the Islamic Hijri Date of (1 Shaʻban 1443 AH). This result is one day after the month of Rajab (i.e. the 1st of the following month) and it calculated the month Rajab to be 29 days rather than 30 days.

However, if the option passed to the Intl.DateTimeFormat() is ar-SA (i.e. arabic-Saudi Arabia), it will give the correct result. This is strange because the ar-SA locale uses the Islamic (Hijri) calendar by default.

Is this an error/bug or is it the correct internal workings of javascript?

Is there a more robust method to get the Islamic Date in Javascript other than using the 'ar-SA' locale (but not using external libraries)?

See the code example below:

I have tested this in node and chrome and it gives the same resulting discrepancy.

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));

The 3rd March 2022 is the end of this Hijri Month (month of Rajab for this year 1443 AH); i.e. 30 Rajab 1443 AH.

The month of Rajab for the year 1443 AH is 30 days in accordance with the Islamic (Hijri) Calendar in accordance with all websites, applications, MS Office, and Windows calendars.

When using the javascript Intl.DateTimeFormat() to display the Islamic (Hijri) date for the 3 March 2022 using the islamic calendar option, it will give the Islamic Hijri Date of (1 Shaʻban 1443 AH). This result is one day after the month of Rajab (i.e. the 1st of the following month) and it calculated the month Rajab to be 29 days rather than 30 days.

However, if the option passed to the Intl.DateTimeFormat() is ar-SA (i.e. arabic-Saudi Arabia), it will give the correct result. This is strange because the ar-SA locale uses the Islamic (Hijri) calendar by default.

Is this an error/bug or is it the correct internal workings of javascript?

Is there a more robust method to get the Islamic Date in Javascript other than using the 'ar-SA' locale (but not using external libraries)?

See the code example below:

I have tested this in node and chrome and it gives the same resulting discrepancy.

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));

Share Improve this question asked Feb 5, 2022 at 9:47 Mohsen AlyafeiMohsen Alyafei 5,5573 gold badges35 silver badges54 bronze badges 7
  • Am I reading the question correctly that the output you'd expect from the Islamic Calendar is Rajab 30, 1443 AH? – T.J. Crowder Commented Feb 5, 2022 at 9:54
  • I wondered if it could be a timezone thing so I tried let date = new Date(2022, 3 - 1, 3, 12);, but it gives the same result (for me here in the UK). new Date(2022, 3 - 1, 3, 12) gives me Rajab 29, 1443 AH so it definitely seems to be leaving off the 30th. I wish I could say why. :-) – T.J. Crowder Commented Feb 5, 2022 at 9:58
  • 1 Since V8 (in Chrome), SpiderMonkey (in Firefox), and JavaScriptCore (in Safari on iOS) all do the same thing (30 Rajab for ar-SA-u-nu-latn and Shaʻban 1 for en-u-ca-islamic-nu-latn, I suspect it's not a bug, but something obscure but not incorrect about how DateTimeFormat interprets the options and locale you're giving it. But I really don't know (and you'd certainly know better than I). I hope you find out what it is! – T.J. Crowder Commented Feb 5, 2022 at 10:06
  • 1 Note that in new Date("2022-03-03") the date is parsed as UTC so you're just asking for timezone issues. You can fix that by constructing the date as local, i.e. new Date(2022, 2, 3) which will return the expected date "Rajab 30, 1443 AH". – RobG Commented Feb 5, 2022 at 13:37
  • 1 BTW, new Date(2022, 2, 3, 3, 35, 9) returns Rajab 30, but adding 1 second returns Shaʻban 1. What is significant about the time 03:35:10? – RobG Commented Feb 5, 2022 at 14:43
 |  Show 2 more comments

2 Answers 2

Reset to default 14

There are three possible reasons for the "off by one" date problems you're seeing:

  1. Time zone mismatch between date initialization and date formatting
  2. Using the wrong variation of the Islamic calendar (JS implementations typically offer 5 different Islamic calendars!)
  3. Bugs in the ICU library used for JS's calendar calculations

I'll cover each of these below.

1. Time zone mismatch between date initialization and date formatting

The most common reason for off-by-one-day errors is (as @RobG noted in his comments above) a mismatch between the time zone used when declaring the Date value and the time zone used when formatting it in your desired calendar.

When you initialize a Date instance using an ISO 8601 string, the actual value stored in the Date instance is the number of milliseconds since January 1, 1970 UTC. Depending on your system time zone, new Date('2022-02-03') can be February 3 or February 2 in your system time zone. One way to evade this problem is to use UTC when formatting too:

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

Note that I'm using Date.toLocaleDateString above, but the result is the same as if you'd used Intl.DateTimeFormat.format. The parameters and implementations are the same for both methods.

2. Using the wrong variation of the Islamic calendar (JS implementations typically offer 5 different Islamic calendars!)

A second and more subtle issue is that multiple variations of islamic calendars are used in JavaScript. The list of supported calendars is here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendars. Excerpting from that page, here are the supported Islamic calendar variations:

  • 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

(There's also a deprecated islamicc calendar, but islamic-civil should be used instead.)

The default calendar for the ar-SA locale is the islamic-umalqura calendar, not the islamic calendar. To verify:

new Intl.DateTimeFormat('ar-SA').resolvedOptions();
// {
//   calendar: "islamic-umalqura"
//   day: "numeric"
//   locale: "ar-SA"
//   month: "numeric"
//   numberingSystem: "arab"
//   timeZone: "America/Los_Angeles"
//   year: "numeric"
// }

Different Islamic calendar variations will yield different calendar dates. For example:

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. Bugs in the ICU library used for JS's calendar calculations

A third possible reason for unexpected dates is if there's a bug in the calendar calculation code inside the JS engine. As far as I know, all major browsers delegate their calendar calculations to a library called 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/.

BTW, while answering this question I noticed a bug in the MDN documentation for the Intl.DateTimeFormat constructor where the list of supported calendars is wrong. I filed https://github.com/mdn/content/pull/12764 to fix the content. This PR has already been merged, but it may take a while for the production MDN site to be updated with the fixed content.

Following from the excellent information and data provided below by Justin Grant, I made a comparison of the output of the five (5) Islamic calendars:

  1. islamic
  2. islamic-umalqura
  3. islamic-tbla
  4. islamic-civil
  5. islamic-rgsa

The comparison of the output was made over a period of 50 years (from 1 Jan 1980 to 31 Dec 2030).

The resulting output is as follows:

============================================================
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%)
=============================================================

The result gives the following general information:

The dates under the default islamic calendar always agree with the islamic-rgsa. Not sure if the default islamic uses the same code as the islamic rgsa or it selects the other types based on the region.

The dates under the islamic-civil calendar are the most different from all other calendar formats (between 85% to 100%).

The dates under the new islamic-umalqura calendar are in variance with the other calendars by around 50% to 62%.

From observations on some islamic sites and applications, the new islamic-umalqura calendar is the most commonly used Islamic calendar. It is also preferred in Europe and North America.

Because the locale ar-SA uses the islamic-umalqura calendar by default, the date outputs (and months length) of the ar-SA will differ from that of the islamic calendar in over 61% of the time.

The test code I have used is provided below, but please note that it needs about 25 seconds to give the output; please wait for it.

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)+"%)";}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论