< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 887
Coverable lines: 887
Total lines: 2531
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 606
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
InternalGetAbbreviatedDayOfWeekNames()0%220%
InternalGetAbbreviatedDayOfWeekNamesCore()100%110%
InternalGetSuperShortDayNames()0%220%
InternalGetSuperShortDayNamesCore()100%110%
InternalGetDayOfWeekNames()0%220%
InternalGetDayOfWeekNamesCore()100%110%
InternalGetAbbreviatedMonthNames()0%220%
InternalGetAbbreviatedMonthNamesCore()0%220%
InternalGetMonthNames()0%220%
internalGetMonthNamesCore()0%220%
.ctor()100%110%
InitializeOverridableProperties(...)0%12120%
GetInstance(...)0%220%
GetProviderNonNull(System.IFormatProvider)0%880%
GetFormat(...)0%220%
Clone()100%110%
AMDesignatorTChar()0%660%
GetEra(...)0%16160%
GetEraName(...)0%440%
GetAbbreviatedEraName(...)0%660%
DateSeparatorTChar()0%660%
OnLongDatePatternChanged()100%110%
OnLongTimePatternChanged()100%110%
PMDesignatorTChar()0%660%
OnShortDatePatternChanged()100%110%
OnShortTimePatternChanged()100%110%
TimeSeparatorTChar()0%660%
OnYearMonthPatternChanged()100%110%
CheckNullValue(...)0%440%
InternalGetMonthName(...)0%880%
InternalGetGenitiveMonthNames(...)0%660%
InternalGetLeapYearMonthNames()0%220%
GetAbbreviatedDayName(...)0%220%
GetShortestDayName(...)0%220%
GetCombinedPatterns(...)0%440%
GetAllDateTimePatterns()0%440%
GetAllDateTimePatterns(...)0%38380%
GetDayName(...)0%220%
GetAbbreviatedMonthName(...)0%220%
GetMonthName(...)0%220%
GetMergedPatterns(...)0%10100%
ReadOnly(...)0%220%
SetAllDateTimePatterns(...)0%22220%
DecimalSeparatorTChar()0%660%
ValidateStyles(...)0%660%
ThrowInvalid(System.Globalization.DateTimeStyles,System.Boolean)0%660%
InitializeFormatFlags()100%110%
YearMonthAdjustment(...)0%16160%
GetJapaneseCalendarDTFI()0%220%
GetTaiwanCalendarDTFI()0%220%
ClearTokenHashTable()100%110%
CreateTokenHashTable()0%74740%
AddMonthNames(...)0%660%
TryParseHebrewNumber(...)0%12120%
IsHebrewChar(...)0%220%
IsAllowedJapaneseTokenFollowedByNonSpaceLetter(...)0%10100%
Tokenize(...)0%50500%
InsertAtCurrentHashNode(...)0%10100%
InsertHash(...)0%30300%
CompareStringIgnoreCaseOptimized(...)0%660%
.ctor(...)100%110%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.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    /// <summary>
 14    /// Flags used to indicate different styles of month names.
 15    /// This is an internal flag used by internalGetMonthName().
 16    /// Use flag here in case that we need to provide a combination of these styles
 17    /// (such as month name of a leap year in genitive form.  Not likely for now,
 18    /// but would like to keep the option open).
 19    /// </summary>
 20
 21    [Flags]
 22    internal enum MonthNameStyles
 23    {
 24        Regular = 0x00000000,
 25        Genitive = 0x00000001,
 26        LeapYear = 0x00000002,
 27    }
 28
 29    /// <summary>
 30    /// Flags used to indicate special rule used in parsing/formatting
 31    /// for a specific DateTimeFormatInfo instance.
 32    /// This is an internal flag.
 33    ///
 34    /// This flag is different from MonthNameStyles because this flag
 35    /// can be expanded to accommodate parsing behaviors like CJK month names
 36    /// or alternative month names, etc.
 37    /// </summary>
 38    [Flags]
 39    internal enum DateTimeFormatFlags
 40    {
 41        None = 0x00000000,
 42        UseGenitiveMonth = 0x00000001,
 43        UseLeapYearMonth = 0x00000002,
 44        UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
 45        UseHebrewRule = 0x00000008,   // Format/Parse using the Hebrew calendar rule.
 46        UseSpacesInDayNames = 0x00000010,   // Has spaces or non-breaking space in the day names.
 47        UseDigitPrefixInTokens = 0x00000020,   // Has token starting with numbers.
 48
 49        NotInitialized = -1,
 50    }
 51
 52    public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
 53    {
 54        // an index which points to a record in Culture Data Table.
 55        private readonly CultureData _cultureData;
 56
 57        // The culture name used to create this DTFI.
 58        private string? _name;
 59
 60        // CompareInfo usually used by the parser.
 61        private CompareInfo? _compareInfo;
 62
 63        private string? amDesignator;
 64        private string? pmDesignator;
 65
 66        private string? dateSeparator;            // derived from short date (whidbey expects, arrowhead doesn't)
 67
 68        private string? generalShortTimePattern;     // short date + short time (whidbey expects, arrowhead doesn't)
 69
 70        private string? generalLongTimePattern;     // short date + long time (whidbey expects, arrowhead doesn't)
 71
 72        private string? timeSeparator;            // derived from long time (whidbey expects, arrowhead doesn't)
 73        private string? monthDayPattern;
 74        private string? dateTimeOffsetPattern;
 75
 76        private byte[]? amDesignatorUtf8;
 77        private byte[]? pmDesignatorUtf8;
 78        private byte[]? timeSeparatorUtf8;
 79        private byte[]? dateSeparatorUtf8;
 80
 81        private const string rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
 82
 83        // The sortable pattern is based on ISO 8601.
 84        private const string sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
 85        private const string universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
 86
 87        private Calendar calendar;
 88
 089        private int firstDayOfWeek = -1;
 090        private int calendarWeekRule = -1;
 91
 92        private string? fullDateTimePattern;        // long date + long time (whidbey expects, arrowhead doesn't)
 93
 94        private string[]? abbreviatedDayNames;
 95
 96        private string[]? m_superShortDayNames;
 97
 98        private string[]? dayNames;
 99        private string[]? abbreviatedMonthNames;
 100        private string[]? monthNames;
 101
 102        // Cache the genitive month names that we retrieve from the data table.
 103
 104        private string[]? genitiveMonthNames;
 105
 106        // Cache the abbreviated genitive month names that we retrieve from the data table.
 107
 108        private string[]? m_genitiveAbbreviatedMonthNames;
 109
 110        // Cache the month names of a leap year that we retrieve from the data table.
 111        private string[]? leapYearMonthNames;
 112
 113        // For our "patterns" arrays we have 2 variables, a string and a string[]
 114        //
 115        // The string[] contains the list of patterns, EXCEPT the default may not be included.
 116        // The string contains the default pattern.
 117        // When we initially construct our string[], we set the string to string[0]
 118
 119        // The "default" Date/time patterns
 120        private string? longDatePattern;
 121        private string? shortDatePattern;
 122        private string? yearMonthPattern;
 123        private string? longTimePattern;
 124        private string? shortTimePattern;
 125
 126        private string[]? allYearMonthPatterns;
 127
 128        private string[]? allShortDatePatterns;
 129        private string[]? allLongDatePatterns;
 130        private string[]? allShortTimePatterns;
 131        private string[]? allLongTimePatterns;
 132
 133        // Cache the era names for this DateTimeFormatInfo instance.
 134        private string[]? m_eraNames;
 135        private string[]? m_abbrevEraNames;
 136        private string[]? m_abbrevEnglishEraNames;
 137
 138        private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
 139
 140        // CultureInfo updates this
 141        internal bool _isReadOnly;
 142
 143        // This flag gives hints about if formatting/parsing should perform special code path for things like
 144        // genitive form or leap year month names.
 145
 0146        private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
 147
 0148        private string CultureName => _name ??= _cultureData.CultureName;
 149
 150        // Culture matches current DTFI. mainly used for string comparisons during parsing.
 0151        private CultureInfo Culture => field ??= CultureInfo.GetCultureInfo(CultureName);
 152
 153        // The language name of the culture used to create this DTFI.
 0154        private string LanguageName => field ??= _cultureData.TwoLetterISOLanguageName;
 155
 156        /// <summary>
 157        /// Create an array of string which contains the abbreviated day names.
 158        /// </summary>
 0159        private string[] InternalGetAbbreviatedDayOfWeekNames() => abbreviatedDayNames ?? InternalGetAbbreviatedDayOfWee
 160
 161        [MethodImpl(MethodImplOptions.NoInlining)]
 162        private string[] InternalGetAbbreviatedDayOfWeekNamesCore()
 163        {
 164            // Get the abbreviated day names for our current calendar
 0165            abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID);
 0166            Debug.Assert(abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 
 0167            return abbreviatedDayNames;
 168        }
 169
 170        /// <summary>
 171        /// Returns the string array of the one-letter day of week names.
 172        /// </summary>
 0173        private string[] InternalGetSuperShortDayNames() => m_superShortDayNames ?? InternalGetSuperShortDayNamesCore();
 174
 175        [MethodImpl(MethodImplOptions.NoInlining)]
 176        private string[] InternalGetSuperShortDayNamesCore()
 177        {
 178            // Get the super short day names for our current calendar
 0179            m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID);
 0180            Debug.Assert(m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.InternalGetSuperShortDayNames] Expected 
 0181            return m_superShortDayNames;
 182        }
 183
 184        /// <summary>
 185        /// Create an array of string which contains the day names.
 186        /// </summary>
 0187        private string[] InternalGetDayOfWeekNames() => dayNames ?? InternalGetDayOfWeekNamesCore();
 188
 189        [MethodImpl(MethodImplOptions.NoInlining)]
 190        private string[] InternalGetDayOfWeekNamesCore()
 191        {
 192            // Get the day names for our current calendar
 0193            dayNames = _cultureData.DayNames(Calendar.ID);
 0194            Debug.Assert(dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
 0195            return dayNames;
 196        }
 197
 198        /// <summary>
 199        /// Create an array of string which contains the abbreviated month names.
 200        /// </summary>
 0201        private string[] InternalGetAbbreviatedMonthNames() => abbreviatedMonthNames ?? InternalGetAbbreviatedMonthNames
 202
 203        [MethodImpl(MethodImplOptions.NoInlining)]
 204        private string[] InternalGetAbbreviatedMonthNamesCore()
 205        {
 206            // Get the month names for our current calendar
 0207            abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID);
 0208            Debug.Assert(abbreviatedMonthNames.Length == 12 || abbreviatedMonthNames.Length == 13,
 0209                "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
 0210            return abbreviatedMonthNames;
 211        }
 212
 213        /// <summary>
 214        /// Create an array of string which contains the month names.
 215        /// </summary>
 0216        private string[] InternalGetMonthNames() => monthNames ?? internalGetMonthNamesCore();
 217
 218        [MethodImpl(MethodImplOptions.NoInlining)]
 219        private string[] internalGetMonthNamesCore()
 220        {
 221            // Get the month names for our current calendar
 0222            monthNames = _cultureData.MonthNames(Calendar.ID);
 0223            Debug.Assert(monthNames.Length == 12 || monthNames.Length == 13,
 0224                "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
 0225            return monthNames;
 226        }
 227
 228        // Invariant DateTimeFormatInfo doesn't have user-overridden values
 229        // Default calendar is gregorian
 230        public DateTimeFormatInfo()
 0231            : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance())
 232        {
 0233        }
 234
 0235        internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
 236        {
 0237            Debug.Assert(cultureData != null);
 0238            Debug.Assert(cal != null);
 239
 240            // Remember our culture
 0241            _cultureData = cultureData;
 0242            calendar = cal;
 0243            InitializeOverridableProperties(cultureData, calendar.ID);
 0244        }
 245
 246        private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId)
 247        {
 0248            Debug.Assert(cultureData != null);
 0249            Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initializ
 250
 0251            if (firstDayOfWeek == -1)
 252            {
 0253                firstDayOfWeek = cultureData.FirstDayOfWeek;
 254            }
 0255            if (calendarWeekRule == -1)
 256            {
 0257                calendarWeekRule = cultureData.CalendarWeekRule;
 258            }
 259
 0260            amDesignator ??= cultureData.AMDesignator;
 0261            pmDesignator ??= cultureData.PMDesignator;
 0262            timeSeparator ??= cultureData.TimeSeparator;
 0263            dateSeparator ??= cultureData.DateSeparator(calendarId);
 264
 0265            allLongTimePatterns = _cultureData.LongTimes;
 0266            Debug.Assert(allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns
 267
 0268            allShortTimePatterns = _cultureData.ShortTimes;
 0269            Debug.Assert(allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patter
 270
 0271            allLongDatePatterns = cultureData.LongDates(calendarId);
 0272            Debug.Assert(allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns
 273
 0274            allShortDatePatterns = cultureData.ShortDates(calendarId);
 0275            Debug.Assert(allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patter
 276
 0277            allYearMonthPatterns = cultureData.YearMonths(calendarId);
 0278            Debug.Assert(allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patter
 0279        }
 280
 281        /// <summary>
 282        /// Returns a default DateTimeFormatInfo that will be universally
 283        /// supported and constant irrespective of the current culture.
 284        /// </summary>
 0285        public static DateTimeFormatInfo InvariantInfo => DateTimeFormat.InvariantFormatInfo;
 286
 287        /// <summary>
 288        /// Returns the current culture's DateTimeFormatInfo.
 289        /// </summary>
 290        public static DateTimeFormatInfo CurrentInfo
 291        {
 292            get
 293            {
 0294                CultureInfo culture = CultureInfo.CurrentCulture;
 0295                if (!culture._isInherited)
 296                {
 0297                    DateTimeFormatInfo? info = culture._dateTimeInfo;
 0298                    if (info != null)
 299                    {
 0300                        return info;
 301                    }
 302                }
 0303                return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo))!;
 304            }
 305        }
 306
 307        public static DateTimeFormatInfo GetInstance(IFormatProvider? provider)
 308        {
 0309            return provider == null ? CurrentInfo : GetProviderNonNull(provider);
 310
 311            static DateTimeFormatInfo GetProviderNonNull(IFormatProvider provider)
 312            {
 0313                if (provider.GetType() == typeof(CultureInfo) && ((CultureInfo)provider)._dateTimeInfo is { } info)
 314                {
 0315                    return info;
 316                }
 317
 0318                return
 0319                    provider as DateTimeFormatInfo ??
 0320                    provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo ??
 0321                    CurrentInfo;
 322            }
 323        }
 324
 325        public object? GetFormat(Type? formatType)
 326        {
 0327            return formatType == typeof(DateTimeFormatInfo) ? this : null;
 328        }
 329
 330        public object Clone()
 331        {
 0332            DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
 333            // We can use the data member calendar in the setter, instead of the property Calendar,
 334            // since the cloned copy should have the same state as the original copy.
 0335            n.calendar = (Calendar)Calendar.Clone();
 0336            n._isReadOnly = false;
 0337            return n;
 338        }
 339
 340        public string AMDesignator
 341        {
 342            get
 343            {
 0344                amDesignator ??= _cultureData.AMDesignator;
 0345                Debug.Assert(amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
 0346                return amDesignator;
 347            }
 348            set
 349            {
 0350                if (IsReadOnly)
 351                {
 0352                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 353                }
 0354                ArgumentNullException.ThrowIfNull(value);
 355
 0356                ClearTokenHashTable();
 0357                amDesignator = value;
 0358                amDesignatorUtf8 = null;
 0359            }
 360        }
 361
 362        internal ReadOnlySpan<TChar> AMDesignatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar>
 363        {
 0364            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 0365            return typeof(TChar) == typeof(char) ?
 0366                Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(AMDesignator) :
 0367                Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(amDesignatorUtf8 ??= Encoding.UTF8.GetBytes(AMDe
 368        }
 369
 370        public Calendar Calendar
 371        {
 372            get
 373            {
 0374                Debug.Assert(calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
 0375                return calendar;
 376            }
 377            [MemberNotNull(nameof(calendar))]
 378            set
 379            {
 0380                if (IsReadOnly)
 381                {
 0382                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 383                }
 0384                ArgumentNullException.ThrowIfNull(value);
 385
 0386                if (value == calendar)
 387                {
 0388                    return;
 389                }
 390
 0391                for (int i = 0; i < OptionalCalendars.Length; i++)
 392                {
 0393                    if (OptionalCalendars[i] == value.ID)
 394                    {
 395                        // We can use this one, so do so.
 396                        // Clean related properties if we already had a calendar set
 0397                        if (calendar != null)
 398                        {
 399                            // clean related properties which are affected by the calendar setting,
 400                            // so that they will be refreshed when they are accessed next time.
 401                            // These properites are in the order as appearing in calendar.xml.
 0402                            m_eraNames = null;
 0403                            m_abbrevEraNames = null;
 0404                            m_abbrevEnglishEraNames = null;
 405
 0406                            monthDayPattern = null;
 407
 0408                            dayNames = null;
 0409                            abbreviatedDayNames = null;
 0410                            m_superShortDayNames = null;
 0411                            monthNames = null;
 0412                            abbreviatedMonthNames = null;
 0413                            genitiveMonthNames = null;
 0414                            m_genitiveAbbreviatedMonthNames = null;
 0415                            leapYearMonthNames = null;
 0416                            formatFlags = DateTimeFormatFlags.NotInitialized;
 417
 0418                            allShortDatePatterns = null;
 0419                            allLongDatePatterns = null;
 0420                            allYearMonthPatterns = null;
 0421                            dateTimeOffsetPattern = null;
 422
 423                            // The defaults need reset as well:
 0424                            longDatePattern = null;
 0425                            shortDatePattern = null;
 0426                            yearMonthPattern = null;
 427
 428                            // These properies are not in the OS data, but they are dependent on the values like shortDa
 0429                            fullDateTimePattern = null; // Long date + long time
 0430                            generalShortTimePattern = null; // short date + short time
 0431                            generalLongTimePattern = null; // short date + long time
 432
 433                            // Derived item that changes
 0434                            dateSeparator = null;
 435
 436                            // We don't need to do these because they are not changed by changing calendar
 437                            //      amDesignator
 438                            //      pmDesignator
 439                            //      timeSeparator
 440                            //      longTimePattern
 441                            //      firstDayOfWeek
 442                            //      calendarWeekRule
 443
 444                            // remember to reload tokens
 0445                            ClearTokenHashTable();
 446                        }
 447
 448                        // Remember the new calendar
 0449                        calendar = value;
 0450                        InitializeOverridableProperties(_cultureData, calendar.ID);
 451
 452                        // We succeeded, return
 0453                        return;
 454                    }
 455                }
 456
 457                // The assigned calendar is not a valid calendar for this culture, throw
 0458                throw new ArgumentOutOfRangeException(nameof(value), value, SR.Argument_InvalidCalendar);
 459            }
 460        }
 461
 0462        private CalendarId[] OptionalCalendars => field ??= _cultureData.CalendarIds;
 463
 464        /// <summary>
 465        /// Get the era value by parsing the name of the era.
 466        /// </summary>
 467        public int GetEra(string eraName)
 468        {
 0469            ArgumentNullException.ThrowIfNull(eraName);
 470
 471            // The Era Name and Abbreviated Era Name
 472            // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
 473            // would be matched below) but we don't want the empty string to give
 474            // us an Era number
 475            // confer 85900 DTFI.GetEra("") should fail on all cultures
 0476            if (eraName.Length == 0)
 477            {
 0478                return -1;
 479            }
 480
 481            // The following is based on the assumption that the era value is starting from 1, and has a
 482            // serial values.
 483            // If that ever changes, the code has to be changed.
 484
 485            // The calls to String.Compare should use the current culture for the string comparisons, but the
 486            // invariant culture when comparing against the english names.
 0487            for (int i = 0; i < EraNames.Length; i++)
 488            {
 489                // Compare the era name in a case-insensitive way for the appropriate culture.
 0490                if (m_eraNames![i].Length > 0)
 491                {
 0492                    if (Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0)
 493                    {
 0494                        return i + 1;
 495                    }
 496                }
 497            }
 0498            for (int i = 0; i < AbbreviatedEraNames.Length; i++)
 499            {
 500                // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
 0501                if (Culture.CompareInfo.Compare(eraName, m_abbrevEraNames![i], CompareOptions.IgnoreCase) == 0)
 502                {
 0503                    return i + 1;
 504                }
 505            }
 0506            for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
 507            {
 508                // this comparison should use the InvariantCulture.  The English name could have linguistically
 509                // interesting characters.
 0510                if (CompareInfo.Invariant.Compare(eraName, m_abbrevEnglishEraNames![i], CompareOptions.IgnoreCase) == 0)
 511                {
 0512                    return i + 1;
 513                }
 514            }
 0515            return -1;
 516        }
 517
 0518        internal string[] EraNames => m_eraNames ??= _cultureData.EraNames(Calendar.ID);
 519
 520        /// <summary>
 521        /// Get the name of the era for the specified era value.
 522        /// Era names are 1 indexed
 523        /// </summary>
 524        public string GetEraName(int era)
 525        {
 0526            if (era == Calendar.CurrentEra)
 527            {
 0528                era = Calendar.CurrentEraValue;
 529            }
 530
 531            // The following is based on the assumption that the era value is starting from 1, and has a
 532            // serial values. If that ever changes, the code has to be changed.
 0533            string[] names = EraNames;
 0534            era--;
 0535            if ((uint)era >= names.Length)
 536            {
 0537                throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue);
 538            }
 539
 0540            return names[era];
 541        }
 542
 0543        internal string[] AbbreviatedEraNames => m_abbrevEraNames ??= _cultureData.AbbrevEraNames(Calendar.ID);
 544
 545        /// <remarks>
 546        /// Era names are 1 indexed
 547        /// </remarks>
 548        public string GetAbbreviatedEraName(int era)
 549        {
 0550            if (AbbreviatedEraNames.Length == 0)
 551            {
 552                // If abbreviation era name is not used in this culture,
 553                // return the full era name.
 0554                return GetEraName(era);
 555            }
 556
 0557            if (era == Calendar.CurrentEra)
 558            {
 0559                era = Calendar.CurrentEraValue;
 560            }
 561
 0562            string[] names = m_abbrevEraNames!;
 0563            era--;
 0564            if ((uint)era >= (uint)names.Length)
 565            {
 0566                throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue);
 567            }
 568
 0569            return names[era];
 570        }
 571
 572        internal string[] AbbreviatedEnglishEraNames
 573        {
 574            get
 575            {
 0576                if (m_abbrevEnglishEraNames == null)
 577                {
 0578                    Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID 
 0579                    m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
 580                }
 0581                return m_abbrevEnglishEraNames;
 582            }
 583        }
 584
 585        /// <remarks>
 586        /// Note that cultureData derives this from the short date format (unless someone's set this previously)
 587        /// Note that this property is quite undesirable.
 588        /// </remarks>
 589        public string DateSeparator
 590        {
 591            get
 592            {
 0593                dateSeparator ??= _cultureData.DateSeparator(Calendar.ID);
 0594                Debug.Assert(dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
 0595                return dateSeparator;
 596            }
 597            set
 598            {
 0599                if (IsReadOnly)
 600                {
 0601                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 602                }
 0603                ArgumentNullException.ThrowIfNull(value);
 604
 0605                ClearTokenHashTable();
 0606                dateSeparator = value;
 0607                dateSeparatorUtf8 = null;
 0608            }
 609        }
 610
 611        internal ReadOnlySpan<TChar> DateSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar>
 612        {
 0613            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 0614            return typeof(TChar) == typeof(char) ?
 0615                Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(DateSeparator) :
 0616                Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(dateSeparatorUtf8 ??= Encoding.UTF8.GetBytes(Dat
 617        }
 618
 619        public DayOfWeek FirstDayOfWeek
 620        {
 621            get
 622            {
 0623                if (firstDayOfWeek == -1)
 624                {
 0625                    firstDayOfWeek = _cultureData.FirstDayOfWeek;
 626                }
 0627                Debug.Assert(firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
 628
 0629                return (DayOfWeek)firstDayOfWeek;
 630            }
 631            set
 632            {
 0633                if (IsReadOnly)
 634                {
 0635                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 636                }
 637
 0638                if (value < DayOfWeek.Sunday || value > DayOfWeek.Saturday)
 639                {
 0640                    throw new ArgumentOutOfRangeException(
 0641                        nameof(value),
 0642                        value,
 0643                        SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday));
 644                }
 645
 0646                firstDayOfWeek = (int)value;
 0647            }
 648        }
 649
 650        public CalendarWeekRule CalendarWeekRule
 651        {
 652            get
 653            {
 0654                if (calendarWeekRule == -1)
 655                {
 0656                    calendarWeekRule = _cultureData.CalendarWeekRule;
 657                }
 658
 0659                Debug.Assert(calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
 0660                return (CalendarWeekRule)calendarWeekRule;
 661            }
 662            set
 663            {
 0664                if (IsReadOnly)
 665                {
 0666                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 667                }
 0668                if (value < CalendarWeekRule.FirstDay || value > CalendarWeekRule.FirstFourDayWeek)
 669                {
 0670                    throw new ArgumentOutOfRangeException(
 0671                        nameof(value),
 0672                        value,
 0673                        SR.Format(SR.ArgumentOutOfRange_Range, CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayW
 674                }
 675
 0676                calendarWeekRule = (int)value;
 0677            }
 678        }
 679
 680        public string FullDateTimePattern
 681        {
 0682            get => fullDateTimePattern ??= LongDatePattern + " " + LongTimePattern;
 683            set
 684            {
 0685                if (IsReadOnly)
 686                {
 0687                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 688                }
 0689                ArgumentNullException.ThrowIfNull(value);
 690
 0691                fullDateTimePattern = value;
 0692            }
 693        }
 694
 695        /// <summary>
 696        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 697        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 698        /// The string contains the default pattern.
 699        /// When we initially construct our string[], we set the string to string[0]
 700        /// </summary>
 701        public string LongDatePattern
 702        {
 0703            get => longDatePattern ??= UnclonedLongDatePatterns[0]; // initialize from the 1st array value if not set
 704            set
 705            {
 0706                if (IsReadOnly)
 707                {
 0708                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 709                }
 0710                ArgumentNullException.ThrowIfNull(value);
 711
 712                // Remember the new string
 0713                longDatePattern = value;
 714
 0715                OnLongDatePatternChanged();
 0716            }
 717        }
 718
 719        private void OnLongDatePatternChanged()
 720        {
 721            // Clear the token hash table
 0722            ClearTokenHashTable();
 723
 724            // Clean up cached values that will be affected by this property.
 0725            fullDateTimePattern = null;
 0726        }
 727
 728        /// <summary>
 729        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 730        ///
 731        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 732        /// The string contains the default pattern.
 733        /// When we initially construct our string[], we set the string to string[0]
 734        /// </summary>
 735        public string LongTimePattern
 736        {
 0737            get => longTimePattern ??= UnclonedLongTimePatterns[0]; // initialize from the 1st array value if not set
 738            set
 739            {
 0740                if (IsReadOnly)
 741                {
 0742                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 743                }
 0744                ArgumentNullException.ThrowIfNull(value);
 745
 746                // Remember the new string
 0747                longTimePattern = value;
 748
 0749                OnLongTimePatternChanged();
 0750            }
 751        }
 752
 753        private void OnLongTimePatternChanged()
 754        {
 755            // Clear the token hash table
 0756            ClearTokenHashTable();
 757
 758            // Clean up cached values that will be affected by this property.
 0759            fullDateTimePattern = null;     // Full date = long date + long Time
 0760            generalLongTimePattern = null;  // General long date = short date + long Time
 0761            dateTimeOffsetPattern = null;
 0762        }
 763
 764        /// <remarks>
 765        /// Just to be confusing there's only 1 month day pattern, not a whole list
 766        /// </remarks>
 767        public string MonthDayPattern
 768        {
 769            get
 770            {
 0771                if (monthDayPattern == null)
 772                {
 0773                    Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
 0774                    monthDayPattern = _cultureData.MonthDay(Calendar.ID);
 775                }
 776
 0777                Debug.Assert(monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
 0778                return monthDayPattern;
 779            }
 780            set
 781            {
 0782                if (IsReadOnly)
 783                {
 0784                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 785                }
 0786                ArgumentNullException.ThrowIfNull(value);
 787
 0788                monthDayPattern = value;
 0789            }
 790        }
 791
 792        public string PMDesignator
 793        {
 794            get
 795            {
 0796                pmDesignator ??= _cultureData.PMDesignator;
 0797                Debug.Assert(pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
 0798                return pmDesignator;
 799            }
 800            set
 801            {
 0802                if (IsReadOnly)
 803                {
 0804                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 805                }
 0806                ArgumentNullException.ThrowIfNull(value);
 807
 0808                ClearTokenHashTable();
 0809                pmDesignator = value;
 0810                pmDesignatorUtf8 = null;
 0811            }
 812        }
 813
 814        internal ReadOnlySpan<TChar> PMDesignatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar>
 815        {
 0816            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 0817            return typeof(TChar) == typeof(char) ?
 0818                Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(PMDesignator) :
 0819                Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(pmDesignatorUtf8 ??= Encoding.UTF8.GetBytes(PMDe
 820        }
 821
 0822        public string RFC1123Pattern => rfc1123Pattern;
 823
 824        /// <summary>
 825        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 826        ///
 827        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 828        /// The string contains the default pattern.
 829        /// When we initially construct our string[], we set the string to string[0]
 830        /// </summary>
 831        public string ShortDatePattern
 832        {
 0833            get => shortDatePattern ??= UnclonedShortDatePatterns[0]; // initialize from the 1st array value if not set
 834            set
 835            {
 0836                if (IsReadOnly)
 837                {
 0838                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 839                }
 0840                ArgumentNullException.ThrowIfNull(value);
 841
 842                // Remember the new string
 0843                shortDatePattern = value;
 844
 0845                OnShortDatePatternChanged();
 0846            }
 847        }
 848
 849        private void OnShortDatePatternChanged()
 850        {
 851            // Clear the token hash table, note that even short dates could require this
 0852            ClearTokenHashTable();
 853
 854            // Clean up cached values that will be affected by this property.
 0855            generalLongTimePattern = null;   // General long time = short date + long time
 0856            generalShortTimePattern = null;  // General short time = short date + short Time
 0857            dateTimeOffsetPattern = null;
 0858        }
 859
 860        /// <summary>
 861        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 862        ///
 863        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 864        /// The string contains the default pattern.
 865        /// When we initially construct our string[], we set the string to string[0]
 866        /// </summary>
 867        public string ShortTimePattern
 868        {
 0869            get => shortTimePattern ??= UnclonedShortTimePatterns[0]; // initialize from the 1st array value if not set
 870            set
 871            {
 0872                if (IsReadOnly)
 873                {
 0874                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 875                }
 0876                ArgumentNullException.ThrowIfNull(value);
 877
 878                // Remember the new string
 0879                shortTimePattern = value;
 880
 0881                OnShortTimePatternChanged();
 0882            }
 883        }
 884
 885        private void OnShortTimePatternChanged()
 886        {
 887            // Clear the token hash table, note that even short times could require this
 0888            ClearTokenHashTable();
 889
 890            // Clean up cached values that will be affected by this property.
 0891            generalShortTimePattern = null; // General short date = short date + short time.
 0892        }
 893
 0894        public string SortableDateTimePattern => sortableDateTimePattern;
 895
 896        /// <summary>
 897        /// Return the pattern for 'g' general format: shortDate + short time
 898        /// This is used by DateTimeFormat.cs to get the pattern for 'g'.
 899        /// We put this internal property here so that we can avoid doing the
 900        /// concatation every time somebody asks for the general format.
 901        /// </summary>
 0902        internal string GeneralShortTimePattern => generalShortTimePattern ??= ShortDatePattern + " " + ShortTimePattern
 903
 904        /// <summary>
 905        /// Return the pattern for 'G' general format: shortDate + Long time.
 906        /// We put this internal property here so that we can avoid doing the
 907        /// concatation every time somebody asks for the general format.
 908        /// </summary>
 0909        internal string GeneralLongTimePattern => generalLongTimePattern ??= ShortDatePattern + " " + LongTimePattern;
 910
 911        /// Return the default pattern DateTimeOffset : shortDate + long time + time zone offset.
 912        /// This is used by DateTimeFormat.cs to get the pattern for short Date + long time +  time zone offset
 913        /// We put this internal property here so that we can avoid doing the
 914        /// concatation every time somebody uses this form.
 915        internal string DateTimeOffsetPattern
 916        {
 917            get
 918            {
 0919                if (dateTimeOffsetPattern == null)
 920                {
 921                    /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to a
 922
 0923                    bool foundZ = false;
 0924                    bool inQuote = false;
 0925                    char quote = '\'';
 0926                    string longTimePattern = LongTimePattern;
 0927                    for (int i = 0; !foundZ && i < longTimePattern.Length; i++)
 928                    {
 0929                        switch (longTimePattern[i])
 930                        {
 931                            case 'z':
 932                                /* if we aren't in a quote, we've found a z */
 0933                                foundZ = !inQuote;
 934                                /* we'll fall out of the loop now because the test includes !foundZ */
 0935                                break;
 936                            case '\'':
 937                            case '\"':
 0938                                if (inQuote && (quote == longTimePattern[i]))
 939                                {
 940                                    /* we were in a quote and found a matching exit quote, so we are outside a quote now
 0941                                    inQuote = false;
 942                                }
 0943                                else if (!inQuote)
 944                                {
 0945                                    quote = longTimePattern[i];
 0946                                    inQuote = true;
 947                                }
 948                                else
 949                                {
 950                                    /* we were in a quote and saw the other type of quote character, so we are still in 
 951                                }
 952                                break;
 953                            case '%':
 954                            case '\\':
 0955                                i++; /* skip next character that is escaped by this backslash */
 956                                break;
 957                            default:
 958                                break;
 959                        }
 960                    }
 961
 0962                    dateTimeOffsetPattern = foundZ ?
 0963                        ShortDatePattern + " " + longTimePattern :
 0964                        ShortDatePattern + " " + longTimePattern + " zzz";
 965                }
 0966                return dateTimeOffsetPattern;
 967            }
 968        }
 969
 970        /// <remarks>
 971        /// Note that cultureData derives this from the long time format (unless someone's set this previously)
 972        /// Note that this property is quite undesirable.
 973        /// </remarks>
 974        public string TimeSeparator
 975        {
 976            get
 977            {
 0978                timeSeparator ??= _cultureData.TimeSeparator;
 0979                Debug.Assert(timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
 0980                return timeSeparator;
 981            }
 982            set
 983            {
 0984                if (IsReadOnly)
 985                {
 0986                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 987                }
 0988                ArgumentNullException.ThrowIfNull(value);
 989
 0990                ClearTokenHashTable();
 0991                timeSeparator = value;
 0992                timeSeparatorUtf8 = null;
 0993            }
 994        }
 995
 996        internal ReadOnlySpan<TChar> TimeSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar>
 997        {
 0998            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 0999            return typeof(TChar) == typeof(char) ?
 01000                Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(TimeSeparator) :
 01001                Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(timeSeparatorUtf8 ??= Encoding.UTF8.GetBytes(Tim
 1002        }
 1003
 01004        public string UniversalSortableDateTimePattern => universalSortableDateTimePattern;
 1005
 1006        /// <summary>
 1007        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 1008        ///
 1009        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 1010        /// The string contains the default pattern.
 1011        /// When we initially construct our string[], we set the string to string[0]
 1012        /// </summary>
 1013        public string YearMonthPattern
 1014        {
 01015            get => yearMonthPattern ??= UnclonedYearMonthPatterns[0]; // initialize from the 1st array value if not set
 1016            set
 1017            {
 01018                if (IsReadOnly)
 1019                {
 01020                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1021                }
 01022                ArgumentNullException.ThrowIfNull(value);
 1023
 1024                // Remember the new string
 01025                yearMonthPattern = value;
 1026
 01027                OnYearMonthPatternChanged();
 01028            }
 1029        }
 1030
 1031        private void OnYearMonthPatternChanged()
 1032        {
 1033            // Clear the token hash table, note that even short times could require this
 01034            ClearTokenHashTable();
 01035        }
 1036
 1037        /// <summary>
 1038        /// Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
 1039        /// </summary>
 1040        private static void CheckNullValue(string[] values, int length)
 1041        {
 01042            Debug.Assert(values != null, "value != null");
 01043            Debug.Assert(values.Length >= length);
 01044            for (int i = 0; i < length; i++)
 1045            {
 01046                if (values[i] == null)
 1047                {
 01048                    throw new ArgumentNullException("value", SR.ArgumentNull_ArrayValue);
 1049                }
 1050            }
 01051        }
 1052
 1053        public string[] AbbreviatedDayNames
 1054        {
 01055            get => (string[])InternalGetAbbreviatedDayOfWeekNames().Clone();
 1056            set
 1057            {
 01058                if (IsReadOnly)
 1059                {
 01060                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1061                }
 01062                ArgumentNullException.ThrowIfNull(value);
 01063                if (value.Length != 7)
 1064                {
 01065                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
 1066                }
 1067
 01068                CheckNullValue(value, value.Length);
 01069                ClearTokenHashTable();
 1070
 01071                abbreviatedDayNames = value;
 01072            }
 1073        }
 1074
 1075        /// <summary>
 1076        /// Returns the string array of the one-letter day of week names.
 1077        /// </summary>
 1078        public string[] ShortestDayNames
 1079        {
 01080            get => (string[])InternalGetSuperShortDayNames().Clone();
 1081            set
 1082            {
 01083                if (IsReadOnly)
 1084                {
 01085                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1086                }
 01087                ArgumentNullException.ThrowIfNull(value);
 01088                if (value.Length != 7)
 1089                {
 01090                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
 1091                }
 1092
 01093                CheckNullValue(value, value.Length);
 01094                m_superShortDayNames = value;
 01095            }
 1096        }
 1097
 1098        public string[] DayNames
 1099        {
 01100            get => (string[])InternalGetDayOfWeekNames().Clone();
 1101            set
 1102            {
 01103                if (IsReadOnly)
 1104                {
 01105                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1106                }
 01107                ArgumentNullException.ThrowIfNull(value);
 01108                if (value.Length != 7)
 1109                {
 01110                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
 1111                }
 1112
 01113                CheckNullValue(value, value.Length);
 01114                ClearTokenHashTable();
 1115
 01116                dayNames = value;
 01117            }
 1118        }
 1119
 1120        public string[] AbbreviatedMonthNames
 1121        {
 01122            get => (string[])InternalGetAbbreviatedMonthNames().Clone();
 1123            set
 1124            {
 01125                if (IsReadOnly)
 1126                {
 01127                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1128                }
 01129                ArgumentNullException.ThrowIfNull(value);
 01130                if (value.Length != 13)
 1131                {
 01132                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
 1133                }
 1134
 01135                CheckNullValue(value, value.Length - 1);
 01136                ClearTokenHashTable();
 01137                abbreviatedMonthNames = value;
 01138            }
 1139        }
 1140
 1141        public string[] MonthNames
 1142        {
 01143            get => (string[])InternalGetMonthNames().Clone();
 1144            set
 1145            {
 01146                if (IsReadOnly)
 1147                {
 01148                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1149                }
 01150                ArgumentNullException.ThrowIfNull(value);
 01151                if (value.Length != 13)
 1152                {
 01153                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
 1154                }
 1155
 01156                CheckNullValue(value, value.Length - 1);
 01157                monthNames = value;
 01158                ClearTokenHashTable();
 01159            }
 1160        }
 1161
 01162        internal bool HasSpacesInMonthNames => (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
 1163
 01164        internal bool HasSpacesInDayNames => (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
 1165
 1166        /// <summary>
 1167        /// Return the month name using the specified MonthNameStyles in either abbreviated form
 1168        /// or full form.
 1169        /// </summary>
 1170        internal string InternalGetMonthName(int month, MonthNameStyles style, bool abbreviated)
 1171        {
 01172            string[] monthNamesArray = style switch
 01173            {
 01174                MonthNameStyles.Genitive => InternalGetGenitiveMonthNames(abbreviated),
 01175                MonthNameStyles.LeapYear => InternalGetLeapYearMonthNames(),
 01176                _ => (abbreviated ? InternalGetAbbreviatedMonthNames() : InternalGetMonthNames()),
 01177            };
 1178
 1179            // The month range is from 1 ~ m_monthNames.Length
 1180            // (actually is 13 right now for all cases)
 01181            month--;
 01182            if ((uint)month >= (uint)monthNamesArray.Length)
 1183            {
 01184                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, monthNamesArray.Length);
 1185            }
 1186
 01187            return monthNamesArray[month];
 1188        }
 1189
 1190        /// <summary>
 1191        /// Retrieve the array which contains the month names in genitive form.
 1192        /// If this culture does not use the genitive form, the normal month name is returned.
 1193        /// </summary>
 1194        internal string[] InternalGetGenitiveMonthNames(bool abbreviated)
 1195        {
 01196            if (abbreviated)
 1197            {
 01198                if (m_genitiveAbbreviatedMonthNames == null)
 1199                {
 01200                    m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(Calendar.ID);
 01201                    Debug.Assert(m_genitiveAbbreviatedMonthNames.Length == 13,
 01202                        "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a ye
 1203                }
 1204
 01205                return m_genitiveAbbreviatedMonthNames;
 1206            }
 1207
 01208            if (genitiveMonthNames == null)
 1209            {
 01210                genitiveMonthNames = _cultureData.GenitiveMonthNames(Calendar.ID);
 01211                Debug.Assert(genitiveMonthNames.Length == 13,
 01212                    "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
 1213            }
 1214
 01215            return genitiveMonthNames;
 1216        }
 1217
 1218        /// <summary>
 1219        /// Retrieve the month names used in a leap year.
 1220        /// If this culture does not have different month names in a leap year,
 1221        /// the normal month name is returned.
 1222        /// </summary>
 1223        internal string[] InternalGetLeapYearMonthNames()
 1224        {
 01225            if (leapYearMonthNames == null)
 1226            {
 01227                Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.InternalGetLeapYearMonthNames] Expected Calendar.ID >
 01228                leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID);
 01229                Debug.Assert(leapYearMonthNames.Length == 13,
 01230                    "[DateTimeFormatInfo.InternalGetLeapYearMonthNames] Expepcted 13 leap year month names");
 1231            }
 1232
 01233            return leapYearMonthNames;
 1234        }
 1235
 1236        public string GetAbbreviatedDayName(DayOfWeek dayofweek)
 1237        {
 01238            string[] names = InternalGetAbbreviatedDayOfWeekNames(); // Use the internal method to avoid a clone.
 01239            int dow = (int)dayofweek;
 01240            if ((uint)dow >= (uint)names.Length)
 1241            {
 01242                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Satu
 1243            }
 1244
 01245            return names[dow];
 1246        }
 1247
 1248        /// <summary>
 1249        /// Returns the super short day of week names for the specified day of week.
 1250        /// </summary>
 1251        public string GetShortestDayName(DayOfWeek dayOfWeek)
 1252        {
 01253            string[] names = InternalGetSuperShortDayNames();
 01254            int dow = (int)dayOfWeek;
 01255            if ((uint)dow >= (uint)names.Length)
 1256            {
 01257                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayOfWeek), dayOfWeek, DayOfWeek.Sunday, DayOfWeek.Satu
 1258            }
 1259
 01260            return names[dow];
 1261        }
 1262
 1263        /// <summary>
 1264        /// Get all possible combination of inputs
 1265        /// </summary>
 1266        private static string[] GetCombinedPatterns(string[] patterns1, string[] patterns2, string connectString)
 1267        {
 01268            Debug.Assert(patterns1 != null);
 01269            Debug.Assert(patterns2 != null);
 1270
 1271            // Get array size
 01272            string[] result = new string[patterns1.Length * patterns2.Length];
 1273
 1274            // Counter of actual results
 01275            int k = 0;
 01276            for (int i = 0; i < patterns1.Length; i++)
 1277            {
 01278                for (int j = 0; j < patterns2.Length; j++)
 1279                {
 1280                    // Can't combine if null or empty
 01281                    result[k++] = patterns1[i] + connectString + patterns2[j];
 1282                }
 1283            }
 1284
 1285            // Return the combinations
 01286            return result;
 1287        }
 1288
 1289        public string[] GetAllDateTimePatterns()
 1290        {
 01291            List<string> results = new List<string>(DEFAULT_ALL_DATETIMES_SIZE);
 1292
 01293            foreach (char standardFormat in DateTimeFormat.AllStandardFormats)
 1294            {
 01295                foreach (string pattern in GetAllDateTimePatterns(standardFormat))
 1296                {
 01297                    results.Add(pattern);
 1298                }
 1299            }
 1300
 01301            return results.ToArray();
 1302        }
 1303
 1304        public string[] GetAllDateTimePatterns(char format) =>
 01305            format switch
 01306            {
 01307                'd' => AllShortDatePatterns,
 01308                'D' => AllLongDatePatterns,
 01309                'f' => GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " "),
 01310                'F' or 'U' => GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " "),
 01311                'g' => GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " "),
 01312                'G' => GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " "),
 01313                'm' or 'M' => [MonthDayPattern],
 01314                'o' or 'O' => [RoundtripFormat],
 01315                'r' or 'R' => [rfc1123Pattern],
 01316                's' => [sortableDateTimePattern],
 01317                't' => AllShortTimePatterns,
 01318                'T' => AllLongTimePatterns,
 01319                'u' => [UniversalSortableDateTimePattern],
 01320                'y' or 'Y' => AllYearMonthPatterns,
 01321                _ => throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format)),
 01322            };
 1323
 1324        public string GetDayName(DayOfWeek dayofweek)
 1325        {
 01326            string[] names = InternalGetDayOfWeekNames(); // Use the internal method so we don't clone the array unneces
 01327            int dow = (int)dayofweek;
 01328            if ((uint)dow >= (uint)names.Length)
 1329            {
 01330                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Satu
 1331            }
 1332
 01333            return names[dow];
 1334        }
 1335
 1336        public string GetAbbreviatedMonthName(int month)
 1337        {
 01338            string[] names = InternalGetAbbreviatedMonthNames(); // Use the internal method so we don't clone the array 
 01339            month--;
 01340            if ((uint)month >= (uint)names.Length)
 1341            {
 01342                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13);
 1343            }
 1344
 01345            return names[month];
 1346        }
 1347
 1348        public string GetMonthName(int month)
 1349        {
 01350            string[] names = InternalGetMonthNames(); // Use the internal method so we don't clone the array unnecessari
 01351            month--;
 01352            if ((uint)month >= (uint)names.Length)
 1353            {
 01354                ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13);
 1355            }
 1356
 01357            return names[month];
 1358        }
 1359
 1360        /// <summary>
 1361        /// For our "patterns" arrays we have 2 variables, a string and a string[]
 1362        ///
 1363        /// The string[] contains the list of patterns, EXCEPT the default may not be included.
 1364        /// The string contains the default pattern.
 1365        /// When we initially construct our string[], we set the string to string[0]
 1366        ///
 1367        /// The resulting [] can get returned to the calling app, so clone it.
 1368        /// </summary>
 1369        private static string[] GetMergedPatterns(string[] patterns, string defaultPattern)
 1370        {
 01371            Debug.Assert(patterns != null && patterns.Length > 0,
 01372                            "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
 01373            Debug.Assert(defaultPattern != null,
 01374                            "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
 1375
 1376            // If the default happens to be the first in the list just return (a cloned) copy
 01377            if (defaultPattern == patterns[0])
 1378            {
 01379                return (string[])patterns.Clone();
 1380            }
 1381
 1382            // We either need a bigger list, or the pattern from the list.
 1383            int i;
 01384            for (i = 0; i < patterns.Length; i++)
 1385            {
 1386                // Stop if we found it
 01387                if (defaultPattern == patterns[i])
 1388                    break;
 1389            }
 1390
 1391            // Either way we're going to need a new array
 1392            string[] newPatterns;
 1393
 1394            // Did we find it
 01395            if (i < patterns.Length)
 1396            {
 1397                // Found it, output will be same size
 01398                newPatterns = (string[])patterns.Clone();
 1399
 1400                // Have to move [0] item to [i] so we can re-write default at [0]
 1401                // (remember defaultPattern == [i] so this is OK)
 01402                newPatterns[i] = newPatterns[0];
 1403            }
 1404            else
 1405            {
 1406                // Not found, make room for it
 01407                newPatterns = new string[patterns.Length + 1];
 1408
 1409                // Copy existing array
 01410                Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
 1411            }
 1412
 1413            // Remember the default
 01414            newPatterns[0] = defaultPattern;
 1415
 1416            // Return the reconstructed list
 01417            return newPatterns;
 1418        }
 1419
 1420        // Needed by DateTimeFormatInfo and DateTimeFormat
 1421        internal const string RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
 1422        internal const string RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
 1423
 1424        /// <summary>
 1425        /// Default string isn't necessarily in our string array, so get the
 1426        /// merged patterns of both
 1427        /// </summary>
 01428        private string[] AllYearMonthPatterns => GetMergedPatterns(UnclonedYearMonthPatterns, YearMonthPattern);
 1429
 01430        private string[] AllShortDatePatterns => GetMergedPatterns(UnclonedShortDatePatterns, ShortDatePattern);
 1431
 01432        private string[] AllShortTimePatterns => GetMergedPatterns(UnclonedShortTimePatterns, ShortTimePattern);
 1433
 01434        private string[] AllLongDatePatterns => GetMergedPatterns(UnclonedLongDatePatterns, LongDatePattern);
 1435
 01436        private string[] AllLongTimePatterns => GetMergedPatterns(UnclonedLongTimePatterns, LongTimePattern);
 1437
 1438        /// <remarks>
 1439        /// Clone this string array if you want to return it to user.
 1440        /// Otherwise, you are returning a writable cache copy.
 1441        /// This won't include default, call AllYearMonthPatterns
 1442        /// </remarks>
 1443        private string[] UnclonedYearMonthPatterns
 1444        {
 1445            get
 1446            {
 01447                if (allYearMonthPatterns == null)
 1448                {
 01449                    Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID >
 01450                    allYearMonthPatterns = _cultureData.YearMonths(Calendar.ID);
 01451                    Debug.Assert(allYearMonthPatterns.Length > 0,
 01452                        "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
 1453                }
 1454
 01455                return allYearMonthPatterns;
 1456            }
 1457        }
 1458
 1459        /// <remarks>
 1460        /// Clone this string array if you want to return it to user.
 1461        /// Otherwise, you are returning a writable cache copy.
 1462        /// This won't include default, call AllShortDatePatterns
 1463        /// </remarks>
 1464        private string[] UnclonedShortDatePatterns
 1465        {
 1466            get
 1467            {
 01468                if (allShortDatePatterns == null)
 1469                {
 01470                    Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID >
 01471                    allShortDatePatterns = _cultureData.ShortDates(Calendar.ID);
 01472                    Debug.Assert(allShortDatePatterns.Length > 0,
 01473                        "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
 1474                }
 1475
 01476                return allShortDatePatterns;
 1477            }
 1478        }
 1479
 1480        /// <remarks>
 1481        /// Clone this string array if you want to return it to user.
 1482        /// Otherwise, you are returning a writable cache copy.
 1483        /// This won't include default, call AllLongDatePatterns
 1484        /// </remarks>
 1485        private string[] UnclonedLongDatePatterns
 1486        {
 1487            get
 1488            {
 01489                if (allLongDatePatterns == null)
 1490                {
 01491                    Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 
 01492                    allLongDatePatterns = _cultureData.LongDates(Calendar.ID);
 01493                    Debug.Assert(allLongDatePatterns.Length > 0,
 01494                        "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
 1495                }
 1496
 01497                return allLongDatePatterns;
 1498            }
 1499        }
 1500
 1501        /// <remarks>
 1502        /// Clone this string array if you want to return it to user.
 1503        /// Otherwise, you are returning a writable cache copy.
 1504        /// This won't include default, call AllShortTimePatterns
 1505        /// </remarks>
 1506        private string[] UnclonedShortTimePatterns
 1507        {
 1508            get
 1509            {
 01510                if (allShortTimePatterns == null)
 1511                {
 01512                    allShortTimePatterns = _cultureData.ShortTimes;
 01513                    Debug.Assert(allShortTimePatterns.Length > 0,
 01514                        "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
 1515                }
 1516
 01517                return allShortTimePatterns;
 1518            }
 1519        }
 1520
 1521        /// <remarks>
 1522        /// Clone this string array if you want to return it to user.
 1523        /// Otherwise, you are returning a writable cache copy.
 1524        /// This won't include default, call AllLongTimePatterns
 1525        /// </remarks>
 1526        private string[] UnclonedLongTimePatterns
 1527        {
 1528            get
 1529            {
 01530                if (allLongTimePatterns == null)
 1531                {
 01532                    allLongTimePatterns = _cultureData.LongTimes;
 01533                    Debug.Assert(allLongTimePatterns.Length > 0,
 01534                        "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
 1535                }
 1536
 01537                return allLongTimePatterns;
 1538            }
 1539        }
 1540
 1541        public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
 1542        {
 01543            ArgumentNullException.ThrowIfNull(dtfi);
 1544
 01545            if (dtfi.IsReadOnly)
 1546            {
 01547                return dtfi;
 1548            }
 1549
 01550            DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
 1551            // We can use the data member calendar in the setter, instead of the property Calendar,
 1552            // since the cloned copy should have the same state as the original copy.
 01553            newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
 01554            newInfo._isReadOnly = true;
 01555            return newInfo;
 1556        }
 1557
 01558        public bool IsReadOnly => _isReadOnly;
 1559
 1560        /// <summary>
 1561        /// Return the native name for the calendar in DTFI.Calendar.  The native name is referred to
 1562        /// the culture used to create the DTFI.  E.g. in the following example, the native language is Japanese.
 1563        /// DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
 1564        /// String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
 1565        /// DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(Gr
 1566        /// String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
 1567        /// </summary>
 01568        public string NativeCalendarName => _cultureData.CalendarName(Calendar.ID);
 1569
 1570        /// <summary>
 1571        /// Used by custom cultures and others to set the list of available formats. Note that none of them are
 1572        /// explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
 1573        /// from the list.
 1574        ///
 1575        /// Most of the format characters that can be used in GetAllDateTimePatterns are
 1576        /// not really needed since they are one of the following:
 1577        ///
 1578        ///  r/R/s/u     locale-independent constants -- cannot be changed!
 1579        ///  m/M/y/Y     fields with a single string in them -- that can be set through props directly
 1580        ///  f/F/g/G/U   derived fields based on combinations of various of the below formats
 1581        ///
 1582        /// NOTE: No special validation is done here beyond what is done when the actual respective fields
 1583        /// are used (what would be the point of disallowing here what we allow in the appropriate property?)
 1584        ///
 1585        /// WARNING: If more validation is ever done in one place, it should be done in the other.
 1586        /// </summary>
 1587        public void SetAllDateTimePatterns(string[] patterns, char format)
 1588        {
 01589            if (IsReadOnly)
 1590            {
 01591                throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1592            }
 01593            ArgumentNullException.ThrowIfNull(patterns);
 1594
 01595            if (patterns.Length == 0)
 1596            {
 01597                throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns));
 1598            }
 1599
 01600            for (int i = 0; i < patterns.Length; i++)
 1601            {
 01602                if (patterns[i] == null)
 1603                {
 01604                    throw new ArgumentNullException(nameof(patterns) + "[" + i + "]", SR.ArgumentNull_ArrayValue);
 1605                }
 1606            }
 1607
 1608            // Remember the patterns, and use the 1st as default
 1609            switch (format)
 1610            {
 1611                case 'd':
 01612                    allShortDatePatterns = patterns;
 01613                    shortDatePattern = allShortDatePatterns[0];
 01614                    OnShortDatePatternChanged();
 01615                    break;
 1616
 1617                case 'D':
 01618                    allLongDatePatterns = patterns;
 01619                    longDatePattern = allLongDatePatterns[0];
 01620                    OnLongDatePatternChanged();
 01621                    break;
 1622
 1623                case 't':
 01624                    allShortTimePatterns = patterns;
 01625                    shortTimePattern = allShortTimePatterns[0];
 01626                    OnShortTimePatternChanged();
 01627                    break;
 1628
 1629                case 'T':
 01630                    allLongTimePatterns = patterns;
 01631                    longTimePattern = allLongTimePatterns[0];
 01632                    OnLongTimePatternChanged();
 01633                    break;
 1634
 1635                case 'y':
 1636                case 'Y':
 01637                    allYearMonthPatterns = patterns;
 01638                    yearMonthPattern = allYearMonthPatterns[0];
 01639                    OnYearMonthPatternChanged();
 01640                    break;
 1641
 1642                default:
 01643                    throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format));
 1644            }
 1645        }
 1646
 1647        public string[] AbbreviatedMonthGenitiveNames
 1648        {
 01649            get => (string[])InternalGetGenitiveMonthNames(true).Clone();
 1650            set
 1651            {
 01652                if (IsReadOnly)
 1653                {
 01654                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1655                }
 01656                ArgumentNullException.ThrowIfNull(value);
 01657                if (value.Length != 13)
 1658                {
 01659                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
 1660                }
 1661
 01662                CheckNullValue(value, value.Length - 1);
 01663                ClearTokenHashTable();
 01664                m_genitiveAbbreviatedMonthNames = value;
 01665            }
 1666        }
 1667
 1668        public string[] MonthGenitiveNames
 1669        {
 01670            get => (string[])InternalGetGenitiveMonthNames(false).Clone();
 1671            set
 1672            {
 01673                if (IsReadOnly)
 1674                {
 01675                    throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
 1676                }
 01677                ArgumentNullException.ThrowIfNull(value);
 01678                if (value.Length != 13)
 1679                {
 01680                    throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
 1681                }
 1682
 01683                CheckNullValue(value, value.Length - 1);
 01684                genitiveMonthNames = value;
 01685                ClearTokenHashTable();
 01686            }
 1687        }
 1688
 1689        // Decimal separator used by positive TimeSpan pattern
 1690        private string? _decimalSeparator;
 1691        internal string DecimalSeparator =>
 01692            _decimalSeparator ??=
 01693            new NumberFormatInfo(_cultureData.UseUserOverride ? CultureData.GetCultureData(_cultureData.CultureName, fal
 1694
 1695        private byte[]? _decimalSeparatorUtf8;
 1696        internal ReadOnlySpan<TChar> DecimalSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar>
 1697        {
 01698            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 01699            return typeof(TChar) == typeof(char) ?
 01700                Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(DecimalSeparator) :
 01701                Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(_decimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes
 1702        }
 1703
 1704        // Positive TimeSpan Pattern
 1705        private string? _fullTimeSpanPositivePattern;
 1706        internal string FullTimeSpanPositivePattern =>
 01707            _fullTimeSpanPositivePattern ??= "d':'h':'mm':'ss'" + DecimalSeparator + "'FFFFFFF";
 1708
 1709        // Negative TimeSpan Pattern
 1710        private string? _fullTimeSpanNegativePattern;
 1711        internal string FullTimeSpanNegativePattern =>
 01712            _fullTimeSpanNegativePattern ??= "'-'" + FullTimeSpanPositivePattern;
 1713
 1714        // Get suitable CompareInfo from current DTFI object.
 1715        internal CompareInfo CompareInfo =>
 1716            // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
 1717            // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData.
 01718            _compareInfo ??= CompareInfo.GetCompareInfo(_cultureData.SortName);
 1719
 1720        internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowT
 1721                                                               | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurre
 1722                                                               | DateTimeStyles.AdjustToUniversal | DateTimeStyles.Assum
 1723                                                               | DateTimeStyles.AssumeUniversal | DateTimeStyles.Roundtr
 1724
 1725        internal static void ValidateStyles(DateTimeStyles style, bool styles = false)
 1726        {
 1727            const DateTimeStyles localUniversal = DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal;
 1728
 01729            if ((style & InvalidDateTimeStyles) != 0 ||
 01730                (style & localUniversal) == localUniversal ||
 01731                (style & (DateTimeStyles.RoundtripKind | localUniversal | DateTimeStyles.AdjustToUniversal)) > DateTimeS
 1732            {
 01733                ThrowInvalid(style, styles);
 1734            }
 1735
 1736            static void ThrowInvalid(DateTimeStyles style, bool styles)
 1737            {
 01738                string message = (style & InvalidDateTimeStyles) != 0 ? SR.Argument_InvalidDateTimeStyles
 01739                    : (style & localUniversal) == localUniversal ? SR.Argument_ConflictingDateTimeStyles
 01740                    : SR.Argument_ConflictingDateTimeRoundtripStyles;
 01741                throw new ArgumentException(message, styles ? nameof(styles) : nameof(style));
 1742            }
 01743        }
 1744
 1745        /// <summary>
 1746        /// Return the internal flag used in formatting and parsing.
 1747        /// The flag can be used to indicate things like if genitive forms is used in
 1748        /// this DTFi, or if leap year gets different month names.
 1749        /// </summary>
 01750        internal DateTimeFormatFlags FormatFlags => formatFlags != DateTimeFormatFlags.NotInitialized ? formatFlags : In
 1751
 1752        [MethodImpl(MethodImplOptions.NoInlining)]
 1753        private DateTimeFormatFlags InitializeFormatFlags()
 1754        {
 1755            // Build the format flags from the data in this DTFI
 01756            formatFlags =
 01757                (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
 01758                    MonthNames, InternalGetGenitiveMonthNames(false), AbbreviatedMonthNames, InternalGetGenitiveMonthNam
 01759                (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
 01760                    MonthNames, InternalGetGenitiveMonthNames(false), AbbreviatedMonthNames, InternalGetGenitiveMonthNam
 01761                (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayN
 01762                (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
 01763            return formatFlags;
 1764        }
 1765
 1766        internal bool HasForceTwoDigitYears =>
 1767            // Handle Japanese and Taiwan cases.
 1768            // If is y/yy, do not get (year % 100). "y" will print
 1769            // year without leading zero.  "yy" will print year with two-digit in leading zero.
 1770            // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
 1771            // So year 5 is "05", and year 125 is "125".
 1772            // The reason for not doing (year % 100) is for Taiwan calendar.
 1773            // If year 125, then output 125 and not 25.
 1774            // Note: OS uses "yyyy" for Taiwan calendar by default.
 01775            calendar.ID is CalendarId.JAPAN or CalendarId.TAIWAN;
 1776
 1777        /// <summary>
 1778        /// Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
 1779        /// </summary>
 01780        internal bool HasYearMonthAdjustment => (FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0;
 1781
 1782        /// <summary>
 1783        /// This is a callback that the parser can make back into the DTFI to let it fiddle with special
 1784        /// cases associated with that culture or calendar. Currently this only has special cases for
 1785        /// the Hebrew calendar, but this could be extended to other cultures.
 1786        ///
 1787        /// The return value is whether the year and month are actually valid for this calendar.
 1788        /// </summary>
 1789        internal bool YearMonthAdjustment(ref int year, ref int month, bool parsedMonthName)
 1790        {
 01791            if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
 1792            {
 1793                // Special rules to fix up the Hebrew year/month
 1794
 1795                // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is n
 1796                // E.g. if the year is 5763, we only format as 763.
 01797                if (year < 1000)
 1798                {
 01799                    year += 5000;
 1800                }
 1801
 1802                // Because we need to calculate leap year, we should fall out now for an invalid year.
 01803                if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSuppor
 1804                {
 01805                    return false;
 1806                }
 1807
 1808                // To handle leap months, the set of month names in the symbol table does not always correspond to the n
 1809                // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid an
 1810                // shuffle the other months down.
 01811                if (parsedMonthName)
 1812                {
 01813                    if (!Calendar.IsLeapYear(year))
 1814                    {
 01815                        if (month >= 8)
 1816                        {
 01817                            month--;
 1818                        }
 01819                        else if (month == 7)
 1820                        {
 01821                            return false;
 1822                        }
 1823                    }
 1824                }
 1825            }
 01826            return true;
 1827        }
 1828
 1829        // DateTimeFormatInfo tokenizer.  This is used by DateTime.Parse() to break input string into tokens.
 1830        private TokenHashValue[]? _dtfiTokenHash;
 1831
 1832        private const int TOKEN_HASH_SIZE = 199;
 1833        private const int SECOND_PRIME = 197;
 1834        private const string dateSeparatorOrTimeZoneOffset = "-";
 1835        private const string invariantDateSeparator = "/";
 1836        private const string invariantTimeSeparator = ":";
 1837
 1838        // Common Ignorable Symbols
 1839        internal const string IgnorablePeriod = ".";
 1840        internal const string IgnorableComma = ",";
 1841
 1842        // Year/Month/Day suffixes
 1843        internal const string CJKYearSuff = "\u5e74";
 1844        internal const string CJKMonthSuff = "\u6708";
 1845        internal const string CJKDaySuff = "\u65e5";
 1846
 1847        internal const string KoreanYearSuff = "\ub144";
 1848        internal const string KoreanMonthSuff = "\uc6d4";
 1849        internal const string KoreanDaySuff = "\uc77c";
 1850
 1851        internal const string KoreanHourSuff = "\uc2dc";
 1852        internal const string KoreanMinuteSuff = "\ubd84";
 1853        internal const string KoreanSecondSuff = "\ucd08";
 1854
 1855        internal const string CJKHourSuff = "\u6642";
 1856        internal const string ChineseHourSuff = "\u65f6";
 1857
 1858        internal const string CJKMinuteSuff = "\u5206";
 1859        internal const string CJKSecondSuff = "\u79d2";
 1860
 1861        internal const string JapaneseEraStart = "\u5143";
 1862
 1863        internal const string LocalTimeMark = "T";
 1864
 1865        internal const string GMTName = "GMT";
 1866        internal const string ZuluName = "Z";
 1867
 1868        internal const string KoreanLangName = "ko";
 1869        internal const string JapaneseLangName = "ja";
 1870        internal const string EnglishLangName = "en";
 1871
 1872        private static DateTimeFormatInfo? s_jajpDTFI;
 1873        private static DateTimeFormatInfo? s_zhtwDTFI;
 1874
 1875        /// <summary>
 1876        /// Create a Japanese DTFI which uses JapaneseCalendar.  This is used to parse
 1877        /// date string with Japanese era name correctly even when the supplied DTFI
 1878        /// does not use Japanese calendar.
 1879        /// The created instance is stored in global s_jajpDTFI.
 1880        /// </summary>
 1881        internal static DateTimeFormatInfo GetJapaneseCalendarDTFI()
 1882        {
 01883            DateTimeFormatInfo? temp = s_jajpDTFI;
 01884            if (temp == null)
 1885            {
 01886                temp = new CultureInfo("ja-JP", false).DateTimeFormat;
 01887                temp.Calendar = JapaneseCalendar.GetDefaultInstance();
 01888                s_jajpDTFI = temp;
 1889            }
 01890            return temp;
 1891        }
 1892
 1893        /// <summary>
 1894        /// Create a Taiwan DTFI which uses TaiwanCalendar.  This is used to parse
 1895        /// date string with era name correctly even when the supplied DTFI
 1896        /// does not use Taiwan calendar.
 1897        /// The created instance is stored in global s_zhtwDTFI.
 1898        /// </summary>
 1899        internal static DateTimeFormatInfo GetTaiwanCalendarDTFI()
 1900        {
 01901            DateTimeFormatInfo? temp = s_zhtwDTFI;
 01902            if (temp == null)
 1903            {
 01904                temp = new CultureInfo("zh-TW", false).DateTimeFormat;
 01905                temp.Calendar = TaiwanCalendar.GetDefaultInstance();
 01906                s_zhtwDTFI = temp;
 1907            }
 01908            return temp;
 1909        }
 1910
 1911        /// <summary>
 1912        /// DTFI properties should call this when the setter are called.
 1913        /// </summary>
 1914        private void ClearTokenHashTable()
 1915        {
 01916            _dtfiTokenHash = null;
 01917            formatFlags = DateTimeFormatFlags.NotInitialized;
 01918        }
 1919
 1920        internal TokenHashValue[] CreateTokenHashTable()
 1921        {
 01922            TokenHashValue[]? temp = _dtfiTokenHash;
 01923            if (temp == null)
 1924            {
 01925                temp = new TokenHashValue[TOKEN_HASH_SIZE];
 1926
 01927                string sep = TimeSeparator.Trim();
 01928                if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
 01929                if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
 1930
 01931                if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep)
 1932                {
 1933                    //
 1934                    // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or C
 1935                    // ja-JP and ko-KR.  In these cases having the same symbol inserted into the hash table with multipl
 1936                    // DateTime.Parse behavior.  For instance, the DateTimeFormatInfo.Tokenize() method might return SEP
 1937                    // instead of SEP_HourSuff.
 1938                    //
 01939                    InsertHash(temp, TimeSeparator, TokenType.SEP_Time, 0);
 1940                }
 1941
 01942                if (_name == "fr-CA")
 1943                {
 01944                    InsertHash(temp, " h", TokenType.SEP_HourSuff, 0);
 01945                    InsertHash(temp, " h ", TokenType.SEP_HourSuff, 0);
 01946                    InsertHash(temp, " min", TokenType.SEP_MinuteSuff, 0);
 01947                    InsertHash(temp, " min ", TokenType.SEP_MinuteSuff, 0);
 01948                    InsertHash(temp, " s", TokenType.SEP_SecondSuff, 0);
 01949                    InsertHash(temp, " s ", TokenType.SEP_SecondSuff, 0);
 1950                }
 1951
 01952                InsertHash(temp, AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
 01953                InsertHash(temp, PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
 1954
 01955                if (LanguageName.Equals("sq"))
 1956                {
 1957                    // Albanian allows time formats like "12:00.PD"
 01958                    InsertHash(temp, IgnorablePeriod + AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
 01959                    InsertHash(temp, IgnorablePeriod + PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
 1960                }
 1961
 1962                // CJK suffix
 01963                InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
 01964                InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
 01965                InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
 01966                InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
 01967                InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
 01968                InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
 1969
 01970                InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
 01971                InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
 01972                InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
 01973                InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
 1974
 01975                if (!LocalAppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN)
 1976                {
 1977                    // We need to support parsing the dates has the start of era symbol which means it is year 1 in the 
 1978                    // The start of era symbol has to be followed by the year symbol suffix, otherwise it would be inval
 01979                    InsertHash(temp, JapaneseEraStart, TokenType.YearNumberToken, 1);
 01980                    InsertHash(temp, "(", TokenType.IgnorableSymbol, 0);
 01981                    InsertHash(temp, ")", TokenType.IgnorableSymbol, 0);
 1982                }
 1983
 1984                // TODO: This ignores other custom cultures that might want to do something similar
 01985                if (LanguageName.Equals(KoreanLangName))
 1986                {
 1987                    // Korean suffix
 01988                    InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
 01989                    InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
 01990                    InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
 1991                }
 1992
 01993                if (LanguageName.Equals("ky"))
 1994                {
 1995                    // For some cultures, the date separator works more like a comma, being allowed before or after any 
 01996                    InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
 1997                }
 1998                else
 1999                {
 02000                    InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
 2001                }
 2002
 2003                // We need to rescan the date words since we're always synthetic
 02004                DateTimeFormatInfoScanner scanner = new DateTimeFormatInfoScanner();
 02005                string[]? dateWords = scanner.GetDateWordsOfDTFI(this);
 2006
 2007                // Ensure the formatflags is initialized.
 02008                _ = FormatFlags;
 2009
 2010                // For some cultures, the date separator works more like a comma, being allowed before or after any date
 2011                // In these cultures, we do not use normal date separator since we disallow date separator after a date 
 2012                // This is determined in DateTimeFormatInfoScanner.  Use this flag to determine if we should treat date 
 02013                bool useDateSepAsIgnorableSymbol = false;
 2014
 02015                if (dateWords != null)
 2016                {
 2017                    // There are DateWords.  It could be a real date word (such as "de"), or a monthPostfix.
 2018                    // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
 02019                    for (int i = 0; i < dateWords.Length; i++)
 2020                    {
 02021                        switch (dateWords[i][0])
 2022                        {
 2023                            // This is a month postfix
 2024                            case DateTimeFormatInfoScanner.MonthPostfixChar:
 2025                                // Get the real month postfix.
 02026                                ReadOnlySpan<char> monthPostfix = dateWords[i].AsSpan(1);
 2027                                // Add the month name + postfix into the token.
 02028                                AddMonthNames(temp, monthPostfix);
 02029                                break;
 2030                            case DateTimeFormatInfoScanner.IgnorableSymbolChar:
 02031                                string symbol = dateWords[i].Substring(1);
 02032                                InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
 02033                                if (DateSeparator.Trim().Equals(symbol))
 2034                                {
 2035                                    // The date separator is the same as the ignorable symbol.
 02036                                    useDateSepAsIgnorableSymbol = true;
 2037                                }
 02038                                break;
 2039                            default:
 02040                                InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
 2041                                // TODO: This ignores similar custom cultures
 02042                                if (LanguageName.Equals("eu"))
 2043                                {
 2044                                    // Basque has date words with leading dots
 02045                                    InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
 2046                                }
 2047                                break;
 2048                        }
 2049                    }
 2050                }
 2051
 02052                if (!useDateSepAsIgnorableSymbol)
 2053                {
 2054                    // Use the normal date separator.
 02055                    InsertHash(temp, DateSeparator, TokenType.SEP_Date, 0);
 2056                }
 2057                // Add the regular month names.
 02058                AddMonthNames(temp);
 2059
 2060                // Add the abbreviated month names.
 02061                for (int i = 1; i <= 13; i++)
 2062                {
 02063                    InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
 2064                }
 2065
 02066                if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
 2067                {
 02068                    string[] genitiveMonthNames = InternalGetGenitiveMonthNames(abbreviated: false);
 02069                    string[] abbreviatedGenitiveMonthNames = InternalGetGenitiveMonthNames(abbreviated: true);
 2070
 02071                    for (int i = 1; i <= 13; i++)
 2072                    {
 02073                        InsertHash(temp, genitiveMonthNames[i - 1], TokenType.MonthToken, i);
 02074                        InsertHash(temp, abbreviatedGenitiveMonthNames[i - 1], TokenType.MonthToken, i);
 2075                    }
 2076                }
 2077
 02078                if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
 2079                {
 02080                    for (int i = 1; i <= 13; i++)
 2081                    {
 02082                        string str = InternalGetMonthName(i, MonthNameStyles.LeapYear, false);
 02083                        InsertHash(temp, str, TokenType.MonthToken, i);
 2084                    }
 2085                }
 2086
 02087                for (int i = 0; i < 7; i++)
 2088                {
 2089                    // We have to call public methods here to work with inherited DTFI.
 02090                    string str = GetDayName((DayOfWeek)i);
 02091                    InsertHash(temp, str, TokenType.DayOfWeekToken, i);
 2092
 02093                    str = GetAbbreviatedDayName((DayOfWeek)i);
 02094                    InsertHash(temp, str, TokenType.DayOfWeekToken, i);
 2095                }
 2096
 02097                int[] eras = calendar.Eras;
 02098                for (int i = 1; i <= eras.Length; i++)
 2099                {
 02100                    InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
 02101                    InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
 2102                }
 2103
 02104                if (!GlobalizationMode.Invariant)
 2105                {
 02106                    if (LanguageName.Equals(JapaneseLangName))
 2107                    {
 2108                        // Japanese allows day of week forms like: "(Tue)"
 02109                        for (int i = 0; i < 7; i++)
 2110                        {
 02111                            string specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
 02112                            InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
 2113                        }
 02114                        if (Calendar.GetType() != typeof(JapaneseCalendar))
 2115                        {
 2116                            // Special case for Japanese.  If this is a Japanese DTFI, and the calendar is not Japanese 
 2117                            // we will check Japanese Era name as well when the calendar is Gregorian.
 02118                            DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
 02119                            for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++)
 2120                            {
 02121                                InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
 02122                                InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
 2123                                // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
 02124                                InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i
 2125                            }
 2126                        }
 2127                    }
 2128                    // TODO: This prohibits similar custom cultures, but we hard coded the name
 02129                    else if (CultureName.Equals("zh-TW"))
 2130                    {
 02131                        DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
 02132                        for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++)
 2133                        {
 02134                            if (twDtfi.GetEraName(i).Length > 0)
 2135                            {
 02136                                InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
 2137                            }
 2138                        }
 2139                    }
 2140                }
 2141
 02142                InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
 02143                InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
 2144
 2145                // Add invariant month names and day names.
 02146                for (int i = 1; i <= 12; i++)
 2147                {
 2148                    // We have to call public methods here to work with inherited DTFI.
 2149                    // Insert the month name first, so that they are at the front of abbreviated
 2150                    // month names.
 02151                    string str = InvariantInfo.GetMonthName(i);
 02152                    InsertHash(temp, str, TokenType.MonthToken, i);
 02153                    str = InvariantInfo.GetAbbreviatedMonthName(i);
 02154                    InsertHash(temp, str, TokenType.MonthToken, i);
 2155                }
 2156
 02157                for (int i = 0; i < 7; i++)
 2158                {
 2159                    // We have to call public methods here to work with inherited DTFI.
 02160                    string str = InvariantInfo.GetDayName((DayOfWeek)i);
 02161                    InsertHash(temp, str, TokenType.DayOfWeekToken, i);
 2162
 02163                    str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
 02164                    InsertHash(temp, str, TokenType.DayOfWeekToken, i);
 2165                }
 2166
 02167                for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
 2168                {
 2169                    // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
 02170                    InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
 2171                }
 2172
 02173                InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
 02174                InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0);
 02175                InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0);
 2176
 02177                InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
 02178                InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
 2179
 02180                _dtfiTokenHash = temp;
 2181            }
 02182            return temp;
 2183        }
 2184
 2185        private void AddMonthNames(TokenHashValue[] temp, ReadOnlySpan<char> monthPostfix = default)
 2186        {
 02187            for (int i = 1; i <= 13; i++)
 2188            {
 2189                // We have to call public methods here to work with inherited DTFI.
 2190                // Insert the month name first, so that they are at the front of abbreviated
 2191                // month names.
 02192                string str = GetMonthName(i);
 02193                if (str.Length > 0)
 2194                {
 02195                    if (!monthPostfix.IsEmpty)
 2196                    {
 2197                        // Insert the month name with the postfix first, so it can be matched first.
 02198                        InsertHash(temp, string.Concat(str, monthPostfix), TokenType.MonthToken, i);
 2199                    }
 2200                    else
 2201                    {
 02202                        InsertHash(temp, str, TokenType.MonthToken, i);
 2203                    }
 2204                }
 02205                str = GetAbbreviatedMonthName(i);
 02206                InsertHash(temp, str, TokenType.MonthToken, i);
 2207            }
 02208        }
 2209
 2210        /// <summary>
 2211        /// Try to parse the current word to see if it is a Hebrew number.
 2212        /// Tokens will be updated accordingly.
 2213        /// This is called by the Lexer of DateTime.Parse().
 2214        ///
 2215        /// Unlike most of the functions in this class, the return value indicates
 2216        /// whether or not it started to parse. The badFormat parameter indicates
 2217        /// if parsing began, but the format was bad.
 2218        /// </summary>
 2219        private static bool TryParseHebrewNumber(
 2220            ref __DTString str,
 2221            out bool badFormat,
 2222            out int number)
 2223        {
 02224            number = -1;
 02225            badFormat = false;
 2226
 02227            int i = str.Index;
 02228            if (!HebrewNumber.IsDigit(str.Value[i]))
 2229            {
 2230                // If the current character is not a Hebrew digit, just return false.
 2231                // There is no chance that we can parse a valid Hebrew number from here.
 02232                return false;
 2233            }
 2234            // The current character is a Hebrew digit.  Try to parse this word as a Hebrew number.
 02235            HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
 2236            HebrewNumberParsingState state;
 2237
 2238            do
 2239            {
 02240                state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
 2241                switch (state)
 2242                {
 2243                    case HebrewNumberParsingState.InvalidHebrewNumber:    // Not a valid Hebrew number.
 2244                    case HebrewNumberParsingState.NotHebrewDigit:         // The current character is not a Hebrew digit
 2245                        // Break out so that we don't continue to try parse this as a Hebrew number.
 02246                        return false;
 2247                }
 02248            } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
 2249
 2250            // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
 02251            Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEnd
 02252                "Invalid returned state from HebrewNumber.ParseByChar()");
 2253
 02254            if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)
 2255            {
 2256                // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
 02257                return false;
 2258            }
 2259
 2260            // We have found a valid Hebrew number.  Update the index.
 02261            str.Advance(i - str.Index);
 2262
 2263            // Get the final Hebrew number value from the HebrewNumberParsingContext.
 02264            number = context.result;
 2265
 02266            return true;
 2267        }
 2268
 2269        private static bool IsHebrewChar(char ch)
 2270        {
 02271            return ch >= '\x0590' && ch <= '\x05ff';
 2272        }
 2273
 2274        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2275        private bool IsAllowedJapaneseTokenFollowedByNonSpaceLetter(string tokenString, char nextCh)
 2276        {
 2277            // Allow the parser to recognize the case when having some date part followed by JapaneseEraStart "\u5143"
 2278            // without spaces in between. e.g. Era name followed by \u5143 in the date formats ggy.
 2279            // Also, allow recognizing the year suffix symbol "\u5e74" followed the JapaneseEraStart "\u5143"
 02280            if (!LocalAppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN &&
 02281                (
 02282                    // something like ggy, era followed by year and the year is specified using the JapaneseEraStart "\u
 02283                    nextCh == JapaneseEraStart[0] ||
 02284                    // JapaneseEraStart followed by year suffix "\u5143"
 02285                    (tokenString == JapaneseEraStart && nextCh == CJKYearSuff[0])
 02286                ))
 2287            {
 02288                return true;
 2289            }
 02290            return false;
 2291        }
 2292
 2293        internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
 2294                               ref __DTString str)
 2295        {
 02296            tokenType = TokenType.UnknownToken;
 02297            tokenValue = 0;
 2298
 2299            TokenHashValue value;
 02300            Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
 2301
 02302            char ch = str.m_current;
 02303            bool isLetter = char.IsLetter(ch);
 02304            if (isLetter)
 2305            {
 02306                ch = Culture.TextInfo.ToLower(ch);
 02307                if (!GlobalizationMode.Invariant && IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask)
 2308                {
 02309                    if (TryParseHebrewNumber(ref str, out bool badFormat, out tokenValue))
 2310                    {
 02311                        if (badFormat)
 2312                        {
 02313                            tokenType = TokenType.UnknownToken;
 02314                            return false;
 2315                        }
 2316                        // This is a Hebrew number.
 2317                        // Do nothing here.  TryParseHebrewNumber() will update token accordingly.
 02318                        tokenType = TokenType.HebrewNumber;
 02319                        return true;
 2320                    }
 2321                }
 2322            }
 2323
 02324            int hashcode = ch % TOKEN_HASH_SIZE;
 02325            int hashProbe = 1 + ch % SECOND_PRIME;
 02326            int remaining = str.Length - str.Index;
 02327            int i = 0;
 2328
 02329            TokenHashValue[] hashTable = _dtfiTokenHash ?? CreateTokenHashTable();
 2330            do
 2331            {
 02332                value = hashTable[hashcode];
 02333                if (value == null)
 2334                {
 2335                    // Not found.
 2336                    break;
 2337                }
 2338                // Check this value has the right category (regular token or separator token) that we are looking for.
 02339                if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining)
 2340                {
 02341                    bool compareStrings = true;
 02342                    if (isLetter)
 2343                    {
 2344                        // If this token starts with a letter, make sure that we won't allow partial match.  So you can'
 2345                        // Also an optimization to avoid string comparison
 02346                        int nextCharIndex = str.Index + value.tokenString.Length;
 02347                        if (nextCharIndex > str.Length)
 2348                        {
 02349                            compareStrings = false;
 2350                        }
 02351                        else if (nextCharIndex < str.Length)
 2352                        {
 2353                            // Check word boundary. The next character should NOT be a letter.
 02354                            char nextCh = str.Value[nextCharIndex];
 02355                            compareStrings = !char.IsLetter(nextCh) || IsAllowedJapaneseTokenFollowedByNonSpaceLetter(va
 2356                        }
 2357                    }
 2358
 02359                    if (compareStrings &&
 02360                        ((value.tokenString.Length == 1 && str.Value[str.Index] == value.tokenString[0]) ||
 02361                         Culture.CompareInfo.Compare(str.Value.Slice(str.Index, value.tokenString.Length), value.tokenSt
 2362                    {
 02363                        tokenType = value.tokenType & TokenMask;
 02364                        tokenValue = value.tokenValue;
 02365                        str.Advance(value.tokenString.Length);
 02366                        return true;
 2367                    }
 02368                    else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) ||
 02369                             (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames))
 2370                    {
 2371                        // For month or day token, we will match the names which have spaces.
 02372                        int matchStrLen = 0;
 02373                        if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen))
 2374                        {
 02375                            tokenType = value.tokenType & TokenMask;
 02376                            tokenValue = value.tokenValue;
 02377                            str.Advance(matchStrLen);
 02378                            return true;
 2379                        }
 2380                    }
 2381                }
 02382                i++;
 02383                hashcode += hashProbe;
 02384                if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
 02385            } while (i < TOKEN_HASH_SIZE);
 2386
 02387            return false;
 2388        }
 2389
 2390        private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, string str, char ch, TokenType tokenType, int t
 2391        {
 2392            // Remember the current slot.
 02393            TokenHashValue previousNode = hashTable[hashcode];
 2394
 2395            // Insert the new node into the current slot.
 02396            hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
 2397
 02398            while (++pos < TOKEN_HASH_SIZE)
 2399            {
 02400                hashcode += hashProbe;
 02401                if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
 2402                // Remember this slot
 02403                TokenHashValue temp = hashTable[hashcode];
 2404
 02405                if (temp != null && Culture.TextInfo.ToLower(temp.tokenString[0]) != ch)
 2406                {
 2407                    continue;
 2408                }
 2409                // Put the previous slot into this slot.
 02410                hashTable[hashcode] = previousNode;
 02411                if (temp == null)
 2412                {
 2413                    // Done
 02414                    return;
 2415                }
 02416                previousNode = temp;
 2417            }
 02418            Debug.Fail("The hashtable is full.  This should not happen.");
 2419        }
 2420
 2421        private void InsertHash(TokenHashValue[] hashTable, string str, TokenType tokenType, int tokenValue)
 2422        {
 2423            // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
 02424            if (string.IsNullOrEmpty(str))
 2425            {
 02426                return;
 2427            }
 2428
 2429            TokenHashValue value;
 02430            int i = 0;
 2431            // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are
 2432            // DateTime.Parse().
 02433            if (char.IsWhiteSpace(str[0]) || char.IsWhiteSpace(str[^1]))
 2434            {
 02435                str = str.Trim();   // Trim white space characters.
 2436                // Could have space for separators
 02437                if (str.Length == 0)
 2438                {
 02439                    return;
 2440                }
 2441            }
 2442
 02443            char ch = Culture.TextInfo.ToLower(str[0]);
 02444            int hashcode = ch % TOKEN_HASH_SIZE;
 02445            int hashProbe = 1 + ch % SECOND_PRIME;
 2446            do
 2447            {
 02448                value = hashTable[hashcode];
 02449                if (value == null)
 2450                {
 02451                    hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
 02452                    return;
 2453                }
 2454                else
 2455                {
 2456                    // Collision happens. Find another slot.
 02457                    if (str.Length >= value.tokenString.Length)
 2458                    {
 2459                        // If there are two tokens with the same prefix, we have to make sure that the longer token shou
 2460                        // the shorter ones.
 02461                        if (CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, val
 2462                        {
 02463                            if (str.Length > value.tokenString.Length)
 2464                            {
 2465                                // The str to be inserted has the same prefix as the current token, and str is longer.
 2466                                // Insert str into this node, and shift every node behind it.
 02467                                InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProb
 02468                                return;
 2469                            }
 2470                            else
 2471                            {
 2472                                // Same token.  If they have different types (regular token vs separator token).  Add th
 2473                                // If we have the same regular token or separator token in the hash already, do NOT upda
 2474                                // Therefore, the order of inserting token is significant here regarding what tokenType 
 2475
 2476                                // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , an
 2477                                // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), 
 2478
 02479                                int nTokenType = (int)tokenType;
 02480                                int nCurrentTokenTypeInHash = (int)value.tokenType;
 2481
 2482                                // The following is the fix for the issue of throwing FormatException when "mar" is pass
 02483                                if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType &
 02484                                    ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType
 2485                                {
 02486                                    value.tokenType |= tokenType;
 02487                                    if (tokenValue != 0)
 2488                                    {
 02489                                        value.tokenValue = tokenValue;
 2490                                    }
 2491                                }
 2492                                // The token to be inserted is already in the table.  Skip it.
 02493                                return;
 2494                            }
 2495                        }
 2496                    }
 2497                }
 02498                i++;
 02499                hashcode += hashProbe;
 02500                if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
 02501            } while (i < TOKEN_HASH_SIZE);
 02502            Debug.Fail("The hashtable is full.  This should not happen.");
 2503        }
 2504
 2505        private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offs
 2506        {
 2507            // Optimize for one character cases which are common due to date and time separators (/ and :)
 02508            if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2])
 2509            {
 02510                return true;
 2511            }
 2512
 02513            return Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.Igno
 2514        }
 2515
 2516        internal sealed class TokenHashValue
 2517        {
 2518            internal string tokenString;
 2519            internal TokenType tokenType;
 2520            internal int tokenValue;
 2521
 02522            internal TokenHashValue(string tokenString, TokenType tokenType, int tokenValue)
 2523            {
 02524                this.tokenString = tokenString;
 02525                this.tokenType = tokenType;
 02526                this.tokenValue = tokenValue;
 02527            }
 2528        }
 2529    }
 2530}
 2531

Methods/Properties

.ctor(System.Globalization.CultureData,System.Globalization.Calendar)
CultureName()
Culture()
LanguageName()
InternalGetAbbreviatedDayOfWeekNames()
InternalGetAbbreviatedDayOfWeekNamesCore()
InternalGetSuperShortDayNames()
InternalGetSuperShortDayNamesCore()
InternalGetDayOfWeekNames()
InternalGetDayOfWeekNamesCore()
InternalGetAbbreviatedMonthNames()
InternalGetAbbreviatedMonthNamesCore()
InternalGetMonthNames()
internalGetMonthNamesCore()
.ctor()
InitializeOverridableProperties(System.Globalization.CultureData,System.Globalization.CalendarId)
InvariantInfo()
CurrentInfo()
GetInstance(System.IFormatProvider)
GetProviderNonNull(System.IFormatProvider)
GetFormat(System.Type)
Clone()
AMDesignator()
AMDesignator(System.String)
AMDesignatorTChar()
Calendar()
Calendar(System.Globalization.Calendar)
OptionalCalendars()
GetEra(System.String)
EraNames()
GetEraName(System.Int32)
AbbreviatedEraNames()
GetAbbreviatedEraName(System.Int32)
AbbreviatedEnglishEraNames()
DateSeparator()
DateSeparator(System.String)
DateSeparatorTChar()
FirstDayOfWeek()
FirstDayOfWeek(System.DayOfWeek)
CalendarWeekRule()
CalendarWeekRule(System.Globalization.CalendarWeekRule)
FullDateTimePattern()
FullDateTimePattern(System.String)
LongDatePattern()
LongDatePattern(System.String)
OnLongDatePatternChanged()
LongTimePattern()
LongTimePattern(System.String)
OnLongTimePatternChanged()
MonthDayPattern()
MonthDayPattern(System.String)
PMDesignator()
PMDesignator(System.String)
PMDesignatorTChar()
RFC1123Pattern()
ShortDatePattern()
ShortDatePattern(System.String)
OnShortDatePatternChanged()
ShortTimePattern()
ShortTimePattern(System.String)
OnShortTimePatternChanged()
SortableDateTimePattern()
GeneralShortTimePattern()
GeneralLongTimePattern()
DateTimeOffsetPattern()
TimeSeparator()
TimeSeparator(System.String)
TimeSeparatorTChar()
UniversalSortableDateTimePattern()
YearMonthPattern()
YearMonthPattern(System.String)
OnYearMonthPatternChanged()
CheckNullValue(System.String[],System.Int32)
AbbreviatedDayNames()
AbbreviatedDayNames(System.String[])
ShortestDayNames()
ShortestDayNames(System.String[])
DayNames()
DayNames(System.String[])
AbbreviatedMonthNames()
AbbreviatedMonthNames(System.String[])
MonthNames()
MonthNames(System.String[])
HasSpacesInMonthNames()
HasSpacesInDayNames()
InternalGetMonthName(System.Int32,System.Globalization.MonthNameStyles,System.Boolean)
InternalGetGenitiveMonthNames(System.Boolean)
InternalGetLeapYearMonthNames()
GetAbbreviatedDayName(System.DayOfWeek)
GetShortestDayName(System.DayOfWeek)
GetCombinedPatterns(System.String[],System.String[],System.String)
GetAllDateTimePatterns()
GetAllDateTimePatterns(System.Char)
GetDayName(System.DayOfWeek)
GetAbbreviatedMonthName(System.Int32)
GetMonthName(System.Int32)
GetMergedPatterns(System.String[],System.String)
AllYearMonthPatterns()
AllShortDatePatterns()
AllShortTimePatterns()
AllLongDatePatterns()
AllLongTimePatterns()
UnclonedYearMonthPatterns()
UnclonedShortDatePatterns()
UnclonedLongDatePatterns()
UnclonedShortTimePatterns()
UnclonedLongTimePatterns()
ReadOnly(System.Globalization.DateTimeFormatInfo)
IsReadOnly()
NativeCalendarName()
SetAllDateTimePatterns(System.String[],System.Char)
AbbreviatedMonthGenitiveNames()
AbbreviatedMonthGenitiveNames(System.String[])
MonthGenitiveNames()
MonthGenitiveNames(System.String[])
DecimalSeparator()
DecimalSeparatorTChar()
FullTimeSpanPositivePattern()
FullTimeSpanNegativePattern()
CompareInfo()
ValidateStyles(System.Globalization.DateTimeStyles,System.Boolean)
ThrowInvalid(System.Globalization.DateTimeStyles,System.Boolean)
FormatFlags()
InitializeFormatFlags()
HasForceTwoDigitYears()
HasYearMonthAdjustment()
YearMonthAdjustment(System.Int32&,System.Int32&,System.Boolean)
GetJapaneseCalendarDTFI()
GetTaiwanCalendarDTFI()
ClearTokenHashTable()
CreateTokenHashTable()
AddMonthNames(System.Globalization.DateTimeFormatInfo/TokenHashValue[],System.ReadOnlySpan`1<System.Char>)
TryParseHebrewNumber(System.__DTString&,System.Boolean&,System.Int32&)
IsHebrewChar(System.Char)
IsAllowedJapaneseTokenFollowedByNonSpaceLetter(System.String,System.Char)
Tokenize(System.TokenType,System.TokenType&,System.Int32&,System.__DTString&)
InsertAtCurrentHashNode(System.Globalization.DateTimeFormatInfo/TokenHashValue[],System.String,System.Char,System.TokenType,System.Int32,System.Int32,System.Int32,System.Int32)
InsertHash(System.Globalization.DateTimeFormatInfo/TokenHashValue[],System.String,System.TokenType,System.Int32)
CompareStringIgnoreCaseOptimized(System.String,System.Int32,System.Int32,System.String,System.Int32,System.Int32)
.ctor(System.String,System.TokenType,System.Int32)