There 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-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.