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
|
Show 2 more comments
2 Answers
Reset to default 14There are three possible reasons for the "off by one" date problems you're seeing:
- Time zone mismatch between date initialization and date formatting
- Using the wrong variation of the Islamic calendar (JS implementations typically offer 5 different Islamic calendars!)
- 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:
- islamic
- islamic-umalqura
- islamic-tbla
- islamic-civil
- 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)+"%)";}
Rajab 30, 1443 AH
? – T.J. Crowder Commented Feb 5, 2022 at 9:54let 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 meRajab 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:58ar-SA-u-nu-latn
and Shaʻban 1 foren-u-ca-islamic-nu-latn
, I suspect it's not a bug, but something obscure but not incorrect about howDateTimeFormat
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:06new 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:37new 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