File: src\libraries\System.Private.CoreLib\src\System\Globalization\CalendarData.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace System.Globalization
{
    // List of calendar data
    // Note the we cache overrides.
    // Note that localized names (resource names) aren't available from here.
    //
    //  NOTE: Calendars depend on the locale name that creates it.  Only a few
    //        properties are available without locales using CalendarData.GetCalendar(CalendarData)
    //
    internal sealed partial class CalendarData
    {
        // Max calendars
        internal const int MAX_CALENDARS = 23;
 
        // Identity
        internal string sNativeName = null!; // Calendar Name for the locale
 
        // Formats
        internal string[] saShortDates = null!; // Short Data format, default first
        internal string[] saYearMonths = null!; // Year/Month Data format, default first
        internal string[] saLongDates = null!; // Long Data format, default first
        internal string sMonthDay = null!; // Month/Day format
 
        // Calendar Parts Names
        internal string[] saEraNames = null!; // Names of Eras
        internal string[] saAbbrevEraNames = null!; // Abbreviated Era Names
        internal string[] saAbbrevEnglishEraNames = null!; // Abbreviated Era Names in English
        internal string[] saDayNames = null!; // Day Names, null to use locale data, starts on Sunday
        internal string[] saAbbrevDayNames = null!; // Abbrev Day Names, null to use locale data, starts on Sunday
        internal string[] saSuperShortDayNames = null!; // Super short Day of week names
        internal string[] saMonthNames = null!; // Month Names (13)
        internal string[] saAbbrevMonthNames = null!; // Abbrev Month Names (13)
        internal string[] saMonthGenitiveNames = null!; // Genitive Month Names (13)
        internal string[] saAbbrevMonthGenitiveNames = null!; // Genitive Abbrev Month Names (13)
        internal string[] saLeapYearMonthNames = null!; // Multiple strings for the month names in a leap year.
 
        internal int iTwoDigitYearMax = 2049; // Max 2 digit year
        private int iCurrentEra;  // current era # (usually 1)
 
        // Use overrides?
        internal bool bUseUserOverrides; // True if we want user overrides.
 
        // Static invariant for the invariant locale
        internal static readonly CalendarData Invariant = CreateInvariant();
 
        // Private constructor
        private CalendarData()
        {
        }
 
        // Invariant factory
        private static CalendarData CreateInvariant()
        {
            // Set our default/gregorian US calendar data
            // Calendar IDs are 1-based, arrays are 0 based.
            CalendarData invariant = new CalendarData();
 
            // Set default data for calendar
            // Note that we don't load resources since this IS NOT supposed to change (by definition)
            invariant.sNativeName = "Gregorian Calendar";  // Calendar Name
 
            // Year
            invariant.iTwoDigitYearMax = 2049; // Max 2 digit year
            invariant.iCurrentEra = 1; // Current era #
 
            // Formats
            invariant.saShortDates = ["MM/dd/yyyy", "yyyy-MM-dd"];          // short date format
            invariant.saLongDates = ["dddd, dd MMMM yyyy"];                 // long date format
            invariant.saYearMonths = ["yyyy MMMM"];                         // year month format
            invariant.sMonthDay = "MMMM dd";                                            // Month day pattern
 
            // Calendar Parts Names
            invariant.saEraNames = ["A.D."];     // Era names
            invariant.saAbbrevEraNames = ["AD"];      // Abbreviated Era names
            invariant.saAbbrevEnglishEraNames = ["AD"];     // Abbreviated era names in English
            invariant.saDayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // day names
            invariant.saAbbrevDayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];     // abbreviated day names
            invariant.saSuperShortDayNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];      // The super short day names
            invariant.saMonthNames = [ "January", "February", "March", "April", "May", "June",
                                       "July", "August", "September", "October", "November", "December", string.Empty ]; // month names
            invariant.saAbbrevMonthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", string.Empty ]; // abbreviated month names
            invariant.saMonthGenitiveNames = invariant.saMonthNames;              // Genitive month names (same as month names for invariant)
            invariant.saAbbrevMonthGenitiveNames = invariant.saAbbrevMonthNames;    // Abbreviated genitive month names (same as abbrev month names for invariant)
            invariant.saLeapYearMonthNames = invariant.saMonthNames;              // leap year month names are unused in Gregorian English (invariant)
 
            invariant.bUseUserOverrides = false;
 
            return invariant;
        }
 
        //
        // Get a bunch of data for a calendar
        //
        internal CalendarData(string localeName, CalendarId calendarId, bool bUseUserOverrides)
        {
            this.bUseUserOverrides = bUseUserOverrides;
 
            Debug.Assert(!GlobalizationMode.Invariant);
 
            bool loadedCalendarData = LoadCalendarDataFromSystemCore(localeName, calendarId);
 
            if (!loadedCalendarData)
            {
                // LoadCalendarDataFromSystem sometimes can fail on Linux if the installed ICU package is missing some resources.
                // The ICU package can miss some resources in some cases like if someone compile and build the ICU package manually or ICU has a regression.
 
                // Something failed, try invariant for missing parts
                // This is really not good, but we don't want the callers to crash.
                this.sNativeName ??= string.Empty;           // Calendar Name for the locale.
 
                // Formats
                this.saShortDates ??= Invariant.saShortDates; // Short Data format, default first
                this.saYearMonths ??= Invariant.saYearMonths; // Year/Month Data format, default first
                this.saLongDates ??= Invariant.saLongDates;  // Long Data format, default first
                this.sMonthDay ??= Invariant.sMonthDay;    // Month/Day format
 
                // Calendar Parts Names
                this.saEraNames ??= Invariant.saEraNames;              // Names of Eras
                this.saAbbrevEraNames ??= Invariant.saAbbrevEraNames;        // Abbreviated Era Names
                this.saAbbrevEnglishEraNames ??= Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
                this.saDayNames ??= Invariant.saDayNames;              // Day Names, null to use locale data, starts on Sunday
                this.saAbbrevDayNames ??= Invariant.saAbbrevDayNames;        // Abbrev Day Names, null to use locale data, starts on Sunday
                this.saSuperShortDayNames ??= Invariant.saSuperShortDayNames;    // Super short Day of week names
                this.saMonthNames ??= Invariant.saMonthNames;            // Month Names (13)
                this.saAbbrevMonthNames ??= Invariant.saAbbrevMonthNames;      // Abbrev Month Names (13)
                // Genitive and Leap names can follow the fallback below
            }
 
            if (calendarId == CalendarId.TAIWAN)
            {
                if (SystemSupportsTaiwaneseCalendar())
                {
                    // We got the month/day names from the OS (same as gregorian), but the native name is wrong
                    this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
                }
                else
                {
                    this.sNativeName = string.Empty;
                }
            }
 
            // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
            if (this.saMonthGenitiveNames == null || this.saMonthGenitiveNames.Length == 0 || string.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
                this.saMonthGenitiveNames = this.saMonthNames;              // Genitive month names (same as month names for invariant)
            if (this.saAbbrevMonthGenitiveNames == null || this.saAbbrevMonthGenitiveNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
                this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames;    // Abbreviated genitive month names (same as abbrev month names for invariant)
            if (this.saLeapYearMonthNames == null || this.saLeapYearMonthNames.Length == 0 || string.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
                this.saLeapYearMonthNames = this.saMonthNames;
 
            InitializeEraNames(localeName, calendarId);
 
            InitializeAbbreviatedEraNames(localeName, calendarId);
 
            // Abbreviated English Era Names are only used for the Japanese calendar.
            if (calendarId == CalendarId.JAPAN)
            {
                this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
            }
            else
            {
                // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
                this.saAbbrevEnglishEraNames = [""];
            }
 
            // Japanese is the only thing with > 1 era.  Its current era # is how many ever
            // eras are in the array.  (And the others all have 1 string in the array)
            this.iCurrentEra = this.saEraNames.Length;
        }
 
        private void InitializeEraNames(string localeName, CalendarId calendarId)
        {
            // Note that the saEraNames only include "A.D."  We don't have localized names for other calendars available from windows
            switch (calendarId)
            {
                // For Localized Gregorian we really expect the data from the OS.
                case CalendarId.GREGORIAN:
                    // Fallback for CoreCLR < Win7 or culture.dll missing
                    if (AreEraNamesEmpty())
                    {
                        this.saEraNames = ["A.D."];
                    }
                    break;
 
                // The rest of the calendars have constant data, so we'll just use that
                case CalendarId.GREGORIAN_US:
                case CalendarId.JULIAN:
                    this.saEraNames = ["A.D."];
                    break;
                case CalendarId.HEBREW:
                    this.saEraNames = ["C.E."];
                    break;
                case CalendarId.HIJRI:
                case CalendarId.UMALQURA:
                    if (localeName == "dv-MV")
                    {
                        // Special case for Divehi
                        this.saEraNames = ["\x0780\x07a8\x0796\x07b0\x0783\x07a9"];
                    }
                    else
                    {
                        this.saEraNames = ["\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629"];
                    }
                    break;
                case CalendarId.GREGORIAN_ARABIC:
                case CalendarId.GREGORIAN_XLIT_ENGLISH:
                case CalendarId.GREGORIAN_XLIT_FRENCH:
                    // These are all the same:
                    this.saEraNames = ["\x0645"];
                    break;
 
                case CalendarId.GREGORIAN_ME_FRENCH:
                    this.saEraNames = ["ap. J.-C."];
                    break;
 
                case CalendarId.TAIWAN:
                    if (SystemSupportsTaiwaneseCalendar())
                    {
                        this.saEraNames = ["\x4e2d\x83ef\x6c11\x570b"];
                    }
                    else
                    {
                        this.saEraNames = [string.Empty];
                    }
                    break;
 
                case CalendarId.KOREA:
                    this.saEraNames = ["\xb2e8\xae30"];
                    break;
 
                case CalendarId.THAI:
                    this.saEraNames = ["\x0e1e\x002e\x0e28\x002e"];
                    break;
 
                case CalendarId.JAPAN:
                case CalendarId.JAPANESELUNISOLAR:
                    this.saEraNames = JapaneseCalendar.EraNames();
                    break;
 
                case CalendarId.PERSIAN:
                    if (AreEraNamesEmpty())
                    {
                        this.saEraNames = ["\x0647\x002e\x0634"];
                    }
                    break;
 
                default:
                    // Most calendars are just "A.D."
                    this.saEraNames = Invariant.saEraNames;
                    break;
 
                    bool AreEraNamesEmpty() =>
                        this.saEraNames == null || this.saEraNames.Length == 0 || string.IsNullOrEmpty(this.saEraNames[0]);
            }
        }
 
        private void InitializeAbbreviatedEraNames(string localeName, CalendarId calendarId)
        {
            // Note that the saAbbrevEraNames only include "AD"  We don't have localized names for other calendars available from windows
            switch (calendarId)
            {
                // For Localized Gregorian we really expect the data from the OS.
                case CalendarId.GREGORIAN:
                    // Fallback for CoreCLR < Win7 or culture.dll missing
                    if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevEraNames[0]))
                    {
                        this.saAbbrevEraNames = ["AD"];
                    }
                    break;
 
                // The rest of the calendars have constant data, so we'll just use that
                case CalendarId.GREGORIAN_US:
                case CalendarId.JULIAN:
                    this.saAbbrevEraNames = ["AD"];
                    break;
                case CalendarId.JAPAN:
                case CalendarId.JAPANESELUNISOLAR:
                    this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
                    break;
                case CalendarId.HIJRI:
                case CalendarId.UMALQURA:
                    if (localeName == "dv-MV")
                    {
                        // Special case for Divehi
                        this.saAbbrevEraNames = ["\x0780\x002e"];
                    }
                    else
                    {
                        this.saAbbrevEraNames = ["\x0647\x0640"];
                    }
                    break;
                case CalendarId.TAIWAN:
                    // Get era name and abbreviate it
                    this.saAbbrevEraNames = new string[1];
                    if (this.saEraNames[0].Length == 4)
                    {
                        this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2, 2);
                    }
                    else
                    {
                        this.saAbbrevEraNames[0] = this.saEraNames[0];
                    }
                    break;
 
                case CalendarId.PERSIAN:
                    if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevEraNames[0]))
                    {
                        this.saAbbrevEraNames = this.saEraNames;
                    }
                    break;
 
                default:
                    // Most calendars just use the full name
                    this.saAbbrevEraNames = this.saEraNames;
                    break;
            }
        }
 
        internal static int GetCalendarCurrentEra(Calendar calendar)
        {
            if (GlobalizationMode.Invariant)
            {
                return Invariant.iCurrentEra;
            }
 
            //
            // Get a calendar.
            // Unfortunately we depend on the locale in the OS, so we need a locale
            // no matter what.  So just get the appropriate calendar from the
            // appropriate locale here
            //
 
            // Get a culture name
            // TODO: Note that this doesn't handle the new calendars (lunisolar, etc)
            CalendarId calendarId = calendar.BaseCalendarID;
            string culture = CalendarIdToCultureName(calendarId);
 
            // Return our calendar
            return CultureInfo.GetCultureInfo(culture)._cultureData.GetCalendar(calendarId).iCurrentEra;
        }
 
        private static string CalendarIdToCultureName(CalendarId calendarId)
        {
            switch (calendarId)
            {
                case CalendarId.GREGORIAN_US:
                    return "fa-IR";             // "fa-IR" Iran
 
                case CalendarId.JAPAN:
                    return "ja-JP";             // "ja-JP" Japan
 
                case CalendarId.TAIWAN:
                    return "zh-TW";             // zh-TW Taiwan
 
                case CalendarId.KOREA:
                    return "ko-KR";             // "ko-KR" Korea
 
                case CalendarId.HIJRI:
                case CalendarId.GREGORIAN_ARABIC:
                case CalendarId.UMALQURA:
                    return "ar-SA";             // "ar-SA" Saudi Arabia
 
                case CalendarId.THAI:
                    return "th-TH";             // "th-TH" Thailand
 
                case CalendarId.HEBREW:
                    return "he-IL";             // "he-IL" Israel
 
                case CalendarId.GREGORIAN_ME_FRENCH:
                    return "ar-DZ";             // "ar-DZ" Algeria
 
                case CalendarId.GREGORIAN_XLIT_ENGLISH:
                case CalendarId.GREGORIAN_XLIT_FRENCH:
                    return "ar-IQ";             // "ar-IQ"; Iraq
 
                default:
                    // Default to gregorian en-US
                    break;
            }
 
            return "en-US";
        }
 
        private static bool SystemSupportsTaiwaneseCalendar() => GlobalizationMode.UseNls ?
                                                            NlsSystemSupportsTaiwaneseCalendar() :
                                                            IcuSystemSupportsTaiwaneseCalendar();
    }
}