How to convert other calendar dates into Gregorian?

  internationalization, localization, windows

Short Version

How can i convert the ar-SA date "09/03/43" into October 15, 2021?

Long Version (aka research effort)

Parsing a date string from most locales is pretty easy. For example, take today’s date (October 15, 2021) in Germany:

15.10.2021

We ask Windows for the LOCALE_SShortDate format string, which says that in Germany (de-DE) dates are formatted as:

dd.MM.yyyy

And so we parse the original string into tokens:

  • 15.10.2021

And we can retrieve the pieces:

  • dd: 15 (day)
  • MM: 10 (month)
  • yyyy: 2021 (year)

And that’s that, we now know it is:

  • October 15, 2021

Now lets do it with another locale

The current date is Saudi Arabia, in their Arabic (Saudi Arabia) ar-SA locale is:

09/03/43

This string does not represent

  • September 3, 1943
  • March 9, 1943

The arabic calendar is something else entirely.

But i can ask Windows for the LOCALE_SShortDate format string:

dd/MM/yyyy

And break the original string into tokens:

  • 09/03.43

But now is where things fall apart, because:

  • dd: 09 (day)
  • MM: 03 (month)
  • yyyy: 43 (year)

Is not correct – at least not correct for the purposes of understanding what date it is.

Locale Info

In order to try to see what Windows know about this locale, and what clue might there be about figuring out how to convert a locale’s year into a Gregorian year, i dump all the LOCALE_ constants:

