Search code examples
javascriptdatedatetimecalendarhijri

Discrepancy in Javascript Intl.DateTimeFormat() Outputs for the Islamic (Hijri) Calendar between 'islamic' and 'ar-SA'


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


Solution

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