< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 477
Coverable lines: 477
Total lines: 1425
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 354
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .ctor()100%110%
File 1: .cctor()100%110%
File 1: CreateInvariant()100%110%
File 1: .ctor(...)0%52520%
File 1: InitializeEraNames(...)0%32320%
File 1: AreEraNamesEmpty()0%440%
File 1: InitializeAbbreviatedEraNames(...)0%33330%
File 1: GetCalendarCurrentEra(...)0%220%
File 1: CalendarIdToCultureName(...)0%23230%
File 1: SystemSupportsTaiwaneseCalendar()0%220%
File 2: IcuLoadCalendarDataFromSystem(...)0%660%
File 2: IcuGetCalendars(...)0%440%
File 2: IcuSystemSupportsTaiwaneseCalendar()100%110%
File 2: GetCalendarInfo(...)100%110%
File 2: EnumDatePatterns(...)0%660%
File 2: FixDefaultShortDatePattern(...)0%30300%
File 2: NormalizeDatePattern(...)0%36360%
File 2: NormalizeDayOfWeek(...)0%220%
File 2: CountOccurrences(...)0%440%
File 2: EnumMonthNames(...)0%10100%
File 2: EnumEraNames(...)0%880%
File 2: EnumCalendarInfo(...)0%220%
File 2: EnumCalendarInfo(...)100%110%
File 2: EnumCalendarInfoCallback(...)0%660%
File 3: NlsSystemSupportsTaiwaneseCalendar()100%110%
File 3: CallGetCalendarInfoEx(...)100%110%
File 3: CallGetCalendarInfoEx(...)0%440%
File 3: EnumCalendarInfoCallback(...)0%220%
File 3: EnumCalendarsCallback(...)0%220%
File 4: LoadCalendarDataFromSystemCore(...)0%880%
File 4: InsertOrSwapOverride(...)0%660%
File 4: NlsLoadCalendarDataFromSystem(...)0%440%
File 4: NormalizeCalendarId(...)0%10100%
File 4: CheckSpecialCalendar(...)0%10100%
File 4: CallEnumCalendarInfo(...)0%14140%
File 4: GetCalendarDayInfo(...)0%440%
File 4: GetCalendarMonthInfo(...)0%440%
File 4: GetCalendarsCore(...)0%14140%
File 4: NlsGetCalendars(...)0%660%
File 4: GetTwoDigitYearMax(...)0%440%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5
 6namespace System.Globalization
 7{
 8    // List of calendar data
 9    // Note the we cache overrides.
 10    // Note that localized names (resource names) aren't available from here.
 11    //
 12    //  NOTE: Calendars depend on the locale name that creates it.  Only a few
 13    //        properties are available without locales using CalendarData.GetCalendar(CalendarData)
 14    //
 15    internal sealed partial class CalendarData
 16    {
 17        // Max calendars
 18        internal const int MAX_CALENDARS = 23;
 19
 20        // Identity
 21        internal string sNativeName = null!; // Calendar Name for the locale
 22
 23        // Formats
 24        internal string[] saShortDates = null!; // Short Data format, default first
 25        internal string[] saYearMonths = null!; // Year/Month Data format, default first
 26        internal string[] saLongDates = null!; // Long Data format, default first
 27        internal string sMonthDay = null!; // Month/Day format
 28
 29        // Calendar Parts Names
 30        internal string[] saEraNames = null!; // Names of Eras
 31        internal string[] saAbbrevEraNames = null!; // Abbreviated Era Names
 32        internal string[] saAbbrevEnglishEraNames = null!; // Abbreviated Era Names in English
 33        internal string[] saDayNames = null!; // Day Names, null to use locale data, starts on Sunday
 34        internal string[] saAbbrevDayNames = null!; // Abbrev Day Names, null to use locale data, starts on Sunday
 35        internal string[] saSuperShortDayNames = null!; // Super short Day of week names
 36        internal string[] saMonthNames = null!; // Month Names (13)
 37        internal string[] saAbbrevMonthNames = null!; // Abbrev Month Names (13)
 38        internal string[] saMonthGenitiveNames = null!; // Genitive Month Names (13)
 39        internal string[] saAbbrevMonthGenitiveNames = null!; // Genitive Abbrev Month Names (13)
 40        internal string[] saLeapYearMonthNames = null!; // Multiple strings for the month names in a leap year.
 41
 042        internal int iTwoDigitYearMax = 2049; // Max 2 digit year
 43        private int iCurrentEra;  // current era # (usually 1)
 44
 45        // Use overrides?
 46        internal bool bUseUserOverrides; // True if we want user overrides.
 47
 48        // Static invariant for the invariant locale
 049        internal static readonly CalendarData Invariant = CreateInvariant();
 50
 51        // Private constructor
 052        private CalendarData()
 53        {
 054        }
 55
 56        // Invariant factory
 57        private static CalendarData CreateInvariant()
 58        {
 59            // Set our default/gregorian US calendar data
 60            // Calendar IDs are 1-based, arrays are 0 based.
 061            CalendarData invariant = new CalendarData();
 62
 63            // Set default data for calendar
 64            // Note that we don't load resources since this IS NOT supposed to change (by definition)
 065            invariant.sNativeName = "Gregorian Calendar";  // Calendar Name
 66
 67            // Year
 068            invariant.iTwoDigitYearMax = 2049; // Max 2 digit year
 069            invariant.iCurrentEra = 1; // Current era #
 70
 71            // Formats
 072            invariant.saShortDates = ["MM/dd/yyyy", "yyyy-MM-dd"];          // short date format
 073            invariant.saLongDates = ["dddd, dd MMMM yyyy"];                 // long date format
 074            invariant.saYearMonths = ["yyyy MMMM"];                         // year month format
 075            invariant.sMonthDay = "MMMM dd";                                            // Month day pattern
 76
 77            // Calendar Parts Names
 078            invariant.saEraNames = ["A.D."];     // Era names
 079            invariant.saAbbrevEraNames = ["AD"];      // Abbreviated Era names
 080            invariant.saAbbrevEnglishEraNames = ["AD"];     // Abbreviated era names in English
 081            invariant.saDayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // da
 082            invariant.saAbbrevDayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];     // abbreviated day names
 083            invariant.saSuperShortDayNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];      // The super short day nam
 084            invariant.saMonthNames = [ "January", "February", "March", "April", "May", "June",
 085                                       "July", "August", "September", "October", "November", "December", string.Empty ];
 086            invariant.saAbbrevMonthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 087                                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", string.Empty ]; // abbreviated mo
 088            invariant.saMonthGenitiveNames = invariant.saMonthNames;              // Genitive month names (same as month
 089            invariant.saAbbrevMonthGenitiveNames = invariant.saAbbrevMonthNames;    // Abbreviated genitive month names 
 090            invariant.saLeapYearMonthNames = invariant.saMonthNames;              // leap year month names are unused in
 91
 092            invariant.bUseUserOverrides = false;
 93
 094            return invariant;
 95        }
 96
 97        //
 98        // Get a bunch of data for a calendar
 99        //
 0100        internal CalendarData(string localeName, CalendarId calendarId, bool bUseUserOverrides)
 101        {
 0102            this.bUseUserOverrides = bUseUserOverrides;
 103
 0104            Debug.Assert(!GlobalizationMode.Invariant);
 105
 0106            bool loadedCalendarData = LoadCalendarDataFromSystemCore(localeName, calendarId);
 107
 0108            if (!loadedCalendarData)
 109            {
 110                // LoadCalendarDataFromSystem sometimes can fail on Linux if the installed ICU package is missing some r
 111                // The ICU package can miss some resources in some cases like if someone compile and build the ICU packa
 112
 113                // Something failed, try invariant for missing parts
 114                // This is really not good, but we don't want the callers to crash.
 0115                this.sNativeName ??= string.Empty;           // Calendar Name for the locale.
 116
 117                // Formats
 0118                this.saShortDates ??= Invariant.saShortDates; // Short Data format, default first
 0119                this.saYearMonths ??= Invariant.saYearMonths; // Year/Month Data format, default first
 0120                this.saLongDates ??= Invariant.saLongDates;  // Long Data format, default first
 0121                this.sMonthDay ??= Invariant.sMonthDay;    // Month/Day format
 122
 123                // Calendar Parts Names
 0124                this.saEraNames ??= Invariant.saEraNames;              // Names of Eras
 0125                this.saAbbrevEraNames ??= Invariant.saAbbrevEraNames;        // Abbreviated Era Names
 0126                this.saAbbrevEnglishEraNames ??= Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
 0127                this.saDayNames ??= Invariant.saDayNames;              // Day Names, null to use locale data, starts on 
 0128                this.saAbbrevDayNames ??= Invariant.saAbbrevDayNames;        // Abbrev Day Names, null to use locale dat
 0129                this.saSuperShortDayNames ??= Invariant.saSuperShortDayNames;    // Super short Day of week names
 0130                this.saMonthNames ??= Invariant.saMonthNames;            // Month Names (13)
 0131                this.saAbbrevMonthNames ??= Invariant.saAbbrevMonthNames;      // Abbrev Month Names (13)
 132                // Genitive and Leap names can follow the fallback below
 133            }
 134
 0135            if (calendarId == CalendarId.TAIWAN)
 136            {
 0137                if (SystemSupportsTaiwaneseCalendar())
 138                {
 139                    // We got the month/day names from the OS (same as gregorian), but the native name is wrong
 0140                    this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
 141                }
 142                else
 143                {
 0144                    this.sNativeName = string.Empty;
 145                }
 146            }
 147
 148            // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
 0149            if (this.saMonthGenitiveNames == null || this.saMonthGenitiveNames.Length == 0 || string.IsNullOrEmpty(this.
 0150                this.saMonthGenitiveNames = this.saMonthNames;              // Genitive month names (same as month names
 0151            if (this.saAbbrevMonthGenitiveNames == null || this.saAbbrevMonthGenitiveNames.Length == 0 || string.IsNullO
 0152                this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames;    // Abbreviated genitive month names (same 
 0153            if (this.saLeapYearMonthNames == null || this.saLeapYearMonthNames.Length == 0 || string.IsNullOrEmpty(this.
 0154                this.saLeapYearMonthNames = this.saMonthNames;
 155
 0156            InitializeEraNames(localeName, calendarId);
 157
 0158            InitializeAbbreviatedEraNames(localeName, calendarId);
 159
 160            // Abbreviated English Era Names are only used for the Japanese calendar.
 0161            if (calendarId == CalendarId.JAPAN)
 162            {
 0163                this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
 164            }
 165            else
 166            {
 167                // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendar
 0168                this.saAbbrevEnglishEraNames = [""];
 169            }
 170
 171            // Japanese is the only thing with > 1 era.  Its current era # is how many ever
 172            // eras are in the array.  (And the others all have 1 string in the array)
 0173            this.iCurrentEra = this.saEraNames.Length;
 0174        }
 175
 176        private void InitializeEraNames(string localeName, CalendarId calendarId)
 177        {
 178            // Note that the saEraNames only include "A.D."  We don't have localized names for other calendars available
 179            switch (calendarId)
 180            {
 181                // For Localized Gregorian we really expect the data from the OS.
 182                case CalendarId.GREGORIAN:
 183                    // Fallback for CoreCLR < Win7 or culture.dll missing
 0184                    if (AreEraNamesEmpty())
 185                    {
 0186                        this.saEraNames = ["A.D."];
 187                    }
 0188                    break;
 189
 190                // The rest of the calendars have constant data, so we'll just use that
 191                case CalendarId.GREGORIAN_US:
 192                case CalendarId.JULIAN:
 0193                    this.saEraNames = ["A.D."];
 0194                    break;
 195                case CalendarId.HEBREW:
 0196                    this.saEraNames = ["C.E."];
 0197                    break;
 198                case CalendarId.HIJRI:
 199                case CalendarId.UMALQURA:
 0200                    if (localeName == "dv-MV")
 201                    {
 202                        // Special case for Divehi
 0203                        this.saEraNames = ["\x0780\x07a8\x0796\x07b0\x0783\x07a9"];
 204                    }
 205                    else
 206                    {
 0207                        this.saEraNames = ["\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629"];
 208                    }
 0209                    break;
 210                case CalendarId.GREGORIAN_ARABIC:
 211                case CalendarId.GREGORIAN_XLIT_ENGLISH:
 212                case CalendarId.GREGORIAN_XLIT_FRENCH:
 213                    // These are all the same:
 0214                    this.saEraNames = ["\x0645"];
 0215                    break;
 216
 217                case CalendarId.GREGORIAN_ME_FRENCH:
 0218                    this.saEraNames = ["ap. J.-C."];
 0219                    break;
 220
 221                case CalendarId.TAIWAN:
 0222                    if (SystemSupportsTaiwaneseCalendar())
 223                    {
 0224                        this.saEraNames = ["\x4e2d\x83ef\x6c11\x570b"];
 225                    }
 226                    else
 227                    {
 0228                        this.saEraNames = [string.Empty];
 229                    }
 0230                    break;
 231
 232                case CalendarId.KOREA:
 0233                    this.saEraNames = ["\xb2e8\xae30"];
 0234                    break;
 235
 236                case CalendarId.THAI:
 0237                    this.saEraNames = ["\x0e1e\x002e\x0e28\x002e"];
 0238                    break;
 239
 240                case CalendarId.JAPAN:
 241                case CalendarId.JAPANESELUNISOLAR:
 0242                    this.saEraNames = JapaneseCalendar.EraNames();
 0243                    break;
 244
 245                case CalendarId.PERSIAN:
 0246                    if (AreEraNamesEmpty())
 247                    {
 0248                        this.saEraNames = ["\x0647\x002e\x0634"];
 249                    }
 0250                    break;
 251
 252                default:
 253                    // Most calendars are just "A.D."
 0254                    this.saEraNames = Invariant.saEraNames;
 255                    break;
 256
 257                    bool AreEraNamesEmpty() =>
 0258                        this.saEraNames == null || this.saEraNames.Length == 0 || string.IsNullOrEmpty(this.saEraNames[0
 259            }
 0260        }
 261
 262        private void InitializeAbbreviatedEraNames(string localeName, CalendarId calendarId)
 263        {
 264            // Note that the saAbbrevEraNames only include "AD"  We don't have localized names for other calendars avail
 265            switch (calendarId)
 266            {
 267                // For Localized Gregorian we really expect the data from the OS.
 268                case CalendarId.GREGORIAN:
 269                    // Fallback for CoreCLR < Win7 or culture.dll missing
 0270                    if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.
 271                    {
 0272                        this.saAbbrevEraNames = ["AD"];
 273                    }
 0274                    break;
 275
 276                // The rest of the calendars have constant data, so we'll just use that
 277                case CalendarId.GREGORIAN_US:
 278                case CalendarId.JULIAN:
 0279                    this.saAbbrevEraNames = ["AD"];
 0280                    break;
 281                case CalendarId.JAPAN:
 282                case CalendarId.JAPANESELUNISOLAR:
 0283                    this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
 0284                    break;
 285                case CalendarId.HIJRI:
 286                case CalendarId.UMALQURA:
 0287                    if (localeName == "dv-MV")
 288                    {
 289                        // Special case for Divehi
 0290                        this.saAbbrevEraNames = ["\x0780\x002e"];
 291                    }
 292                    else
 293                    {
 0294                        this.saAbbrevEraNames = ["\x0647\x0640"];
 295                    }
 0296                    break;
 297                case CalendarId.TAIWAN:
 298                    // Get era name and abbreviate it
 0299                    this.saAbbrevEraNames = new string[1];
 0300                    if (this.saEraNames[0].Length == 4)
 301                    {
 0302                        this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2, 2);
 303                    }
 304                    else
 305                    {
 0306                        this.saAbbrevEraNames[0] = this.saEraNames[0];
 307                    }
 0308                    break;
 309
 310                case CalendarId.PERSIAN:
 0311                    if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.
 312                    {
 0313                        this.saAbbrevEraNames = this.saEraNames;
 314                    }
 0315                    break;
 316
 317                default:
 318                    // Most calendars just use the full name
 0319                    this.saAbbrevEraNames = this.saEraNames;
 320                    break;
 321            }
 0322        }
 323
 324        internal static int GetCalendarCurrentEra(Calendar calendar)
 325        {
 0326            if (GlobalizationMode.Invariant)
 327            {
 0328                return Invariant.iCurrentEra;
 329            }
 330
 331            //
 332            // Get a calendar.
 333            // Unfortunately we depend on the locale in the OS, so we need a locale
 334            // no matter what.  So just get the appropriate calendar from the
 335            // appropriate locale here
 336            //
 337
 338            // Get a culture name
 339            // TODO: Note that this doesn't handle the new calendars (lunisolar, etc)
 0340            CalendarId calendarId = calendar.BaseCalendarID;
 0341            string culture = CalendarIdToCultureName(calendarId);
 342
 343            // Return our calendar
 0344            return CultureInfo.GetCultureInfo(culture)._cultureData.GetCalendar(calendarId).iCurrentEra;
 345        }
 346
 347        private static string CalendarIdToCultureName(CalendarId calendarId)
 348        {
 349            switch (calendarId)
 350            {
 351                case CalendarId.GREGORIAN_US:
 0352                    return "fa-IR";             // "fa-IR" Iran
 353
 354                case CalendarId.JAPAN:
 0355                    return "ja-JP";             // "ja-JP" Japan
 356
 357                case CalendarId.TAIWAN:
 0358                    return "zh-TW";             // zh-TW Taiwan
 359
 360                case CalendarId.KOREA:
 0361                    return "ko-KR";             // "ko-KR" Korea
 362
 363                case CalendarId.HIJRI:
 364                case CalendarId.GREGORIAN_ARABIC:
 365                case CalendarId.UMALQURA:
 0366                    return "ar-SA";             // "ar-SA" Saudi Arabia
 367
 368                case CalendarId.THAI:
 0369                    return "th-TH";             // "th-TH" Thailand
 370
 371                case CalendarId.HEBREW:
 0372                    return "he-IL";             // "he-IL" Israel
 373
 374                case CalendarId.GREGORIAN_ME_FRENCH:
 0375                    return "ar-DZ";             // "ar-DZ" Algeria
 376
 377                case CalendarId.GREGORIAN_XLIT_ENGLISH:
 378                case CalendarId.GREGORIAN_XLIT_FRENCH:
 0379                    return "ar-IQ";             // "ar-IQ"; Iraq
 380
 381                default:
 382                    // Default to gregorian en-US
 383                    break;
 384            }
 385
 0386            return "en-US";
 387        }
 388
 0389        private static bool SystemSupportsTaiwaneseCalendar() => GlobalizationMode.UseNls ?
 0390                                                            NlsSystemSupportsTaiwaneseCalendar() :
 0391                                                            IcuSystemSupportsTaiwaneseCalendar();
 392    }
 393}
 394

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9using System.Text;
 10
 11namespace System.Globalization
 12{
 13    // needs to be kept in sync with CalendarDataType in System.Globalization.Native
 14    internal enum CalendarDataType
 15    {
 16        Uninitialized = 0,
 17        NativeName = 1,
 18        MonthDay = 2,
 19        ShortDates = 3,
 20        LongDates = 4,
 21        YearMonths = 5,
 22        DayNames = 6,
 23        AbbrevDayNames = 7,
 24        MonthNames = 8,
 25        AbbrevMonthNames = 9,
 26        SuperShortDayNames = 10,
 27        MonthGenitiveNames = 11,
 28        AbbrevMonthGenitiveNames = 12,
 29        EraNames = 13,
 30        AbbrevEraNames = 14,
 31    }
 32
 33    internal sealed partial class CalendarData
 34    {
 35        private bool IcuLoadCalendarDataFromSystem(string localeName, CalendarId calendarId)
 36        {
 37            // ToDo: think if not to convert this function with multiple calls to JS into one call with multiple data re
 038            Debug.Assert(!GlobalizationMode.UseNls);
 39
 040            bool result = true;
 41
 42            // these can return null but are later replaced with String.Empty or other non-nullable value
 043            result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.NativeName, out this.sNativeName!);
 044            result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.MonthDay, out this.sMonthDay!);
 45
 046            if (this.sMonthDay != null)
 47            {
 048                this.sMonthDay = NormalizeDatePattern(this.sMonthDay);
 49            }
 50
 051            result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.ShortDates, out this.saShortDates!);
 052            result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.LongDates, out this.saLongDates!);
 053            result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.YearMonths, out this.saYearMonths!);
 054            result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.DayNames, out this.saDayNames!);
 055            result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.AbbrevDayNames, out this.saAbbrevDayName
 056            result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.SuperShortDayNames, out this.saSuperShor
 57
 058            string? leapHebrewMonthName = null;
 059            result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthNames, out this.saMonthNames!, ref le
 060            if (leapHebrewMonthName != null)
 61            {
 062                Debug.Assert(this.saMonthNames != null);
 63
 64                // In Hebrew calendar, get the leap month name Adar II and override the non-leap month 7
 065                Debug.Assert(calendarId == CalendarId.HEBREW && saMonthNames.Length == 13);
 066                saLeapYearMonthNames = (string[])saMonthNames.Clone();
 067                saLeapYearMonthNames[6] = leapHebrewMonthName;
 68
 69                // The returned data from ICU has 6th month name as 'Adar I' and 7th month name as 'Adar'
 70                // We need to adjust that in the list used with non-leap year to have 6th month as 'Adar' and 7th month 
 71                // note that when formatting non-leap year dates, 7th month shouldn't get used at all.
 072                saMonthNames[5] = saMonthNames[6];
 073                saMonthNames[6] = leapHebrewMonthName;
 74
 75            }
 076            result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthNames, out this.saAbbrevMonthNa
 077            result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthGenitiveNames, out this.saMonthGeniti
 078            result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthGenitiveNames, out this.saAbbre
 79
 080            result &= EnumEraNames(localeName, calendarId, CalendarDataType.EraNames, out this.saEraNames!);
 081            result &= EnumEraNames(localeName, calendarId, CalendarDataType.AbbrevEraNames, out this.saAbbrevEraNames!);
 82
 083            return result;
 84        }
 85
 86        // Call native side to figure out which calendars are allowed
 87        internal static int IcuGetCalendars(string localeName, CalendarId[] calendars)
 88        {
 089            Debug.Assert(!GlobalizationMode.Invariant);
 090            Debug.Assert(!GlobalizationMode.UseNls);
 91
 92            // NOTE: there are no 'user overrides' on Linux
 93            int count;
 94#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 95            if (GlobalizationMode.Hybrid)
 96            {
 97                count = Interop.Globalization.GetCalendarsNative(localeName, calendars, calendars.Length);
 98            }
 99            else
 100#endif
 101            {
 0102                count = Interop.Globalization.GetCalendars(localeName, calendars, calendars.Length);
 103            }
 104
 105            // ensure there is at least 1 calendar returned
 0106            if (count == 0 && calendars.Length > 0)
 107            {
 0108                calendars[0] = CalendarId.GREGORIAN;
 0109                count = 1;
 110            }
 111
 0112            return count;
 113        }
 114
 115        private static bool IcuSystemSupportsTaiwaneseCalendar()
 116        {
 0117            Debug.Assert(!GlobalizationMode.UseNls);
 0118            return true;
 119        }
 120
 121        // PAL Layer ends here
 122
 123        private static unsafe bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, 
 124        {
 0125            Debug.Assert(!GlobalizationMode.Invariant);
 126
 0127            return Interop.CallStringMethod(
 0128                static (buffer, locale, id, type) =>
 0129                {
 0130                    fixed (char* bufferPtr = buffer)
 0131                    {
 0132                        return Interop.Globalization.GetCalendarInfo(locale, id, type, bufferPtr, buffer.Length);
 0133                    }
 0134                },
 0135                localeName,
 0136                calendarId,
 0137                dataType,
 0138                out calendarString);
 139        }
 140
 141        private static unsafe bool EnumDatePatterns(string localeName, CalendarId calendarId, CalendarDataType dataType,
 142        {
 0143            datePatterns = null;
 144
 0145            IcuEnumCalendarsData callbackContext = default;
 0146            callbackContext.Results = new List<string>();
 0147            callbackContext.DisallowDuplicates = true;
 0148            bool result = EnumCalendarInfo(localeName, calendarId, dataType, &callbackContext);
 0149            if (result)
 150            {
 0151                List<string> datePatternsList = callbackContext.Results;
 152
 0153                for (int i = 0; i < datePatternsList.Count; i++)
 154                {
 0155                    datePatternsList[i] = NormalizeDatePattern(datePatternsList[i]);
 156                }
 157
 0158                if (dataType == CalendarDataType.ShortDates)
 0159                    FixDefaultShortDatePattern(datePatternsList);
 160
 0161                datePatterns = datePatternsList.ToArray();
 162            }
 163
 0164            return result;
 165        }
 166
 167        // FixDefaultShortDatePattern will convert the default short date pattern from using 'yy' to using 'yyyy'
 168        // And will ensure the original pattern still exist in the list.
 169        // doing that will have the short date pattern format the year as 4-digit number and not just 2-digit number.
 170        // Example: June 5, 2018 will be formatted to something like 6/5/2018 instead of 6/5/18 fro en-US culture.
 171        private static unsafe void FixDefaultShortDatePattern(List<string> shortDatePatterns)
 172        {
 0173            if (shortDatePatterns.Count == 0)
 0174                return;
 175
 0176            string s = shortDatePatterns[0];
 177
 178            // We are not expecting any pattern have length more than 100.
 179            // We have to do this check to prevent stack overflow as we allocate the buffer on the stack.
 0180            if (s.Length > 100)
 0181                return;
 182
 0183            Span<char> modifiedPattern = stackalloc char[s.Length + 2];
 0184            int index = 0;
 185
 0186            while (index < s.Length)
 187            {
 0188                if (s[index] == '\'')
 189                {
 190                    do
 191                    {
 0192                        modifiedPattern[index] = s[index];
 0193                        index++;
 0194                    } while (index < s.Length && s[index] != '\'');
 195
 0196                    if (index >= s.Length)
 0197                        return;
 198                }
 0199                else if (s[index] == 'y')
 200                {
 0201                    modifiedPattern[index] = 'y';
 0202                    break;
 203                }
 204
 0205                modifiedPattern[index] = s[index];
 0206                index++;
 207            }
 208
 0209            if (index >= s.Length - 1 || s[index + 1] != 'y')
 210            {
 211                // not a 'yy' pattern
 0212                return;
 213            }
 214
 0215            if (index + 2 < s.Length && s[index + 2] == 'y')
 216            {
 217                // we have 'yyy' then nothing to do
 0218                return;
 219            }
 220
 221            // we are sure now we have 'yy' pattern
 222
 0223            Debug.Assert(index + 3 < modifiedPattern.Length);
 224
 0225            modifiedPattern[index + 1] = 'y'; // second y
 0226            modifiedPattern[index + 2] = 'y'; // third y
 0227            modifiedPattern[index + 3] = 'y'; // fourth y
 228
 0229            index += 2;
 230
 231            // Now, copy the rest of the pattern to the destination buffer
 0232            while (index < s.Length)
 233            {
 0234                modifiedPattern[index + 2] = s[index];
 0235                index++;
 236            }
 237
 0238            shortDatePatterns[0] = modifiedPattern.ToString();
 239
 0240            for (int i = 1; i < shortDatePatterns.Count; i++)
 241            {
 0242                if (shortDatePatterns[i] == shortDatePatterns[0])
 243                {
 244                    // Found match in the list to the new constructed pattern, then replace it with the original modifie
 0245                    shortDatePatterns[i] = s;
 0246                    return;
 247                }
 248            }
 249
 250            // if we come here means the newly constructed pattern not found on the list, then add the original pattern
 0251            shortDatePatterns.Add(s);
 0252        }
 253
 254        /// <summary>
 255        /// The ICU date format characters are not exactly the same as the .NET date format characters.
 256        /// NormalizeDatePattern will take in an ICU date pattern and return the equivalent .NET date pattern.
 257        /// </summary>
 258        /// <remarks>
 259        /// see Date Field Symbol Table in http://userguide.icu-project.org/formatparse/datetime
 260        /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx
 261        /// </remarks>
 262        private static unsafe string NormalizeDatePattern(string input)
 263        {
 0264            var destination = input.Length < 128 ?
 0265                new ValueStringBuilder(stackalloc char[128]) :
 0266                new ValueStringBuilder(input.Length);
 267
 0268            int index = 0;
 0269            while (index < input.Length)
 270            {
 0271                switch (input[index])
 272                {
 273                    case '\'':
 274                        // single quotes escape characters, like 'de' in es-SP
 275                        // so read verbatim until the next single quote
 0276                        destination.Append(input[index++]);
 0277                        while (index < input.Length)
 278                        {
 0279                            char current = input[index++];
 0280                            destination.Append(current);
 0281                            if (current == '\'')
 282                            {
 283                                break;
 284                            }
 285                        }
 0286                        break;
 287                    case 'E':
 288                    case 'e':
 289                    case 'c':
 290                        // 'E' in ICU is the day of the week, which maps to 3 or 4 'd's in .NET
 291                        // 'e' in ICU is the local day of the week, which has no representation in .NET, but
 292                        // maps closest to 3 or 4 'd's in .NET
 293                        // 'c' in ICU is the stand-alone day of the week, which has no representation in .NET, but
 294                        // maps closest to 3 or 4 'd's in .NET
 0295                        NormalizeDayOfWeek(input, ref destination, ref index);
 0296                        break;
 297                    case 'L':
 298                    case 'M':
 299                        // 'L' in ICU is the stand-alone name of the month,
 300                        // which maps closest to 'M' in .NET since it doesn't support stand-alone month names in pattern
 301                        // 'M' in both ICU and .NET is the month,
 302                        // but ICU supports 5 'M's, which is the super short month name
 0303                        int occurrences = CountOccurrences(input, input[index], ref index);
 0304                        if (occurrences > 4)
 305                        {
 306                            // 5 'L's or 'M's in ICU is the super short name, which maps closest to MMM in .NET
 0307                            occurrences = 3;
 308                        }
 0309                        destination.Append('M', occurrences);
 0310                        break;
 311                    case 'G':
 312                        // 'G' in ICU is the era, which maps to 'g' in .NET
 0313                        CountOccurrences(input, 'G', ref index);
 314
 315                        // it doesn't matter how many 'G's, since .NET only supports 'g' or 'gg', and they
 316                        // have the same meaning
 0317                        destination.Append('g');
 0318                        break;
 319                    case 'y':
 320                        // a single 'y' in ICU is the year with no padding or trimming.
 321                        // a single 'y' in .NET is the year with 1 or 2 digits
 322                        // so convert any single 'y' to 'yyyy'
 0323                        occurrences = CountOccurrences(input, 'y', ref index);
 0324                        if (occurrences == 1)
 325                        {
 0326                            occurrences = 4;
 327                        }
 0328                        destination.Append('y', occurrences);
 0329                        break;
 330                    default:
 331                        const string unsupportedDateFieldSymbols = "YuUrQqwWDFg";
 0332                        Debug.Assert(!unsupportedDateFieldSymbols.Contains(input[index]),
 0333                            $"Encountered an unexpected date field symbol '{input[index]}' from ICU which has no known c
 334
 0335                        destination.Append(input[index++]);
 336                        break;
 337                }
 338            }
 339
 0340            return destination.ToString();
 341        }
 342
 343        private static void NormalizeDayOfWeek(string input, ref ValueStringBuilder destination, ref int index)
 344        {
 0345            char dayChar = input[index];
 0346            int occurrences = CountOccurrences(input, dayChar, ref index);
 0347            occurrences = Math.Max(occurrences, 3);
 0348            if (occurrences > 4)
 349            {
 350                // 5 and 6 E/e/c characters in ICU is the super short names, which maps closest to ddd in .NET
 0351                occurrences = 3;
 352            }
 353
 0354            destination.Append('d', occurrences);
 0355        }
 356
 357        private static int CountOccurrences(string input, char value, ref int index)
 358        {
 0359            int startIndex = index;
 0360            while (index < input.Length && input[index] == value)
 361            {
 0362                index++;
 363            }
 364
 0365            return index - startIndex;
 366        }
 367
 368        private static unsafe bool EnumMonthNames(string localeName, CalendarId calendarId, CalendarDataType dataType, o
 369        {
 0370            monthNames = null;
 371
 0372            IcuEnumCalendarsData callbackContext = default;
 0373            callbackContext.Results = new List<string>();
 0374            bool result = EnumCalendarInfo(localeName, calendarId, dataType, &callbackContext);
 0375            if (result)
 376            {
 377                // the month-name arrays are expected to have 13 elements.  If ICU only returns 12, add an
 378                // extra empty string to fill the array.
 0379                if (callbackContext.Results.Count == 12)
 380                {
 0381                    callbackContext.Results.Add(string.Empty);
 382                }
 383
 0384                if (callbackContext.Results.Count > 13)
 385                {
 0386                    Debug.Assert(calendarId == CalendarId.HEBREW && callbackContext.Results.Count == 14);
 387
 0388                    if (calendarId == CalendarId.HEBREW)
 389                    {
 0390                        leapHebrewMonthName = callbackContext.Results[13];
 391                    }
 0392                    callbackContext.Results.RemoveRange(13, callbackContext.Results.Count - 13);
 393                }
 394
 0395                monthNames = callbackContext.Results.ToArray();
 396            }
 397
 0398            return result;
 399        }
 400
 401        private static bool EnumEraNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string
 402        {
 0403            bool result = EnumCalendarInfo(localeName, calendarId, dataType, out eraNames);
 404
 405            // .NET expects that only the Japanese calendars have more than 1 era.
 406            // So for other calendars, only return the latest era.
 0407            if (calendarId != CalendarId.JAPAN && calendarId != CalendarId.JAPANESELUNISOLAR && eraNames?.Length > 0)
 408            {
 0409                eraNames = [eraNames[^1]];
 410            }
 411
 0412            return result;
 413        }
 414
 415        internal static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType
 416        {
 0417            calendarData = null;
 418
 0419            IcuEnumCalendarsData callbackContext = default;
 0420            callbackContext.Results = new List<string>();
 0421            bool result = EnumCalendarInfo(localeName, calendarId, dataType, &callbackContext);
 0422            if (result)
 423            {
 0424                calendarData = callbackContext.Results.ToArray();
 425            }
 426
 0427            return result;
 428        }
 429
 430        private static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType,
 431        {
 432#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 433            callbackContext->Results.AddRange(GetCalendarInfoNative(localeName, calendarId, dataType).Split("||"));
 434            return callbackContext->Results.Count > 0;
 435#else
 0436            return Interop.Globalization.EnumCalendarInfo(&EnumCalendarInfoCallback, localeName, calendarId, dataType, (
 437#endif
 438        }
 439
 440        [UnmanagedCallersOnly]
 441        private static unsafe void EnumCalendarInfoCallback(char* calendarStringPtr, IntPtr context)
 442        {
 443            try
 444            {
 0445                ReadOnlySpan<char> calendarStringSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(calendarStrin
 0446                IcuEnumCalendarsData* callbackContext = (IcuEnumCalendarsData*)context;
 447
 0448                if (callbackContext->DisallowDuplicates)
 449                {
 0450                    foreach (string existingResult in callbackContext->Results)
 451                    {
 0452                        if (calendarStringSpan.SequenceEqual(existingResult))
 453                        {
 454                            // the value is already in the results, so don't add it again
 0455                            return;
 456                        }
 457                    }
 458                }
 459
 0460                callbackContext->Results.Add(calendarStringSpan.ToString());
 0461            }
 462            catch (Exception e)
 463            {
 0464                Debug.Fail(e.ToString());
 465                // we ignore the managed exceptions here because EnumCalendarInfoCallback will get called from the nativ
 466                // If we don't ignore the exception here that can cause the runtime to fail fast.
 467            }
 0468        }
 469
 470        private struct IcuEnumCalendarsData
 471        {
 472            public List<string> Results;
 473            public bool DisallowDuplicates;
 474        }
 475    }
 476}
 477

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Nls.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9
 10namespace System.Globalization
 11{
 12    internal sealed partial class CalendarData
 13    {
 14        private static bool NlsSystemSupportsTaiwaneseCalendar()
 15        {
 016            Debug.Assert(!GlobalizationMode.Invariant);
 017            Debug.Assert(GlobalizationMode.UseNls);
 18
 19            // Taiwanese calendar get listed as one of the optional zh-TW calendars only when having zh-TW UI
 020            return CallGetCalendarInfoEx("zh-TW", CalendarId.TAIWAN, CAL_SCALNAME, out string _);
 21        }
 22
 23        // PAL Layer ends here
 24
 25        private const uint CAL_RETURN_NUMBER = 0x20000000;
 26        private const uint CAL_SCALNAME = 0x00000002;
 27
 28        private static bool CallGetCalendarInfoEx(string? localeName, CalendarId calendar, uint calType, out int data)
 29        {
 030            return Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType | CAL_RETURN_NUMB
 31        }
 32
 33        private static unsafe bool CallGetCalendarInfoEx(string localeName, CalendarId calendar, uint calType, out strin
 34        {
 35            const int BUFFER_LENGTH = 80;
 36
 37            // The maximum size for values returned from GetCalendarInfoEx is 80 characters.
 038            char* buffer = stackalloc char[BUFFER_LENGTH];
 39
 040            int ret = Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType, (IntPtr)buffe
 041            if (ret > 0)
 42            {
 043                if (buffer[ret - 1] == '\0')
 44                {
 045                    ret--; // don't include the null termination in the string
 46                }
 047                data = new string(buffer, 0, ret);
 048                return true;
 49            }
 050            data = "";
 051            return false;
 52        }
 53
 54        // Context for EnumCalendarInfoExEx callback.
 55        private struct EnumData
 56        {
 57            public string? userOverride;
 58            public List<string>? strings;
 59        }
 60
 61        // EnumCalendarInfoExEx callback itself.
 62        [UnmanagedCallersOnly]
 63        private static unsafe Interop.BOOL EnumCalendarInfoCallback(char* lpCalendarInfoString, uint calendar, IntPtr pR
 64        {
 065            EnumData* context = (EnumData*)lParam;
 66            try
 67            {
 068                string calendarInfo = new string(lpCalendarInfoString);
 69
 70                // If we had a user override, check to make sure this differs
 071                if (context->userOverride != calendarInfo)
 72                {
 073                    Debug.Assert(context->strings != null);
 074                    context->strings!.Add(calendarInfo); // TODO https://github.com/dotnet/roslyn/issues/65634: Remove !
 75                }
 76
 077                return Interop.BOOL.TRUE;
 78            }
 079            catch (Exception)
 80            {
 081                return Interop.BOOL.FALSE;
 82            }
 083        }
 84
 85        //
 86        // struct to help our calendar data enumeration callback
 87        //
 88        public struct NlsEnumCalendarsData
 89        {
 90            public int userOverride;   // user override value (if found)
 91            public List<int> calendars;      // list of calendars found so far
 92        }
 93
 94        [UnmanagedCallersOnly]
 95        private static unsafe Interop.BOOL EnumCalendarsCallback(char* lpCalendarInfoString, uint calendar, IntPtr reser
 96        {
 097            NlsEnumCalendarsData* context = (NlsEnumCalendarsData*)lParam;
 98            try
 99            {
 100                // If we had a user override, check to make sure this differs
 0101                if (context->userOverride != calendar)
 0102                    context->calendars.Add((int)calendar);
 103
 0104                return Interop.BOOL.TRUE;
 105            }
 0106            catch (Exception)
 107            {
 0108                return Interop.BOOL.FALSE;
 109            }
 0110        }
 111    }
 112}
 113

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Windows.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7
 8namespace System.Globalization
 9{
 10    internal sealed partial class CalendarData
 11    {
 12        private const uint CAL_ICALINTVALUE = 0x00000001;
 13        private const uint CAL_RETURN_GENITIVE_NAMES = 0x10000000;
 14        private const uint CAL_NOUSEROVERRIDE = 0x80000000;
 15        private const uint CAL_SMONTHDAY = 0x00000038;
 16        private const uint CAL_SSHORTDATE = 0x00000005;
 17        private const uint CAL_SLONGDATE = 0x00000006;
 18        private const uint CAL_SYEARMONTH = 0x0000002f;
 19        private const uint CAL_SDAYNAME7 = 0x0000000d;
 20        private const uint CAL_SABBREVDAYNAME7 = 0x00000014;
 21        private const uint CAL_SMONTHNAME1 = 0x00000015;
 22        private const uint CAL_SABBREVMONTHNAME1 = 0x00000022;
 23        private const uint CAL_SSHORTESTDAYNAME7 = 0x00000037;
 24        private const uint CAL_SERASTRING = 0x00000004;
 25        private const uint CAL_SABBREVERASTRING = 0x00000039;
 26        private const uint CAL_ITWODIGITYEARMAX = 0x00000030;
 27
 28        private const uint ENUM_ALL_CALENDARS = 0xffffffff;
 29
 30        private const uint LOCALE_ICALENDARTYPE = 0x00001009;
 31        private const uint LOCALE_SSHORTDATE = 0x0000001F;
 32        private const uint LOCALE_SLONGDATE = 0x00000020;
 33        private const uint LOCALE_SYEARMONTH = 0x00001006;
 34
 35        private bool LoadCalendarDataFromSystemCore(string localeName, CalendarId calendarId)
 36        {
 037            Debug.Assert(!GlobalizationMode.Invariant);
 38
 039            if (GlobalizationMode.UseNls)
 40            {
 041                return NlsLoadCalendarDataFromSystem(localeName, calendarId);
 42            }
 43
 44            // If running using ICU on Windows we should honor user overrides using NLS and the rest from ICU
 045            bool result = IcuLoadCalendarDataFromSystem(localeName, calendarId);
 46
 047            if (result && bUseUserOverrides)
 48            {
 049                NormalizeCalendarId(ref calendarId, ref localeName);
 050                result &= CallGetCalendarInfoEx(localeName, calendarId, CAL_ITWODIGITYEARMAX, out this.iTwoDigitYearMax)
 51
 52                // They want user overrides, see if the user calendar matches the input calendar
 053                CalendarId userCalendar = (CalendarId)CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
 54
 55                // If the calendars were the same, see if the locales were the same
 056                if (userCalendar == calendarId)
 57                {
 058                    string? shortDateOverride = CultureData.ReescapeWin32String(CultureData.GetLocaleInfoEx(localeName, 
 059                    string? longDateOverride = CultureData.ReescapeWin32String(CultureData.GetLocaleInfoEx(localeName, L
 060                    InsertOrSwapOverride(shortDateOverride, ref this.saShortDates);
 061                    InsertOrSwapOverride(longDateOverride, ref this.saLongDates);
 62                }
 63            }
 64
 065            return result;
 66        }
 67
 68        private static void InsertOrSwapOverride(string? value, ref string[] destination)
 69        {
 070            if (value == null)
 071                return;
 72
 073            int i = Array.IndexOf(destination, value);
 074            if (i >= 0)
 75            {
 076                if (i > 0)
 77                {
 078                    string tmp = destination[0];
 079                    destination[0] = value;
 080                    destination[i] = tmp;
 81                }
 82
 083                return;
 84            }
 85
 086            string[] newArray = new string[destination.Length + 1];
 087            newArray[0] = value;
 088            Array.Copy(destination, 0, newArray, 1, destination.Length);
 089            destination = newArray;
 090        }
 91
 92        private bool NlsLoadCalendarDataFromSystem(string localeName, CalendarId calendarId)
 93        {
 094            bool ret = true;
 95
 096            uint useOverrides = bUseUserOverrides ? 0 : CAL_NOUSEROVERRIDE;
 97
 098            NormalizeCalendarId(ref calendarId, ref localeName);
 99
 100            // Numbers
 0101            ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_ITWODIGITYEARMAX | useOverrides, out this.iTwoDigit
 102
 103            // Strings
 0104            ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SCALNAME, out this.sNativeName);
 0105            ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SMONTHDAY, out this.sMonthDay);
 106
 107            // String Arrays
 108            // Formats
 0109            ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SSHORTDATE, LOCALE_SSHORTDATE | useOverrides, out th
 0110            ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SLONGDATE, LOCALE_SLONGDATE | useOverrides, out this
 111
 112            // Get the YearMonth pattern.
 0113            ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SYEARMONTH, LOCALE_SYEARMONTH, out this.saYearMonths
 114
 115            // Day & Month Names
 116            // These are all single calType entries, 1 per day, so we have to make 7 or 13 calls to collect all the name
 117
 118            // Day
 119            // Note that we're off-by-one since managed starts on sunday and windows starts on monday
 0120            ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SDAYNAME7, out this.saDayNames);
 0121            ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SABBREVDAYNAME7, out this.saAbbrevDayNames);
 122
 123            // Month names
 0124            ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1, out this.saMonthNames);
 0125            ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1, out this.saAbbrevMonthNames);
 126
 127            //
 128            // The following LCTYPE are not supported in some platforms.  If the call fails,
 129            // don't return a failure.
 130            //
 0131            GetCalendarDayInfo(localeName, calendarId, CAL_SSHORTESTDAYNAME7, out this.saSuperShortDayNames);
 132
 133            // Gregorian may have genitive month names
 0134            if (calendarId == CalendarId.GREGORIAN)
 135            {
 0136                GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this.saMon
 0137                GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this
 138            }
 139
 140            // Calendar Parts Names
 141            // This doesn't get always get localized names for gregorian (not available in windows < 7)
 142            // so: eg: coreclr on win < 7 won't get these
 0143            CallEnumCalendarInfo(localeName, calendarId, CAL_SERASTRING, 0, out this.saEraNames!);
 0144            CallEnumCalendarInfo(localeName, calendarId, CAL_SABBREVERASTRING, 0, out this.saAbbrevEraNames!);
 145
 146            //
 147            // Calendar Era Info
 148            // Note that calendar era data (offsets, etc) is hard coded for each calendar since this
 149            // data is implementation specific and not dynamic (except perhaps Japanese)
 150            //
 151
 152            // Clean up the escaping of the formats
 0153            this.saShortDates = CultureData.ReescapeWin32Strings(this.saShortDates)!;
 0154            this.saLongDates = CultureData.ReescapeWin32Strings(this.saLongDates)!;
 0155            this.saYearMonths = CultureData.ReescapeWin32Strings(this.saYearMonths)!;
 0156            this.sMonthDay = CultureData.ReescapeWin32String(this.sMonthDay)!;
 157
 0158            return ret;
 159        }
 160
 161        private static void NormalizeCalendarId(ref CalendarId calendarId, ref string localeName)
 162        {
 163            //
 164            // Windows doesn't support some calendars right now, so remap those.
 165            //
 0166            switch (calendarId)
 167            {
 168                case CalendarId.JAPANESELUNISOLAR:    // Data looks like Japanese
 0169                    calendarId = CalendarId.JAPAN;
 0170                    break;
 171                case CalendarId.JULIAN:               // Data looks like gregorian US
 172                case CalendarId.CHINESELUNISOLAR:     // Algorithmic, so actual data is irrelevant
 173                case CalendarId.SAKA:                 // reserved to match Office but not implemented in our code, so da
 174                case CalendarId.LUNAR_ETO_CHN:        // reserved to match Office but not implemented in our code, so da
 175                case CalendarId.LUNAR_ETO_KOR:        // reserved to match Office but not implemented in our code, so da
 176                case CalendarId.LUNAR_ETO_ROKUYOU:    // reserved to match Office but not implemented in our code, so da
 177                case CalendarId.KOREANLUNISOLAR:      // Algorithmic, so actual data is irrelevant
 178                case CalendarId.TAIWANLUNISOLAR:      // Algorithmic, so actual data is irrelevant
 0179                    calendarId = CalendarId.GREGORIAN_US;
 180                    break;
 181            }
 182
 183            //
 184            // Special handling for some special calendar due to OS limitation.
 185            // This includes calendar like Taiwan calendar, UmAlQura calendar, etc.
 186            //
 0187            CheckSpecialCalendar(ref calendarId, ref localeName);
 0188        }
 189
 190        ////////////////////////////////////////////////////////////////////////
 191        //
 192        // For calendars like Gregorain US/Taiwan/UmAlQura, they are not available
 193        // in all OS or all localized versions of OS.
 194        // If OS does not support these calendars, we will fallback by using the
 195        // appropriate fallback calendar and locale combination to retrieve data from OS.
 196        //
 197        // Parameters:
 198        //  __deref_inout pCalendarInt:
 199        //    Pointer to the calendar ID. This will be updated to new fallback calendar ID if needed.
 200        //  __in_out pLocaleNameStackBuffer
 201        //    Pointer to the StackSString object which holds the locale name to be checked.
 202        //    This will be updated to new fallback locale name if needed.
 203        //
 204        ////////////////////////////////////////////////////////////////////////
 205        private static void CheckSpecialCalendar(ref CalendarId calendar, ref string localeName)
 206        {
 207            // Gregorian-US isn't always available in the OS, however it is the same for all locales
 0208            switch (calendar)
 209            {
 210                case CalendarId.GREGORIAN_US:
 211                    // See if this works
 0212                    if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out string _))
 213                    {
 214                        // Failed, set it to a locale (fa-IR) that always has Gregorian US available in the OS
 0215                        localeName = "fa-IR";
 216
 217                        // See if that works
 0218                        if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out string _))
 219                        {
 220                            // Failed again, just use en-US with the gregorian calendar
 0221                            localeName = "en-US";
 0222                            calendar = CalendarId.GREGORIAN;
 223                        }
 224                    }
 0225                    break;
 226                case CalendarId.TAIWAN:
 227                    // Taiwan calendar data is not always in all language version of OS due to Geopolical reasons.
 228                    // It is only available in zh-TW localized versions of Windows.
 229                    // Let's check if OS supports it.  If not, fallback to Greogrian localized for Taiwan calendar.
 0230                    if (!NlsSystemSupportsTaiwaneseCalendar())
 231                    {
 0232                        calendar = CalendarId.GREGORIAN;
 233                    }
 234                    break;
 235            }
 0236        }
 237
 238        private static unsafe bool CallEnumCalendarInfo(string localeName, CalendarId calendar, uint calType, uint lcTyp
 239        {
 0240            EnumData context = default;
 0241            context.userOverride = null;
 0242            context.strings = new List<string>();
 243            // First call GetLocaleInfo if necessary
 0244            if ((lcType != 0) && ((lcType & CAL_NOUSEROVERRIDE) == 0))
 245            {
 246                // They want user overrides, see if the user calendar matches the input calendar
 0247                CalendarId userCalendar = (CalendarId)CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
 248
 249                // If the calendars were the same, see if the locales were the same
 0250                if (userCalendar == calendar)
 251                {
 252                    // They matched, get the user override since locale & calendar match
 0253                    string? res = CultureData.GetLocaleInfoEx(localeName, lcType);
 254
 255                    // if it succeeded remember the override for the later callers
 0256                    if (res != null)
 257                    {
 258                        // Remember this was the override (so we can look for duplicates later in the enum function)
 0259                        context.userOverride = res;
 260
 261                        // Add to the result strings.
 0262                        context.strings.Add(res);
 263                    }
 264                }
 265            }
 266
 267            // Now call the enumeration API. Work is done by our callback function
 0268            Interop.Kernel32.EnumCalendarInfoExEx(&EnumCalendarInfoCallback, localeName, (uint)calendar, null, calType, 
 269
 270            // Now we have a list of data, fail if we didn't find anything.
 0271            Debug.Assert(context.strings != null);
 0272            if (context.strings.Count == 0)
 273            {
 0274                data = null;
 0275                return false;
 276            }
 277
 0278            string[] output = context.strings.ToArray();
 279
 0280            if (calType == CAL_SABBREVERASTRING || calType == CAL_SERASTRING)
 281            {
 282                // Eras are enumerated backwards.  (oldest era name first, but
 283                // Japanese calendar has newest era first in array, and is only
 284                // calendar with multiple eras)
 0285                Array.Reverse(output, 0, output.Length);
 286            }
 287
 0288            data = output;
 289
 0290            return true;
 291        }
 292
 293        ////////////////////////////////////////////////////////////////////////
 294        //
 295        // Get the native day names
 296        //
 297        // NOTE: There's a disparity between .NET & windows day orders, the input day should
 298        //           start with Sunday
 299        //
 300        // Parameters:
 301        //      OUT pOutputStrings      The output string[] value.
 302        //
 303        ////////////////////////////////////////////////////////////////////////
 304        private static bool GetCalendarDayInfo(string localeName, CalendarId calendar, uint calType, out string[] output
 305        {
 0306            bool result = true;
 307
 308            //
 309            // We'll need a new array of 7 items
 310            //
 0311            string[] results = new string[7];
 312
 313            // Get each one of them
 0314            for (int i = 0; i < 7; i++, calType++)
 315            {
 0316                result &= CallGetCalendarInfoEx(localeName, calendar, calType, out results[i]);
 317
 318                // On the first iteration we need to go from CAL_SDAYNAME7 to CAL_SDAYNAME1, so subtract 7 before the ++
 319                // This is because the framework starts on sunday and windows starts on monday when counting days
 0320                if (i == 0)
 0321                    calType -= 7;
 322            }
 323
 0324            outputStrings = results;
 325
 0326            return result;
 327        }
 328
 329        ////////////////////////////////////////////////////////////////////////
 330        //
 331        // Get the native month names
 332        //
 333        // Parameters:
 334        //      OUT pOutputStrings      The output string[] value.
 335        //
 336        ////////////////////////////////////////////////////////////////////////
 337        private static bool GetCalendarMonthInfo(string localeName, CalendarId calendar, uint calType, out string[] outp
 338        {
 339            //
 340            // We'll need a new array of 13 items
 341            //
 0342            string[] results = new string[13];
 343
 344            // Get each one of them
 0345            for (int i = 0; i < 13; i++, calType++)
 346            {
 0347                if (!CallGetCalendarInfoEx(localeName, calendar, calType, out results[i]))
 0348                    results[i] = "";
 349            }
 350
 0351            outputStrings = results;
 352
 0353            return true;
 354        }
 355
 356        internal static unsafe int GetCalendarsCore(string localeName, bool useUserOverride, CalendarId[] calendars)
 357        {
 0358            Debug.Assert(!GlobalizationMode.Invariant);
 359
 0360            if (GlobalizationMode.UseNls)
 361            {
 0362                return NlsGetCalendars(localeName, useUserOverride, calendars);
 363            }
 364
 0365            int count = IcuGetCalendars(localeName, calendars);
 366
 0367            if (useUserOverride)
 368            {
 369                // They want user overrides, see if the user calendar matches the input calendar
 0370                int userCalendar = CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
 371
 0372                if (userCalendar != 0 && (CalendarId)userCalendar != calendars[0])
 373                {
 0374                    CalendarId userOverride = (CalendarId)userCalendar;
 0375                    for (int i = 1; i < calendars.Length; i++)
 376                    {
 0377                        if (calendars[i] == userOverride)
 378                        {
 0379                            CalendarId tmp = calendars[0];
 0380                            calendars[0] = userOverride;
 0381                            calendars[i] = tmp;
 0382                            return count;
 383                        }
 384                    }
 385
 386                    // We didn't find it, we insert it at the beginning of the array. If calendar's array is full, we dr
 0387                    count = count < calendars.Length ? count + 1 : count;
 0388                    Span<CalendarId> tmpSpan = stackalloc CalendarId[count]; // should be 23 max.
 0389                    tmpSpan[0] = userOverride;
 0390                    calendars.AsSpan(0, count - 1).CopyTo(tmpSpan.Slice(1));
 0391                    tmpSpan.CopyTo(calendars);
 392                }
 393            }
 394
 0395            return count;
 396        }
 397
 398        private static int NlsGetCalendars(string localeName, bool useUserOverride, CalendarId[] calendars)
 399        {
 0400            NlsEnumCalendarsData data = default;
 0401            data.userOverride = 0;
 0402            data.calendars = new List<int>();
 403
 404            // First call GetLocaleInfo if necessary
 0405            if (useUserOverride)
 406            {
 407                // They want user overrides, see if the user calendar matches the input calendar
 0408                int userCalendar = CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
 409
 410                // If we got a default, then use it as the first calendar
 0411                if (userCalendar != 0)
 412                {
 0413                    data.userOverride = userCalendar;
 0414                    data.calendars.Add(userCalendar);
 415                }
 416            }
 417
 418            unsafe
 419            {
 0420                Interop.Kernel32.EnumCalendarInfoExEx(&EnumCalendarsCallback, localeName, ENUM_ALL_CALENDARS, null, CAL_
 421            }
 422
 423            // Copy to the output array
 0424            for (int i = 0; i < Math.Min(calendars.Length, data.calendars.Count); i++)
 0425                calendars[i] = (CalendarId)data.calendars[i];
 426
 427            // Now we have a list of data, return the count
 0428            return data.calendars.Count;
 429        }
 430
 431        // Get native two digit year max
 432        internal static int GetTwoDigitYearMax(CalendarId calendarId)
 433        {
 0434            return GlobalizationMode.Invariant ? Invariant.iTwoDigitYearMax :
 0435                    CallGetCalendarInfoEx(null, calendarId, CAL_ITWODIGITYEARMAX, out int twoDigitYearMax) ?
 0436                        twoDigitYearMax :
 0437                        -1;
 438        }
 439    }
 440}
 441

Methods/Properties

.ctor()
.cctor()
CreateInvariant()
.ctor(System.String,System.Globalization.CalendarId,System.Boolean)
InitializeEraNames(System.String,System.Globalization.CalendarId)
AreEraNamesEmpty()
InitializeAbbreviatedEraNames(System.String,System.Globalization.CalendarId)
GetCalendarCurrentEra(System.Globalization.Calendar)
CalendarIdToCultureName(System.Globalization.CalendarId)
SystemSupportsTaiwaneseCalendar()
IcuLoadCalendarDataFromSystem(System.String,System.Globalization.CalendarId)
IcuGetCalendars(System.String,System.Globalization.CalendarId[])
IcuSystemSupportsTaiwaneseCalendar()
GetCalendarInfo(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.String&)
EnumDatePatterns(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.String[]&)
FixDefaultShortDatePattern(System.Collections.Generic.List`1<System.String>)
NormalizeDatePattern(System.String)
NormalizeDayOfWeek(System.String,System.Text.ValueStringBuilder&,System.Int32&)
CountOccurrences(System.String,System.Char,System.Int32&)
EnumMonthNames(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.String[]&,System.String&)
EnumEraNames(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.String[]&)
EnumCalendarInfo(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.String[]&)
EnumCalendarInfo(System.String,System.Globalization.CalendarId,System.Globalization.CalendarDataType,System.Globalization.CalendarData/IcuEnumCalendarsData*)
EnumCalendarInfoCallback(System.Char*,System.IntPtr)
NlsSystemSupportsTaiwaneseCalendar()
CallGetCalendarInfoEx(System.String,System.Globalization.CalendarId,System.UInt32,System.Int32&)
CallGetCalendarInfoEx(System.String,System.Globalization.CalendarId,System.UInt32,System.String&)
EnumCalendarInfoCallback(System.Char*,System.UInt32,System.IntPtr,System.Void*)
EnumCalendarsCallback(System.Char*,System.UInt32,System.IntPtr,System.Void*)
LoadCalendarDataFromSystemCore(System.String,System.Globalization.CalendarId)
InsertOrSwapOverride(System.String,System.String[]&)
NlsLoadCalendarDataFromSystem(System.String,System.Globalization.CalendarId)
NormalizeCalendarId(System.Globalization.CalendarId&,System.String&)
CheckSpecialCalendar(System.Globalization.CalendarId&,System.String&)
CallEnumCalendarInfo(System.String,System.Globalization.CalendarId,System.UInt32,System.UInt32,System.String[]&)
GetCalendarDayInfo(System.String,System.Globalization.CalendarId,System.UInt32,System.String[]&)
GetCalendarMonthInfo(System.String,System.Globalization.CalendarId,System.UInt32,System.String[]&)
GetCalendarsCore(System.String,System.Boolean,System.Globalization.CalendarId[])
NlsGetCalendars(System.String,System.Boolean,System.Globalization.CalendarId[])
GetTwoDigitYearMax(System.Globalization.CalendarId)