Item User Default Invariant en-US Pseudo (Pseudo) Arabic (Saudi Arabia) Pseudo (Pseudo Mirrored) Pseudo (Pseudo Asia)
Locale Identifier 0x0400 (1024) 0x007F (127) 0x0409 (1033) 0x0501 (1281) 0x0401 (1025) 0x09FF (2559) 0x05FE (1534)
LocaleName en-US en-US qps-ploc ar-SA qps-plocm qps-ploca
Sample Integer -12,345,678,901 -12,345,678,901 -12,345,678,901 -123,,4567,,8901 -12,345,678,901 12,345,678,901- -12,345,678,901
Sample Currency ($12,345,678,901.12) (¤12,345,678,901.12) ($12,345,678,901.12) -$123,,4567,,8901.124 -12,345,678,901.12 ر.س.‏ ر.س.‏12,345,678,901.12- -¥12,345,678,901.12
Sample Float -12,345,678,901.12 -12,345,678,901.12 -12,345,678,901.12 -123,,4567,,8901.124 -12,345,678,901.12 12,345,678,901.12- -12,345,678,901.12
Sample Date 10/15/2021 10/15/2021 10/15/2021 15/10/21 09/03/43 09/03/43 2021/10/15
Sample Time 2::51::20 ᴘᴍ 14:51:20 2:51:20 PM 14:51:20 02:51:20 م 02:51:20 م 14:51:20
Sample DateTime 10/15/2021 2::51::20 ᴘᴍ 10/15/2021 14:51:20 10/15/2021 2:51:20 PM 15/10/21 14:51:20 09/03/43 02:51:20 م 09/03/43 02:51:20 م 2021/10/15 14:51:20
Sample Long Date Friday, October 15, 2021 Friday, 15 October 2021 Friday, October 15, 2021 [₣гïδáу !!], 15 ōf [Øçťŏвëŕ !!!] ōf 2021 09/ربيع الأول/1443 09/ربيع الأول/1443 2021年10月15日
LOCALE_ILANGUAGE (language id) 409 007f 409 501 401 09ff 05fe
LOCALE_SLANGUAGE (localized name of language) English (United States) Invariant Language (Invariant Country) English (United States) Pseudo (Pseudo) Arabic (Saudi Arabia) Pseudo (Pseudo Mirrored) Pseudo (Pseudo Asia)
LOCALE_SENGLANGUAGE (English name of language) English Invariant Language English Pseudo Arabic Pseudo Pseudo
LOCALE_SABBREVLANGNAME (abbreviated language name) ENU IVL ENU ENU ARA ARA JPN
LOCALE_SNATIVELANGNAME (native name of language) English Invariant Language English [Þšēūďθ Ļдηğμåģέ !!!] العربية [Рѕёůđó Ľαʼnġџåģе !!!] [Þѕєΰδō Łªиģųāģз !!!]
LOCALE_ICOUNTRY (country code) 1 1 1 61 966 966 81
LOCALE_SCOUNTRY (localized name of country) United States Invariant Country United States Pseudo Saudi Arabia Pseudo Mirrored Pseudo Asia
LOCALE_SENGCOUNTRY (English name of country) United States Invariant Country United States Pseudo Saudi Arabia Pseudo Mirrored Pseudo Asia
LOCALE_SABBREVCTRYNAME (abbreviated country name) USA USA USA SAU SAU JPN
LOCALE_ILANGUAGE (native name of country) United States Invariant Country United States [Рšěüđõ !] المملكة العربية السعودية [Ρšеϋďŏ Мīґґθřėď !!!] [Ρ§зŭďό Α§ΐд !!]
LOCALE_IDEFAULTLANGUAGE (default language id) 409 409 409 501 401 09ff 05fe
LOCALE_IDEFAULTCOUNTRY (default country code) 1 1 1 61 966 966 81
LOCALE_IDEFAULTCODEPAGE (default oem code page) 437 437 437 852 720 720 932
LOCALE_IDEFAULTANSICODEPAGE (default ansi code page) 1252 1252 1252 1250 1256 1256 932
LOCALE_IDEFAULTMACCODEPAGE (default mac code page) 10000 10000 10000 10029 10004 10004 10001
LOCALE_SLIST (list item separator) , , , ,, ; ; ,
LOCALE_IMEASURE (0 = metric, 1 = US) 1 0 1 1 0 0 0
LOCALE_SDECIMAL (decimal separator) . . . . . . .
LOCALE_STHOUSAND (thousand separator) , , , ,, , , ,
LOCALE_SGROUPING (digit grouping) 3;0 3;0 3;0 4;0 3;0 3;0 3;0
LOCALE_IDIGITS (number of fractional digits) 2 2 2 3 2 2 2
LOCALE_ILZERO (leading zeros for decimal) 1 1 1 1 1 1 1
LOCALE_INEGNUMBER (negative number mode) 1 1 1 1 1 3 1
LOCALE_SNATIVEDIGITS (native ascii 0-9) 123456789 123456789 123456789 123456789 ١٢٣٤٥٦٧٨٩ ١٢٣٤٥٦٧٨٩ 123456789
LOCALE_SCURRENCY (local monetary symbol) $ ¤ $ $ ر.س.‏ ر.س.‏ ¥
LOCALE_SINTLSYMBOL (intl monetary symbol) USD XDR USD USD SAR SAR JPY
LOCALE_SMONDECIMALSEP (monetary decimal separator) . . . . . . .
LOCALE_SMONTHOUSANDSEP (monetary thousand separator) , , , ,, , , ,
LOCALE_SMONGROUPING (monetary grouping) 3;0 3;0 3;0 4;0 3;0 3;0 3;0
LOCALE_ICURRDIGITS (local monetary digits) 2 2 2 3 2 2 0
LOCALE_IINTLCURRDIGITS (intl monetary digits) 2 2 2 3 2 2 0
LOCALE_ICURRENCY (positive currency mode) 0 0 0 0 3 2 0
LOCALE_INEGCURR (negative currency mode) 0 0 0 1 8 3 1
LOCALE_SDATE (date separator) / / / / / / /
LOCALE_STIME (time separator) :: : :: : : : :
LOCALE_SSHORTDATE (short date format string) M/d/yyyy MM/dd/yyyy M/d/yyyy d/MM/yy dd/MM/yyyy dd/MM/yyyy yyyy/MM/dd
LOCALE_SLONGDATE (long date format string) dddd, MMMM d, yyyy dddd, dd MMMM yyyy dddd, MMMM d, yyyy dddd, d ‘ōf’ MMMM ‘ōf’ yyyy dd MMMM, yyyy dd MMMM, yyyy yyyy’年’M’月’d’日’
LOCALE_STIMEFORMAT (time format string) h::mm::ss tt HH:mm:ss h::mm::ss tt H:mm:ss hh:mm:ss tt hh:mm:ss tt H:mm:ss
LOCALE_IDATE (short date format ordering) 0 0 0 1 1 1 2
LOCALE_ILDATE (long date format ordering) 0 1 0 1 1 1 2
LOCALE_ITIME (time format specifier) 0 1 0 1 0 0 1
LOCALE_ITIMEMARKPOSN (time marker position) 0 0 0 0 0 0 0
LOCALE_ICENTURY (century format specifier (short date)) 1 1 1 0 1 1 1
LOCALE_ITLZERO (leading zeros in time field) 0 1 0 0 1 1 0
LOCALE_IDAYLZERO (leading zeros in day field (short date)) 0 1 0 0 1 1 1
LOCALE_IMONLZERO (leading zeros in month field (short date)) 0 1 0 1 1 1 1
LOCALE_S1159 (AM designator) ᴀᴍ AM ᴀᴍ АΜ ص ص 午前
LOCALE_S2359 (PM designator) ᴘᴍ PM ᴘᴍ P̰̃M] م م 午後
LOCALE_ICALENDARTYPE (type of calendar specifier) 1 1 1 1 23 23 1
LOCALE_IOPTIONALCALENDAR (additional calendar types specifier) 2 0 2 2 6 6 3
LOCALE_IFIRSTDAYOFWEEK (first day of week specifier) 6 6 6 0 6 5 6
LOCALE_SSORTLOCALE (Name of locale to use for sorting/collation/casing behavior.) en-US en-US qps-ploc ar-SA qps-plocm qps-ploca

Locale Info Spelunking

And just blindly spelunking through this raw data dump, i notice one thing that looks interesting:

Which is 1 for every locale that is Gregorian based, but is other values for the calendars based on other starting points:

Locale ICalendarType Example
Thai (Thauland) 7 "15/10/2564"
Dari (Afghanistan) 22 "1400/7/23"
Pashto (Afghanistan) 22 "1400/7/23"
Persian (Iran) 22 "23/07/1400"
Arabic (Saudi Arabia) 23 "09/03/43"

So it seems that ICalendarType is something that indicates the base of the calendar:

ICalendarType Calendar Example
1 Gregorian 15/10/2021, 2021-10-15
7 Thai Buddhist 15/10/2564
22 Pashto, Persian 1400/7/23, 23/7/1400
23 Arabic 09/03/43

But the question is: what do i do with it?

I guess that the Thai Buddhist calendar has the same months and days as Gregorian, just that it’s zero-year is different:

  • Gergorian: 15/10/2021
  • Thia Buddhist: 15/10/2564

I guess if i subtract 543 from the Buddhist year i get the Gregorian year?

But things go out the window with the Pashto and Persian calendars:

  • Gregorian: 10/15/2021
  • Thai Buddhist: 15/10/2564
  • Pashto: 23/7/1400
  • Arabic: 9/3/43

Calendar Types in WinNls.h

After more hunting, i think that the LOCALE_ICalendarType represents a Calendar ID Value. From WinNls.h:

//
//  Calendar ID Values.
//
#define CAL_GREGORIAN                  1      // Gregorian (localized) calendar
#define CAL_GREGORIAN_US               2      // Gregorian (U.S.) calendar
#define CAL_JAPAN                      3      // Japanese Emperor Era calendar
#define CAL_TAIWAN                     4      // Taiwan calendar
#define CAL_KOREA                      5      // Korean Tangun Era calendar
#define CAL_HIJRI                      6      // Hijri (Arabic Lunar) calendar
#define CAL_THAI                       7      // Thai calendar
#define CAL_HEBREW                     8      // Hebrew (Lunar) calendar
#define CAL_GREGORIAN_ME_FRENCH        9      // Gregorian Middle East French calendar
#define CAL_GREGORIAN_ARABIC           10     // Gregorian Arabic calendar
#define CAL_GREGORIAN_XLIT_ENGLISH     11     // Gregorian Transliterated English calendar
#define CAL_GREGORIAN_XLIT_FRENCH      12     // Gregorian Transliterated French calendar
#define CAL_PERSIAN                    22     // Persian (Solar Hijri) calendar     
#define CAL_UMALQURA                   23     // UmAlQura Hijri (Arabic Lunar) calendar

That seems to fit most of the calendar values i found in the wild:

ICalendarType CALID Calendar Example
1 CAL_GREGORIAN Gregorian 15/10/2021, 2021-10-15
7 CAL_THAI Thai Buddhist 15/10/2564
22 CAL_PERSIAN Pashto, Persian 1400/7/23, 23/7/1400
23 CAL_UMALQURA UmAlQura Hijri (Arabic Lunar) 09/03/43

So this is correct, these other locales use other calendars.

But how do i translate dates from these other calendars into Gregorian?

Source: Windows Questions

LEAVE A COMMENT