Cache Invariant CompareInfo (#15902)
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Globalization / DateTimeFormatInfo.cs
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 // See the LICENSE file in the project root for more information.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8
9 namespace System.Globalization
10 {
11     //
12     // Flags used to indicate different styles of month names.
13     // This is an internal flag used by internalGetMonthName().
14     // Use flag here in case that we need to provide a combination of these styles
15     // (such as month name of a leap year in genitive form.  Not likely for now,
16     // but would like to keep the option open).
17     //
18
19     [Flags]
20     internal enum MonthNameStyles
21     {
22         Regular = 0x00000000,
23         Genitive = 0x00000001,
24         LeapYear = 0x00000002,
25     }
26
27     //
28     // Flags used to indicate special rule used in parsing/formatting
29     // for a specific DateTimeFormatInfo instance.
30     // This is an internal flag.
31     //
32     // This flag is different from MonthNameStyles because this flag
33     // can be expanded to accommodate parsing behaviors like CJK month names
34     // or alternative month names, etc.
35
36     [Flags]
37     internal enum DateTimeFormatFlags
38     {
39         None = 0x00000000,
40         UseGenitiveMonth = 0x00000001,
41         UseLeapYearMonth = 0x00000002,
42         UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
43         UseHebrewRule = 0x00000008,   // Format/Parse using the Hebrew calendar rule.
44         UseSpacesInDayNames = 0x00000010,   // Has spaces or non-breaking space in the day names.
45         UseDigitPrefixInTokens = 0x00000020,   // Has token starting with numbers.
46
47         NotInitialized = -1,
48     }
49
50
51     public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
52     {
53         // cache for the invariant culture.
54         // invariantInfo is constant irrespective of your current culture.
55         private static volatile DateTimeFormatInfo s_invariantInfo;
56
57         // an index which points to a record in Culture Data Table.
58         private CultureData _cultureData;
59
60         // The culture name used to create this DTFI.
61         private String _name = null;
62
63         // The language name of the culture used to create this DTFI.
64         private String _langName = null;
65
66         // CompareInfo usually used by the parser.
67         private CompareInfo _compareInfo = null;
68
69         // Culture matches current DTFI. mainly used for string comparisons during parsing.
70         private CultureInfo _cultureInfo = null;
71
72         //
73         // Caches for various properties.
74         //
75
76         private String amDesignator = null;
77         private String pmDesignator = null;
78
79         private String dateSeparator = null;            // derived from short date (whidbey expects, arrowhead doesn't)
80
81         private String generalShortTimePattern = null;     // short date + short time (whidbey expects, arrowhead doesn't)
82
83         private String generalLongTimePattern = null;     // short date + long time (whidbey expects, arrowhead doesn't)
84
85         private String timeSeparator = null;            // derived from long time (whidbey expects, arrowhead doesn't)
86         private String monthDayPattern = null;
87         // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
88         private String dateTimeOffsetPattern = null;
89
90         //
91         // The following are constant values.
92         //
93         private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
94
95         // The sortable pattern is based on ISO 8601.
96         private const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
97         private const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
98
99         //
100         // The following are affected by calendar settings.
101         //
102         private Calendar calendar = null;
103
104         private int firstDayOfWeek = -1;
105         private int calendarWeekRule = -1;
106
107
108         private String fullDateTimePattern = null;        // long date + long time (whidbey expects, arrowhead doesn't)
109
110         private String[] abbreviatedDayNames = null;
111
112
113         private String[] m_superShortDayNames = null;
114
115         private String[] dayNames = null;
116         private String[] abbreviatedMonthNames = null;
117         private String[] monthNames = null;
118         // Cache the genitive month names that we retrieve from the data table.
119
120         private String[] genitiveMonthNames = null;
121
122         // Cache the abbreviated genitive month names that we retrieve from the data table.
123
124         private String[] m_genitiveAbbreviatedMonthNames = null;
125
126         // Cache the month names of a leap year that we retrieve from the data table.
127
128         private String[] leapYearMonthNames = null;
129
130         // For our "patterns" arrays we have 2 variables, a string and a string[]
131         //
132         // The string[] contains the list of patterns, EXCEPT the default may not be included.
133         // The string contains the default pattern.
134         // When we initially construct our string[], we set the string to string[0]
135
136         // The "default" Date/time patterns 
137         private String longDatePattern = null;
138         private String shortDatePattern = null;
139         private String yearMonthPattern = null;
140         private String longTimePattern = null;
141         private String shortTimePattern = null;
142
143         private String[] allYearMonthPatterns = null;
144
145         private String[] allShortDatePatterns = null;
146         private String[] allLongDatePatterns = null;
147         private String[] allShortTimePatterns = null;
148         private String[] allLongTimePatterns = null;
149
150         // Cache the era names for this DateTimeFormatInfo instance.
151         private String[] m_eraNames = null;
152         private String[] m_abbrevEraNames = null;
153         private String[] m_abbrevEnglishEraNames = null;
154
155         private CalendarId[] optionalCalendars = null;
156
157         private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
158
159         // CultureInfo updates this
160         internal bool _isReadOnly = false;
161
162         // This flag gives hints about if formatting/parsing should perform special code path for things like
163         // genitive form or leap year month names.
164
165         private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
166
167         private String CultureName
168         {
169             get
170             {
171                 if (_name == null)
172                 {
173                     _name = _cultureData.CultureName;
174                 }
175                 return (_name);
176             }
177         }
178
179         private CultureInfo Culture
180         {
181             get
182             {
183                 if (_cultureInfo == null)
184                 {
185                     _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
186                 }
187                 return _cultureInfo;
188             }
189         }
190
191         // TODO: This ignores other cultures that might want to do something similar
192         private String LanguageName
193         {
194             get
195             {
196                 if (_langName == null)
197                 {
198                     _langName = _cultureData.SISO639LANGNAME;
199                 }
200                 return (_langName);
201             }
202         }
203
204         ////////////////////////////////////////////////////////////////////////////
205         //
206         // Create an array of string which contains the abbreviated day names.
207         //
208         ////////////////////////////////////////////////////////////////////////////
209
210         private string[] internalGetAbbreviatedDayOfWeekNames() => this.abbreviatedDayNames ?? internalGetAbbreviatedDayOfWeekNamesCore();
211         [MethodImpl(MethodImplOptions.NoInlining)]
212         private string[] internalGetAbbreviatedDayOfWeekNamesCore()
213         {
214             // Get the abbreviated day names for our current calendar
215             this.abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID);
216             Debug.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week");
217             return this.abbreviatedDayNames;
218         }
219
220         ////////////////////////////////////////////////////////////////////////
221         //
222         // Action: Returns the string array of the one-letter day of week names.
223         // Returns:
224         //  an array of one-letter day of week names
225         // Arguments:
226         //  None
227         // Exceptions:
228         //  None
229         //
230         ////////////////////////////////////////////////////////////////////////
231
232         private string[] internalGetSuperShortDayNames() => this.m_superShortDayNames ?? internalGetSuperShortDayNamesCore();
233         [MethodImpl(MethodImplOptions.NoInlining)]
234         private string[] internalGetSuperShortDayNamesCore()
235         {
236             // Get the super short day names for our current calendar
237             this.m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID);
238             Debug.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week");
239             return this.m_superShortDayNames;
240         }
241
242         ////////////////////////////////////////////////////////////////////////////
243         //
244         // Create an array of string which contains the day names.
245         //
246         ////////////////////////////////////////////////////////////////////////////
247
248         private string[] internalGetDayOfWeekNames() => this.dayNames ?? internalGetDayOfWeekNamesCore();
249         [MethodImpl(MethodImplOptions.NoInlining)]
250         private string[] internalGetDayOfWeekNamesCore()
251         {
252             // Get the day names for our current calendar
253             this.dayNames = _cultureData.DayNames(Calendar.ID);
254             Debug.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
255             return this.dayNames;
256         }
257
258         ////////////////////////////////////////////////////////////////////////////
259         //
260         // Create an array of string which contains the abbreviated month names.
261         //
262         ////////////////////////////////////////////////////////////////////////////
263
264         private String[] internalGetAbbreviatedMonthNames() => this.abbreviatedMonthNames ?? internalGetAbbreviatedMonthNamesCore();
265         [MethodImpl(MethodImplOptions.NoInlining)]
266         private String[] internalGetAbbreviatedMonthNamesCore()
267         {
268             // Get the month names for our current calendar
269             this.abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID);
270             Debug.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13,
271                 "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
272             return this.abbreviatedMonthNames;
273         }
274
275
276         ////////////////////////////////////////////////////////////////////////////
277         //
278         // Create an array of string which contains the month names.
279         //
280         ////////////////////////////////////////////////////////////////////////////
281
282         private string[] internalGetMonthNames() => this.monthNames ?? internalGetMonthNamesCore();
283         [MethodImpl(MethodImplOptions.NoInlining)]
284         private string[] internalGetMonthNamesCore()
285         {
286             // Get the month names for our current calendar
287             this.monthNames = _cultureData.MonthNames(Calendar.ID);
288             Debug.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13,
289                 "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
290             return this.monthNames;
291         }
292
293
294         //
295         // Invariant DateTimeFormatInfo doesn't have user-overriden values
296         // Default calendar is gregorian
297         public DateTimeFormatInfo()
298             : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance())
299         {
300         }
301
302         internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
303         {
304             Debug.Assert(cultureData != null);
305             Debug.Assert(cal != null);
306
307             // Remember our culture
308             _cultureData = cultureData;
309
310             this.Calendar = cal;
311         }
312
313         private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId)
314         {
315             Debug.Assert(cultureData != null);
316             Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId");
317
318             if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
319             if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
320
321             if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
322             if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
323             if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
324             if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarId); }
325
326             this.allLongTimePatterns = _cultureData.LongTimes;
327             Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
328
329             this.allShortTimePatterns = _cultureData.ShortTimes;
330             Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
331
332             this.allLongDatePatterns = cultureData.LongDates(calendarId);
333             Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
334
335             this.allShortDatePatterns = cultureData.ShortDates(calendarId);
336             Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
337
338             this.allYearMonthPatterns = cultureData.YearMonths(calendarId);
339             Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
340         }
341
342         // Returns a default DateTimeFormatInfo that will be universally
343         // supported and constant irrespective of the current culture.
344         // Used by FromString methods.
345         //
346
347         public static DateTimeFormatInfo InvariantInfo
348         {
349             get
350             {
351                 if (s_invariantInfo == null)
352                 {
353                     DateTimeFormatInfo info = new DateTimeFormatInfo();
354                     info.Calendar.SetReadOnlyState(true);
355                     info._isReadOnly = true;
356                     s_invariantInfo = info;
357                 }
358                 return (s_invariantInfo);
359             }
360         }
361
362         // Returns the current culture's DateTimeFormatInfo.  Used by Parse methods.
363         //
364
365         public static DateTimeFormatInfo CurrentInfo
366         {
367             get
368             {
369                 System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
370                 if (!culture._isInherited)
371                 {
372                     DateTimeFormatInfo info = culture.dateTimeInfo;
373                     if (info != null)
374                     {
375                         return info;
376                     }
377                 }
378                 return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
379             }
380         }
381
382         public static DateTimeFormatInfo GetInstance(IFormatProvider provider) =>
383             provider == null ? CurrentInfo :
384             provider is CultureInfo cultureProvider && !cultureProvider._isInherited ? cultureProvider.DateTimeFormat :
385             provider is DateTimeFormatInfo info ? info :
386             provider.GetFormat(typeof(DateTimeFormatInfo)) is DateTimeFormatInfo info2 ? info2 :
387             CurrentInfo; // Couldn't get anything, just use currentInfo as fallback
388
389         public Object GetFormat(Type formatType)
390         {
391             return (formatType == typeof(DateTimeFormatInfo) ? this : null);
392         }
393
394
395         public Object Clone()
396         {
397             DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
398             // We can use the data member calendar in the setter, instead of the property Calendar,
399             // since the cloned copy should have the same state as the original copy.
400             n.calendar = (Calendar)this.Calendar.Clone();
401             n._isReadOnly = false;
402             return n;
403         }
404
405
406         public String AMDesignator
407         {
408             get
409             {
410                 if (this.amDesignator == null)
411                 {
412                     this.amDesignator = _cultureData.SAM1159;
413                 }
414                 Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
415                 return (this.amDesignator);
416             }
417
418             set
419             {
420                 if (IsReadOnly)
421                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
422                 if (value == null)
423                 {
424                     throw new ArgumentNullException(nameof(value),
425                         SR.ArgumentNull_String);
426                 }
427                 ClearTokenHashTable();
428                 amDesignator = value;
429             }
430         }
431
432
433         public Calendar Calendar
434         {
435             get
436             {
437                 Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
438                 return (this.calendar);
439             }
440
441             set
442             {
443                 if (IsReadOnly)
444                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
445                 if (value == null)
446                 {
447                     throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj);
448                 }
449                 if (value == calendar)
450                 {
451                     return;
452                 }
453
454                 for (int i = 0; i < this.OptionalCalendars.Length; i++)
455                 {
456                     if (this.OptionalCalendars[i] == value.ID)
457                     {
458                         // We can use this one, so do so.
459
460                         // Clean related properties if we already had a calendar set
461                         if (calendar != null)
462                         {
463                             // clean related properties which are affected by the calendar setting,
464                             // so that they will be refreshed when they are accessed next time.
465                             //
466
467                             // These properites are in the order as appearing in calendar.xml.
468                             m_eraNames = null;
469                             m_abbrevEraNames = null;
470                             m_abbrevEnglishEraNames = null;
471
472                             monthDayPattern = null;
473
474                             dayNames = null;
475                             abbreviatedDayNames = null;
476                             m_superShortDayNames = null;
477                             monthNames = null;
478                             abbreviatedMonthNames = null;
479                             genitiveMonthNames = null;
480                             m_genitiveAbbreviatedMonthNames = null;
481                             leapYearMonthNames = null;
482                             formatFlags = DateTimeFormatFlags.NotInitialized;
483
484                             allShortDatePatterns = null;
485                             allLongDatePatterns = null;
486                             allYearMonthPatterns = null;
487                             dateTimeOffsetPattern = null;
488
489                             // The defaults need reset as well:
490                             longDatePattern = null;
491                             shortDatePattern = null;
492                             yearMonthPattern = null;
493
494                             // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
495                             fullDateTimePattern = null; // Long date + long time
496                             generalShortTimePattern = null; // short date + short time
497                             generalLongTimePattern = null; // short date + long time
498
499                             // Derived item that changes
500                             dateSeparator = null;
501
502                             // We don't need to do these because they are not changed by changing calendar
503                             //      amDesignator
504                             //      pmDesignator
505                             //      timeSeparator
506                             //      longTimePattern
507                             //      firstDayOfWeek
508                             //      calendarWeekRule
509
510                             // remember to reload tokens
511                             ClearTokenHashTable();
512                         }
513
514                         // Remember the new calendar
515                         calendar = value;
516                         InitializeOverridableProperties(_cultureData, calendar.ID);
517
518                         // We succeeded, return
519                         return;
520                     }
521                 }
522
523                 // The assigned calendar is not a valid calendar for this culture, throw
524                 throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar);
525             }
526         }
527
528         private CalendarId[] OptionalCalendars
529         {
530             get
531             {
532                 if (this.optionalCalendars == null)
533                 {
534                     this.optionalCalendars = _cultureData.CalendarIds;
535                 }
536                 return (this.optionalCalendars);
537             }
538         }
539
540         /*=================================GetEra==========================
541         **Action: Get the era value by parsing the name of the era.
542         **Returns: The era value for the specified era name.
543         **      -1 if the name of the era is not valid or not supported.
544         **Arguments: eraName    the name of the era.
545         **Exceptions: None.
546         ============================================================================*/
547
548
549         public int GetEra(String eraName)
550         {
551             if (eraName == null)
552             {
553                 throw new ArgumentNullException(nameof(eraName),
554                     SR.ArgumentNull_String);
555             }
556
557             // The Era Name and Abbreviated Era Name 
558             // for Taiwan Calendar on non-Taiwan SKU returns empty string (which 
559             // would be matched below) but we don't want the empty string to give
560             // us an Era number
561             // confer 85900 DTFI.GetEra("") should fail on all cultures
562             if (eraName.Length == 0)
563             {
564                 return (-1);
565             }
566
567             // The following is based on the assumption that the era value is starting from 1, and has a
568             // serial values.
569             // If that ever changes, the code has to be changed.
570
571             // The calls to String.Compare should use the current culture for the string comparisons, but the
572             // invariant culture when comparing against the english names.
573             for (int i = 0; i < EraNames.Length; i++)
574             {
575                 // Compare the era name in a case-insensitive way for the appropriate culture.
576                 if (m_eraNames[i].Length > 0)
577                 {
578                     if (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0)
579                     {
580                         return (i + 1);
581                     }
582                 }
583             }
584             for (int i = 0; i < AbbreviatedEraNames.Length; i++)
585             {
586                 // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.              
587                 if (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], CompareOptions.IgnoreCase) == 0)
588                 {
589                     return (i + 1);
590                 }
591             }
592             for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
593             {
594                 // this comparison should use the InvariantCulture.  The English name could have linguistically
595                 // interesting characters.
596                 if (CompareInfo.Invariant.Compare(eraName, m_abbrevEnglishEraNames[i], CompareOptions.IgnoreCase) == 0)
597                 {
598                     return (i + 1);
599                 }
600             }
601             return (-1);
602         }
603
604
605         internal String[] EraNames
606         {
607             get
608             {
609                 if (this.m_eraNames == null)
610                 {
611                     this.m_eraNames = _cultureData.EraNames(Calendar.ID); ;
612                 }
613                 return (this.m_eraNames);
614             }
615         }
616
617         /*=================================GetEraName==========================
618         **Action: Get the name of the era for the specified era value.
619         **Returns: The name of the specified era.
620         **Arguments:
621         **      era the era value.
622         **Exceptions:
623         **      ArguementException if the era valie is invalid.
624         ============================================================================*/
625
626         // Era names are 1 indexed
627         public String GetEraName(int era)
628         {
629             if (era == Calendar.CurrentEra)
630             {
631                 era = Calendar.CurrentEraValue;
632             }
633
634             // The following is based on the assumption that the era value is starting from 1, and has a
635             // serial values.
636             // If that ever changes, the code has to be changed.
637             if ((--era) < EraNames.Length && (era >= 0))
638             {
639                 return (m_eraNames[era]);
640             }
641             throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
642         }
643
644         internal String[] AbbreviatedEraNames
645         {
646             get
647             {
648                 if (this.m_abbrevEraNames == null)
649                 {
650                     this.m_abbrevEraNames = _cultureData.AbbrevEraNames(Calendar.ID);
651                 }
652                 return (this.m_abbrevEraNames);
653             }
654         }
655
656         // Era names are 1 indexed
657         public String GetAbbreviatedEraName(int era)
658         {
659             if (AbbreviatedEraNames.Length == 0)
660             {
661                 // If abbreviation era name is not used in this culture,
662                 // return the full era name.
663                 return (GetEraName(era));
664             }
665             if (era == Calendar.CurrentEra)
666             {
667                 era = Calendar.CurrentEraValue;
668             }
669             if ((--era) < m_abbrevEraNames.Length && (era >= 0))
670             {
671                 return (m_abbrevEraNames[era]);
672             }
673             throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
674         }
675
676         internal String[] AbbreviatedEnglishEraNames
677         {
678             get
679             {
680                 if (this.m_abbrevEnglishEraNames == null)
681                 {
682                     Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
683                     this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
684                 }
685                 return (this.m_abbrevEnglishEraNames);
686             }
687         }
688
689         // Note that cultureData derives this from the short date format (unless someone's set this previously)
690         // Note that this property is quite undesirable.
691         public string DateSeparator
692         {
693             get
694             {
695                 if (dateSeparator == null)
696                 {
697                     dateSeparator = _cultureData.DateSeparator(Calendar.ID);
698                 }
699                 Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
700                 return dateSeparator;
701             }
702             set
703             {
704                 if (IsReadOnly)
705                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
706
707                 if (value == null)
708                 {
709                     throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
710                 }
711                 ClearTokenHashTable();
712                 dateSeparator = value;
713             }
714         }
715
716         public DayOfWeek FirstDayOfWeek
717         {
718             get
719             {
720                 if (this.firstDayOfWeek == -1)
721                 {
722                     this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK;
723                 }
724                 Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
725
726                 return ((DayOfWeek)this.firstDayOfWeek);
727             }
728
729             set
730             {
731                 if (IsReadOnly)
732                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
733                 if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday)
734                 {
735                     firstDayOfWeek = (int)value;
736                 }
737                 else
738                 {
739                     throw new ArgumentOutOfRangeException(
740                         nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
741                         DayOfWeek.Sunday, DayOfWeek.Saturday));
742                 }
743             }
744         }
745
746         public CalendarWeekRule CalendarWeekRule
747         {
748             get
749             {
750                 if (this.calendarWeekRule == -1)
751                 {
752                     this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR;
753                 }
754                 Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
755                 return ((CalendarWeekRule)this.calendarWeekRule);
756             }
757
758             set
759             {
760                 if (IsReadOnly)
761                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
762                 if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek)
763                 {
764                     calendarWeekRule = (int)value;
765                 }
766                 else
767                 {
768                     throw new ArgumentOutOfRangeException(
769                         nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
770                         CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
771                 }
772             }
773         }
774
775         public String FullDateTimePattern
776         {
777             get
778             {
779                 if (fullDateTimePattern == null)
780                 {
781                     fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
782                 }
783                 return (fullDateTimePattern);
784             }
785
786             set
787             {
788                 if (IsReadOnly)
789                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
790                 if (value == null)
791                 {
792                     throw new ArgumentNullException(nameof(value),
793                         SR.ArgumentNull_String);
794                 }
795                 fullDateTimePattern = value;
796             }
797         }
798
799
800         // For our "patterns" arrays we have 2 variables, a string and a string[]
801         //
802         // The string[] contains the list of patterns, EXCEPT the default may not be included.
803         // The string contains the default pattern.
804         // When we initially construct our string[], we set the string to string[0]
805         public String LongDatePattern
806         {
807             get
808             {
809                 // Initialize our long date pattern from the 1st array value if not set
810                 if (this.longDatePattern == null)
811                 {
812                     // Initialize our data
813                     this.longDatePattern = this.UnclonedLongDatePatterns[0];
814                 }
815
816                 return this.longDatePattern;
817             }
818
819             set
820             {
821                 if (IsReadOnly)
822                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
823                 if (value == null)
824                 {
825                     throw new ArgumentNullException(nameof(value),
826                         SR.ArgumentNull_String);
827                 }
828
829                 // Remember the new string
830                 this.longDatePattern = value;
831
832                 // Clear the token hash table
833                 ClearTokenHashTable();
834
835                 // Clean up cached values that will be affected by this property.
836                 this.fullDateTimePattern = null;
837             }
838         }
839
840         // For our "patterns" arrays we have 2 variables, a string and a string[]
841         //
842         // The string[] contains the list of patterns, EXCEPT the default may not be included.
843         // The string contains the default pattern.
844         // When we initially construct our string[], we set the string to string[0]
845         public String LongTimePattern
846         {
847             get
848             {
849                 // Initialize our long time pattern from the 1st array value if not set
850                 if (this.longTimePattern == null)
851                 {
852                     // Initialize our data
853                     this.longTimePattern = this.UnclonedLongTimePatterns[0];
854                 }
855
856                 return this.longTimePattern;
857             }
858
859             set
860             {
861                 if (IsReadOnly)
862                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
863                 if (value == null)
864                 {
865                     throw new ArgumentNullException(nameof(value),
866                         SR.ArgumentNull_String);
867                 }
868
869                 // Remember the new string
870                 this.longTimePattern = value;
871
872                 // Clear the token hash table
873                 ClearTokenHashTable();
874
875                 // Clean up cached values that will be affected by this property.
876                 this.fullDateTimePattern = null;     // Full date = long date + long Time
877                 this.generalLongTimePattern = null;  // General long date = short date + long Time
878                 this.dateTimeOffsetPattern = null;
879             }
880         }
881
882
883         // Note: just to be confusing there's only 1 month day pattern, not a whole list
884         public String MonthDayPattern
885         {
886             get
887             {
888                 if (this.monthDayPattern == null)
889                 {
890                     Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
891                     this.monthDayPattern = _cultureData.MonthDay(Calendar.ID);
892                 }
893                 Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
894                 return (this.monthDayPattern);
895             }
896
897             set
898             {
899                 if (IsReadOnly)
900                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
901                 if (value == null)
902                 {
903                     throw new ArgumentNullException(nameof(value),
904                         SR.ArgumentNull_String);
905                 }
906
907                 this.monthDayPattern = value;
908             }
909         }
910
911
912         public String PMDesignator
913         {
914             get
915             {
916                 if (this.pmDesignator == null)
917                 {
918                     this.pmDesignator = _cultureData.SPM2359;
919                 }
920                 Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
921                 return (this.pmDesignator);
922             }
923
924             set
925             {
926                 if (IsReadOnly)
927                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
928                 if (value == null)
929                 {
930                     throw new ArgumentNullException(nameof(value),
931                         SR.ArgumentNull_String);
932                 }
933                 ClearTokenHashTable();
934
935                 pmDesignator = value;
936             }
937         }
938
939
940         public String RFC1123Pattern
941         {
942             get
943             {
944                 return (rfc1123Pattern);
945             }
946         }
947
948         // For our "patterns" arrays we have 2 variables, a string and a string[]
949         //
950         // The string[] contains the list of patterns, EXCEPT the default may not be included.
951         // The string contains the default pattern.
952         // When we initially construct our string[], we set the string to string[0]
953         public String ShortDatePattern
954         {
955             get
956             {
957                 // Initialize our short date pattern from the 1st array value if not set
958                 if (this.shortDatePattern == null)
959                 {
960                     // Initialize our data
961                     this.shortDatePattern = this.UnclonedShortDatePatterns[0];
962                 }
963
964                 return this.shortDatePattern;
965             }
966
967             set
968             {
969                 if (IsReadOnly)
970                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
971                 if (value == null)
972                     throw new ArgumentNullException(nameof(value),
973                         SR.ArgumentNull_String);
974
975                 // Remember the new string
976                 this.shortDatePattern = value;
977
978                 // Clear the token hash table, note that even short dates could require this
979                 ClearTokenHashTable();
980
981                 // Clean up cached values that will be affected by this property.
982                 generalLongTimePattern = null;   // General long time = short date + long time
983                 generalShortTimePattern = null;  // General short time = short date + short Time
984                 dateTimeOffsetPattern = null;
985             }
986         }
987
988
989         // For our "patterns" arrays we have 2 variables, a string and a string[]
990         //
991         // The string[] contains the list of patterns, EXCEPT the default may not be included.
992         // The string contains the default pattern.
993         // When we initially construct our string[], we set the string to string[0]
994         public String ShortTimePattern
995         {
996             get
997             {
998                 // Initialize our short time pattern from the 1st array value if not set
999                 if (this.shortTimePattern == null)
1000                 {
1001                     // Initialize our data
1002                     this.shortTimePattern = this.UnclonedShortTimePatterns[0];
1003                 }
1004                 return this.shortTimePattern;
1005             }
1006
1007             set
1008             {
1009                 if (IsReadOnly)
1010                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1011                 if (value == null)
1012                 {
1013                     throw new ArgumentNullException(nameof(value),
1014                         SR.ArgumentNull_String);
1015                 }
1016
1017                 // Remember the new string
1018                 this.shortTimePattern = value;
1019
1020                 // Clear the token hash table, note that even short times could require this
1021                 ClearTokenHashTable();
1022
1023                 // Clean up cached values that will be affected by this property.
1024                 generalShortTimePattern = null; // General short date = short date + short time.
1025             }
1026         }
1027
1028
1029         public String SortableDateTimePattern
1030         {
1031             get
1032             {
1033                 return (sortableDateTimePattern);
1034             }
1035         }
1036
1037         /*=================================GeneralShortTimePattern=====================
1038         **Property: Return the pattern for 'g' general format: shortDate + short time
1039         **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1040         **      We put this internal property here so that we can avoid doing the
1041         **      concatation every time somebody asks for the general format.
1042         ==============================================================================*/
1043
1044         internal String GeneralShortTimePattern
1045         {
1046             get
1047             {
1048                 if (generalShortTimePattern == null)
1049                 {
1050                     generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
1051                 }
1052                 return (generalShortTimePattern);
1053             }
1054         }
1055
1056         /*=================================GeneralLongTimePattern=====================
1057         **Property: Return the pattern for 'g' general format: shortDate + Long time
1058         **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1059         **      We put this internal property here so that we can avoid doing the
1060         **      concatation every time somebody asks for the general format.
1061         ==============================================================================*/
1062
1063         internal String GeneralLongTimePattern
1064         {
1065             get
1066             {
1067                 if (generalLongTimePattern == null)
1068                 {
1069                     generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
1070                 }
1071                 return (generalLongTimePattern);
1072             }
1073         }
1074
1075         /*=================================DateTimeOffsetPattern==========================
1076         **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1077         **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time +  time zone offset
1078         **      We put this internal property here so that we can avoid doing the
1079         **      concatation every time somebody uses this form
1080         ==============================================================================*/
1081
1082         internal String DateTimeOffsetPattern
1083         {
1084             get
1085             {
1086                 if (dateTimeOffsetPattern == null)
1087                 {
1088                     string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
1089
1090                     /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
1091
1092                     bool foundZ = false;
1093                     bool inQuote = false;
1094                     char quote = '\'';
1095                     for (int i = 0; !foundZ && i < LongTimePattern.Length; i++)
1096                     {
1097                         switch (LongTimePattern[i])
1098                         {
1099                             case 'z':
1100                                 /* if we aren't in a quote, we've found a z */
1101                                 foundZ = !inQuote;
1102                                 /* we'll fall out of the loop now because the test includes !foundZ */
1103                                 break;
1104                             case '\'':
1105                             case '\"':
1106                                 if (inQuote && (quote == LongTimePattern[i]))
1107                                 {
1108                                     /* we were in a quote and found a matching exit quote, so we are outside a quote now */
1109                                     inQuote = false;
1110                                 }
1111                                 else if (!inQuote)
1112                                 {
1113                                     quote = LongTimePattern[i];
1114                                     inQuote = true;
1115                                 }
1116                                 else
1117                                 {
1118                                     /* we were in a quote and saw the other type of quote character, so we are still in a quote */
1119                                 }
1120                                 break;
1121                             case '%':
1122                             case '\\':
1123                                 i++; /* skip next character that is escaped by this backslash */
1124                                 break;
1125                             default:
1126                                 break;
1127                         }
1128                     }
1129
1130                     if (!foundZ)
1131                     {
1132                         dateTimePattern = dateTimePattern + " zzz";
1133                     }
1134
1135                     dateTimeOffsetPattern = dateTimePattern;
1136                 }
1137                 return (dateTimeOffsetPattern);
1138             }
1139         }
1140
1141         // Note that cultureData derives this from the long time format (unless someone's set this previously)
1142         // Note that this property is quite undesirable.
1143         public string TimeSeparator
1144         {
1145             get
1146             {
1147                 if (timeSeparator == null)
1148                 {
1149                     timeSeparator = _cultureData.TimeSeparator;
1150                 }
1151                 Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
1152                 return (timeSeparator);
1153             }
1154
1155             set
1156             {
1157                 if (IsReadOnly)
1158                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1159
1160                 if (value == null)
1161                 {
1162                     throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
1163                 }
1164
1165                 ClearTokenHashTable();
1166
1167                 timeSeparator = value;
1168             }
1169         }
1170
1171         public String UniversalSortableDateTimePattern
1172         {
1173             get
1174             {
1175                 return (universalSortableDateTimePattern);
1176             }
1177         }
1178
1179         // For our "patterns" arrays we have 2 variables, a string and a string[]
1180         //
1181         // The string[] contains the list of patterns, EXCEPT the default may not be included.
1182         // The string contains the default pattern.
1183         // When we initially construct our string[], we set the string to string[0]
1184         public String YearMonthPattern
1185         {
1186             get
1187             {
1188                 // Initialize our year/month pattern from the 1st array value if not set
1189                 if (this.yearMonthPattern == null)
1190                 {
1191                     // Initialize our data
1192                     this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
1193                 }
1194                 return this.yearMonthPattern;
1195             }
1196
1197             set
1198             {
1199                 if (IsReadOnly)
1200                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1201                 if (value == null)
1202                 {
1203                     throw new ArgumentNullException(nameof(value),
1204                         SR.ArgumentNull_String);
1205                 }
1206
1207                 // Remember the new string
1208                 this.yearMonthPattern = value;
1209
1210                 // Clear the token hash table, note that even short times could require this
1211                 ClearTokenHashTable();
1212             }
1213         }
1214
1215         //
1216         // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
1217         //
1218         private static void CheckNullValue(String[] values, int length)
1219         {
1220             Debug.Assert(values != null, "value != null");
1221             Debug.Assert(values.Length >= length);
1222             for (int i = 0; i < length; i++)
1223             {
1224                 if (values[i] == null)
1225                 {
1226                     throw new ArgumentNullException("value",
1227                         SR.ArgumentNull_ArrayValue);
1228                 }
1229             }
1230         }
1231
1232
1233         public String[] AbbreviatedDayNames
1234         {
1235             get
1236             {
1237                 return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
1238             }
1239
1240             set
1241             {
1242                 if (IsReadOnly)
1243                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1244                 if (value == null)
1245                 {
1246                     throw new ArgumentNullException(nameof(value),
1247                         SR.ArgumentNull_Array);
1248                 }
1249                 if (value.Length != 7)
1250                 {
1251                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1252                 }
1253                 CheckNullValue(value, value.Length);
1254                 ClearTokenHashTable();
1255
1256                 abbreviatedDayNames = value;
1257             }
1258         }
1259
1260         // Returns the string array of the one-letter day of week names.
1261         public String[] ShortestDayNames
1262         {
1263             get
1264             {
1265                 return ((String[])internalGetSuperShortDayNames().Clone());
1266             }
1267
1268             set
1269             {
1270                 if (IsReadOnly)
1271                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1272                 if (value == null)
1273                 {
1274                     throw new ArgumentNullException(nameof(value),
1275                         SR.ArgumentNull_Array);
1276                 }
1277                 if (value.Length != 7)
1278                 {
1279                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1280                 }
1281                 CheckNullValue(value, value.Length);
1282                 this.m_superShortDayNames = value;
1283             }
1284         }
1285
1286
1287         public String[] DayNames
1288         {
1289             get
1290             {
1291                 return ((String[])internalGetDayOfWeekNames().Clone());
1292             }
1293
1294             set
1295             {
1296                 if (IsReadOnly)
1297                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1298                 if (value == null)
1299                 {
1300                     throw new ArgumentNullException(nameof(value),
1301                         SR.ArgumentNull_Array);
1302                 }
1303                 if (value.Length != 7)
1304                 {
1305                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1306                 }
1307                 CheckNullValue(value, value.Length);
1308                 ClearTokenHashTable();
1309
1310                 dayNames = value;
1311             }
1312         }
1313
1314
1315         public String[] AbbreviatedMonthNames
1316         {
1317             get
1318             {
1319                 return ((String[])internalGetAbbreviatedMonthNames().Clone());
1320             }
1321
1322             set
1323             {
1324                 if (IsReadOnly)
1325                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1326                 if (value == null)
1327                 {
1328                     throw new ArgumentNullException(nameof(value),
1329                         SR.ArgumentNull_Array);
1330                 }
1331                 if (value.Length != 13)
1332                 {
1333                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1334                 }
1335                 CheckNullValue(value, value.Length - 1);
1336                 ClearTokenHashTable();
1337                 abbreviatedMonthNames = value;
1338             }
1339         }
1340
1341
1342         public String[] MonthNames
1343         {
1344             get
1345             {
1346                 return ((String[])internalGetMonthNames().Clone());
1347             }
1348
1349             set
1350             {
1351                 if (IsReadOnly)
1352                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1353                 if (value == null)
1354                 {
1355                     throw new ArgumentNullException(nameof(value),
1356                         SR.ArgumentNull_Array);
1357                 }
1358                 if (value.Length != 13)
1359                 {
1360                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1361                 }
1362                 CheckNullValue(value, value.Length - 1);
1363                 monthNames = value;
1364                 ClearTokenHashTable();
1365             }
1366         }
1367
1368         // Whitespaces that we allow in the month names.
1369         // U+00a0 is non-breaking space.
1370         private static readonly char[] s_monthSpaces = { ' ', '\u00a0' };
1371
1372         internal bool HasSpacesInMonthNames
1373         {
1374             get
1375             {
1376                 return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
1377             }
1378         }
1379
1380         internal bool HasSpacesInDayNames
1381         {
1382             get
1383             {
1384                 return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
1385             }
1386         }
1387
1388
1389         //
1390         //  internalGetMonthName
1391         //
1392         // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
1393         //      or full form.
1394         // Arguments:
1395         //      month
1396         //      style           To indicate a form like regular/genitive/month name in a leap year.
1397         //      abbreviated     When true, return abbreviated form.  Otherwise, return a full form.
1398         //  Exceptions:
1399         //      ArgumentOutOfRangeException When month name is invalid.
1400         //
1401         internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated)
1402         {
1403             //
1404             // Right now, style is mutual exclusive, but I make the style to be flag so that
1405             // maybe we can combine flag if there is such a need.
1406             //
1407             String[] monthNamesArray = null;
1408             switch (style)
1409             {
1410                 case MonthNameStyles.Genitive:
1411                     monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
1412                     break;
1413                 case MonthNameStyles.LeapYear:
1414                     monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
1415                     break;
1416                 default:
1417                     monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames() : internalGetMonthNames());
1418                     break;
1419             }
1420             // The month range is from 1 ~ this.m_monthNames.Length
1421             // (actually is 13 right now for all cases)
1422             if ((month < 1) || (month > monthNamesArray.Length))
1423             {
1424                 throw new ArgumentOutOfRangeException(
1425                     nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1426                     1, monthNamesArray.Length));
1427             }
1428             return (monthNamesArray[month - 1]);
1429         }
1430
1431         //
1432         //  internalGetGenitiveMonthNames
1433         //
1434         //  Action: Retrieve the array which contains the month names in genitive form.
1435         //      If this culture does not use the gentive form, the normal month name is returned.
1436         //  Arguments:
1437         //      abbreviated     When true, return abbreviated form.  Otherwise, return a full form.
1438         //
1439         private String[] internalGetGenitiveMonthNames(bool abbreviated)
1440         {
1441             if (abbreviated)
1442             {
1443                 if (this.m_genitiveAbbreviatedMonthNames == null)
1444                 {
1445                     this.m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
1446                     Debug.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
1447                         "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
1448                 }
1449                 return (this.m_genitiveAbbreviatedMonthNames);
1450             }
1451
1452             if (this.genitiveMonthNames == null)
1453             {
1454                 this.genitiveMonthNames = _cultureData.GenitiveMonthNames(this.Calendar.ID);
1455                 Debug.Assert(this.genitiveMonthNames.Length == 13,
1456                     "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
1457             }
1458             return (this.genitiveMonthNames);
1459         }
1460
1461         //
1462         //  internalGetLeapYearMonthNames
1463         //
1464         //  Actions: Retrieve the month names used in a leap year.
1465         //      If this culture does not have different month names in a leap year, the normal month name is returned.
1466         //  Agruments: None. (can use abbreviated later if needed)
1467         //
1468         internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/)
1469         {
1470             if (this.leapYearMonthNames == null)
1471             {
1472                 Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
1473                 this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID);
1474                 Debug.Assert(this.leapYearMonthNames.Length == 13,
1475                     "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
1476             }
1477             return (leapYearMonthNames);
1478         }
1479
1480
1481         public String GetAbbreviatedDayName(DayOfWeek dayofweek)
1482         {
1483             if ((int)dayofweek < 0 || (int)dayofweek > 6)
1484             {
1485                 throw new ArgumentOutOfRangeException(
1486                     nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
1487                     DayOfWeek.Sunday, DayOfWeek.Saturday));
1488             }
1489             //
1490             // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
1491             // property, so it will be slower.  Instead, use GetAbbreviatedDayOfWeekNames() directly.
1492             //
1493             return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
1494         }
1495
1496         // Returns the super short day of week names for the specified day of week.
1497         public string GetShortestDayName(DayOfWeek dayOfWeek)
1498         {
1499             if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6)
1500             {
1501                 throw new ArgumentOutOfRangeException(
1502                     nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range,
1503                     DayOfWeek.Sunday, DayOfWeek.Saturday));
1504             }
1505             //
1506             // Don't call the public property SuperShortDayNames here since a clone is needed in that
1507             // property, so it will be slower.  Instead, use internalGetSuperShortDayNames() directly.
1508             //
1509             return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
1510         }
1511
1512         // Get all possible combination of inputs
1513         private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
1514         {
1515             Debug.Assert(patterns1 != null);
1516             Debug.Assert(patterns2 != null);
1517
1518             // Get array size
1519             String[] result = new String[patterns1.Length * patterns2.Length];
1520
1521             // Counter of actual results
1522             int k = 0;
1523             for (int i = 0; i < patterns1.Length; i++)
1524             {
1525                 for (int j = 0; j < patterns2.Length; j++)
1526                 {
1527                     // Can't combine if null or empty
1528                     result[k++] = patterns1[i] + connectString + patterns2[j];
1529                 }
1530             }
1531
1532             // Return the combinations
1533             return (result);
1534         }
1535
1536         public string[] GetAllDateTimePatterns()
1537         {
1538             List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
1539
1540             for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
1541             {
1542                 String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
1543                 for (int j = 0; j < strings.Length; j++)
1544                 {
1545                     results.Add(strings[j]);
1546                 }
1547             }
1548             return results.ToArray();
1549         }
1550
1551         public string[] GetAllDateTimePatterns(char format)
1552         {
1553             String[] result = null;
1554
1555             switch (format)
1556             {
1557                 case 'd':
1558                     result = this.AllShortDatePatterns;
1559                     break;
1560                 case 'D':
1561                     result = this.AllLongDatePatterns;
1562                     break;
1563                 case 'f':
1564                     result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
1565                     break;
1566                 case 'F':
1567                 case 'U':
1568                     result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
1569                     break;
1570                 case 'g':
1571                     result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
1572                     break;
1573                 case 'G':
1574                     result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
1575                     break;
1576                 case 'm':
1577                 case 'M':
1578                     result = new String[] { MonthDayPattern };
1579                     break;
1580                 case 'o':
1581                 case 'O':
1582                     result = new String[] { RoundtripFormat };
1583                     break;
1584                 case 'r':
1585                 case 'R':
1586                     result = new String[] { rfc1123Pattern };
1587                     break;
1588                 case 's':
1589                     result = new String[] { sortableDateTimePattern };
1590                     break;
1591                 case 't':
1592                     result = this.AllShortTimePatterns;
1593                     break;
1594                 case 'T':
1595                     result = this.AllLongTimePatterns;
1596                     break;
1597                 case 'u':
1598                     result = new String[] { UniversalSortableDateTimePattern };
1599                     break;
1600                 case 'y':
1601                 case 'Y':
1602                     result = this.AllYearMonthPatterns;
1603                     break;
1604                 default:
1605                     throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format));
1606             }
1607             return (result);
1608         }
1609
1610
1611         public String GetDayName(DayOfWeek dayofweek)
1612         {
1613             if ((int)dayofweek < 0 || (int)dayofweek > 6)
1614             {
1615                 throw new ArgumentOutOfRangeException(
1616                     nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
1617                     DayOfWeek.Sunday, DayOfWeek.Saturday));
1618             }
1619
1620             // Use the internal one so that we don't clone the array unnecessarily
1621             return (internalGetDayOfWeekNames()[(int)dayofweek]);
1622         }
1623
1624         public String GetAbbreviatedMonthName(int month)
1625         {
1626             if (month < 1 || month > 13)
1627             {
1628                 throw new ArgumentOutOfRangeException(
1629                     nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1630                     1, 13));
1631             }
1632             // Use the internal one so we don't clone the array unnecessarily
1633             return (internalGetAbbreviatedMonthNames()[month - 1]);
1634         }
1635
1636         public String GetMonthName(int month)
1637         {
1638             if (month < 1 || month > 13)
1639             {
1640                 throw new ArgumentOutOfRangeException(
1641                     nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1642                     1, 13));
1643             }
1644             // Use the internal one so we don't clone the array unnecessarily
1645             return (internalGetMonthNames()[month - 1]);
1646         }
1647
1648         // For our "patterns" arrays we have 2 variables, a string and a string[]
1649         //
1650         // The string[] contains the list of patterns, EXCEPT the default may not be included.
1651         // The string contains the default pattern.
1652         // When we initially construct our string[], we set the string to string[0]
1653         //
1654         // The resulting [] can get returned to the calling app, so clone it.
1655         private static string[] GetMergedPatterns(string[] patterns, string defaultPattern)
1656         {
1657             Debug.Assert(patterns != null && patterns.Length > 0,
1658                             "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
1659             Debug.Assert(defaultPattern != null,
1660                             "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
1661
1662             // If the default happens to be the first in the list just return (a cloned) copy
1663             if (defaultPattern == patterns[0])
1664             {
1665                 return (string[])patterns.Clone();
1666             }
1667
1668             // We either need a bigger list, or the pattern from the list.
1669             int i;
1670             for (i = 0; i < patterns.Length; i++)
1671             {
1672                 // Stop if we found it
1673                 if (defaultPattern == patterns[i])
1674                     break;
1675             }
1676
1677             // Either way we're going to need a new array
1678             string[] newPatterns;
1679
1680             // Did we find it
1681             if (i < patterns.Length)
1682             {
1683                 // Found it, output will be same size
1684                 newPatterns = (string[])patterns.Clone();
1685
1686                 // Have to move [0] item to [i] so we can re-write default at [0]
1687                 // (remember defaultPattern == [i] so this is OK)
1688                 newPatterns[i] = newPatterns[0];
1689             }
1690             else
1691             {
1692                 // Not found, make room for it
1693                 newPatterns = new String[patterns.Length + 1];
1694
1695                 // Copy existing array
1696                 Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
1697             }
1698
1699             // Remember the default
1700             newPatterns[0] = defaultPattern;
1701
1702             // Return the reconstructed list
1703             return newPatterns;
1704         }
1705
1706         // Needed by DateTimeFormatInfo and DateTimeFormat
1707         internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
1708         internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
1709
1710         // Default string isn't necessarily in our string array, so get the 
1711         // merged patterns of both
1712         private String[] AllYearMonthPatterns
1713         {
1714             get
1715             {
1716                 return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
1717             }
1718         }
1719
1720         private String[] AllShortDatePatterns
1721         {
1722             get
1723             {
1724                 return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
1725             }
1726         }
1727
1728         private String[] AllShortTimePatterns
1729         {
1730             get
1731             {
1732                 return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
1733             }
1734         }
1735
1736         private String[] AllLongDatePatterns
1737         {
1738             get
1739             {
1740                 return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
1741             }
1742         }
1743
1744         private String[] AllLongTimePatterns
1745         {
1746             get
1747             {
1748                 return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
1749             }
1750         }
1751
1752         // NOTE: Clone this string array if you want to return it to user.  Otherwise, you are returning a writable cache copy.
1753         // This won't include default, call AllYearMonthPatterns
1754         private String[] UnclonedYearMonthPatterns
1755         {
1756             get
1757             {
1758                 if (allYearMonthPatterns == null)
1759                 {
1760                     Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
1761                     this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID);
1762                     Debug.Assert(this.allYearMonthPatterns.Length > 0,
1763                         "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
1764                 }
1765
1766                 return allYearMonthPatterns;
1767             }
1768         }
1769
1770
1771         // NOTE: Clone this string array if you want to return it to user.  Otherwise, you are returning a writable cache copy.
1772         // This won't include default, call AllShortDatePatterns
1773         private String[] UnclonedShortDatePatterns
1774         {
1775             get
1776             {
1777                 if (allShortDatePatterns == null)
1778                 {
1779                     Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
1780                     this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID);
1781                     Debug.Assert(this.allShortDatePatterns.Length > 0,
1782                         "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
1783                 }
1784
1785                 return this.allShortDatePatterns;
1786             }
1787         }
1788
1789         // NOTE: Clone this string array if you want to return it to user.  Otherwise, you are returning a writable cache copy.
1790         // This won't include default, call AllLongDatePatterns
1791         private String[] UnclonedLongDatePatterns
1792         {
1793             get
1794             {
1795                 if (allLongDatePatterns == null)
1796                 {
1797                     Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
1798                     this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID);
1799                     Debug.Assert(this.allLongDatePatterns.Length > 0,
1800                         "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
1801                 }
1802
1803                 return this.allLongDatePatterns;
1804             }
1805         }
1806
1807         // NOTE: Clone this string array if you want to return it to user.  Otherwise, you are returning a writable cache copy.
1808         // This won't include default, call AllShortTimePatterns
1809         private String[] UnclonedShortTimePatterns
1810         {
1811             get
1812             {
1813                 if (this.allShortTimePatterns == null)
1814                 {
1815                     this.allShortTimePatterns = _cultureData.ShortTimes;
1816                     Debug.Assert(this.allShortTimePatterns.Length > 0,
1817                         "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
1818                 }
1819
1820                 return this.allShortTimePatterns;
1821             }
1822         }
1823
1824         // NOTE: Clone this string array if you want to return it to user.  Otherwise, you are returning a writable cache copy.
1825         // This won't include default, call AllLongTimePatterns
1826         private String[] UnclonedLongTimePatterns
1827         {
1828             get
1829             {
1830                 if (this.allLongTimePatterns == null)
1831                 {
1832                     this.allLongTimePatterns = _cultureData.LongTimes;
1833                     Debug.Assert(this.allLongTimePatterns.Length > 0,
1834                         "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
1835                 }
1836
1837                 return this.allLongTimePatterns;
1838             }
1839         }
1840
1841         public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
1842         {
1843             if (dtfi == null)
1844             {
1845                 throw new ArgumentNullException(nameof(dtfi),
1846                     SR.ArgumentNull_Obj);
1847             }
1848             if (dtfi.IsReadOnly)
1849             {
1850                 return (dtfi);
1851             }
1852             DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
1853             // We can use the data member calendar in the setter, instead of the property Calendar,
1854             // since the cloned copy should have the same state as the original copy.
1855             newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
1856             newInfo._isReadOnly = true;
1857             return (newInfo);
1858         }
1859
1860         public bool IsReadOnly
1861         {
1862             get
1863             {
1864                 return (_isReadOnly);
1865             }
1866         }
1867
1868         // Return the native name for the calendar in DTFI.Calendar.  The native name is referred to
1869         // the culture used to create the DTFI.  E.g. in the following example, the native language is Japanese.
1870         // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
1871         // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
1872         // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
1873         // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
1874         public string NativeCalendarName
1875         {
1876             get
1877             {
1878                 return _cultureData.CalendarName(Calendar.ID);
1879             }
1880         }
1881
1882         //
1883         // Used by custom cultures and others to set the list of available formats. Note that none of them are
1884         // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
1885         // from the list.
1886         //
1887         // Most of the format characters that can be used in GetAllDateTimePatterns are
1888         // not really needed since they are one of the following:
1889         //
1890         //  r/R/s/u     locale-independent constants -- cannot be changed!
1891         //  m/M/y/Y     fields with a single string in them -- that can be set through props directly
1892         //  f/F/g/G/U   derived fields based on combinations of various of the below formats
1893         //
1894         // NOTE: No special validation is done here beyond what is done when the actual respective fields
1895         // are used (what would be the point of disallowing here what we allow in the appropriate property?)
1896         //
1897         // WARNING: If more validation is ever done in one place, it should be done in the other.
1898         //
1899         public void SetAllDateTimePatterns(String[] patterns, char format)
1900         {
1901             if (IsReadOnly)
1902                 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1903
1904             if (patterns == null)
1905             {
1906                 throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array);
1907             }
1908
1909             if (patterns.Length == 0)
1910             {
1911                 throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns));
1912             }
1913
1914
1915             for (int i = 0; i < patterns.Length; i++)
1916             {
1917                 if (patterns[i] == null)
1918                 {
1919                     throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue);
1920                 }
1921             }
1922
1923             // Remember the patterns, and use the 1st as default
1924             switch (format)
1925             {
1926                 case 'd':
1927                     allShortDatePatterns = patterns;
1928                     shortDatePattern = allShortDatePatterns[0];
1929                     break;
1930
1931                 case 'D':
1932                     allLongDatePatterns = patterns;
1933                     longDatePattern = allLongDatePatterns[0];
1934                     break;
1935
1936                 case 't':
1937                     allShortTimePatterns = patterns;
1938                     shortTimePattern = allShortTimePatterns[0];
1939                     break;
1940
1941                 case 'T':
1942                     allLongTimePatterns = patterns;
1943                     longTimePattern = allLongTimePatterns[0];
1944                     break;
1945
1946                 case 'y':
1947                 case 'Y':
1948                     allYearMonthPatterns = patterns;
1949                     yearMonthPattern = allYearMonthPatterns[0];
1950                     break;
1951
1952                 default:
1953                     throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format));
1954             }
1955
1956             // Clear the token hash table, note that even short dates could require this
1957             ClearTokenHashTable();
1958         }
1959
1960         public String[] AbbreviatedMonthGenitiveNames
1961         {
1962             get
1963             {
1964                 return ((String[])internalGetGenitiveMonthNames(true).Clone());
1965             }
1966
1967             set
1968             {
1969                 if (IsReadOnly)
1970                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1971                 if (value == null)
1972                 {
1973                     throw new ArgumentNullException(nameof(value),
1974                         SR.ArgumentNull_Array);
1975                 }
1976                 if (value.Length != 13)
1977                 {
1978                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1979                 }
1980                 CheckNullValue(value, value.Length - 1);
1981                 ClearTokenHashTable();
1982                 this.m_genitiveAbbreviatedMonthNames = value;
1983             }
1984         }
1985
1986         public String[] MonthGenitiveNames
1987         {
1988             get
1989             {
1990                 return ((String[])internalGetGenitiveMonthNames(false).Clone());
1991             }
1992
1993             set
1994             {
1995                 if (IsReadOnly)
1996                     throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1997                 if (value == null)
1998                 {
1999                     throw new ArgumentNullException(nameof(value),
2000                         SR.ArgumentNull_Array);
2001                 }
2002                 if (value.Length != 13)
2003                 {
2004                     throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
2005                 }
2006                 CheckNullValue(value, value.Length - 1);
2007                 genitiveMonthNames = value;
2008                 ClearTokenHashTable();
2009             }
2010         }
2011
2012         //
2013         // Positive TimeSpan Pattern
2014         //
2015         private string _fullTimeSpanPositivePattern;
2016         internal String FullTimeSpanPositivePattern
2017         {
2018             get
2019             {
2020                 if (_fullTimeSpanPositivePattern == null)
2021                 {
2022                     CultureData cultureDataWithoutUserOverrides;
2023                     if (_cultureData.UseUserOverride)
2024                         cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false);
2025                     else
2026                         cultureDataWithoutUserOverrides = _cultureData;
2027                     String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
2028
2029                     _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
2030                 }
2031                 return _fullTimeSpanPositivePattern;
2032             }
2033         }
2034
2035         //
2036         // Negative TimeSpan Pattern
2037         //
2038         private string _fullTimeSpanNegativePattern;
2039         internal String FullTimeSpanNegativePattern
2040         {
2041             get
2042             {
2043                 if (_fullTimeSpanNegativePattern == null)
2044                     _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
2045                 return _fullTimeSpanNegativePattern;
2046             }
2047         }
2048
2049         //
2050         // Get suitable CompareInfo from current DTFI object.
2051         //
2052         internal CompareInfo CompareInfo
2053         {
2054             get
2055             {
2056                 if (_compareInfo == null)
2057                 {
2058                     // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
2059                     // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData.
2060                     _compareInfo = CompareInfo.GetCompareInfo(_cultureData.SCOMPAREINFO);
2061                 }
2062
2063                 return _compareInfo;
2064             }
2065         }
2066
2067
2068         internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
2069                                                                | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
2070                                                                | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
2071                                                                | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
2072
2073         internal static void ValidateStyles(DateTimeStyles style, String parameterName)
2074         {
2075             if ((style & InvalidDateTimeStyles) != 0)
2076             {
2077                 throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
2078             }
2079             if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
2080             {
2081                 throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
2082             }
2083             if (((style & DateTimeStyles.RoundtripKind) != 0)
2084                 && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0))
2085             {
2086                 throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName);
2087             }
2088         }
2089
2090         //
2091         // Actions: Return the internal flag used in formatting and parsing.
2092         //  The flag can be used to indicate things like if genitive forms is used in this DTFi, or if leap year gets different month names.
2093         //
2094         internal DateTimeFormatFlags FormatFlags => formatFlags != DateTimeFormatFlags.NotInitialized ? formatFlags : InitializeFormatFlags();
2095         [MethodImpl(MethodImplOptions.NoInlining)]
2096         private DateTimeFormatFlags InitializeFormatFlags()
2097         {
2098             // Build the format flags from the data in this DTFI
2099             formatFlags = 
2100                 (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
2101                     MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)) |
2102                 (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
2103                     MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)) |
2104                 (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames) |
2105                 (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
2106             return formatFlags;
2107         }
2108
2109         internal Boolean HasForceTwoDigitYears
2110         {
2111             get
2112             {
2113                 switch (calendar.ID)
2114                 {
2115                     // Handle Japanese and Taiwan cases.
2116                     // If is y/yy, do not get (year % 100). "y" will print
2117                     // year without leading zero.  "yy" will print year with two-digit in leading zero.
2118                     // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
2119                     // So year 5 is "05", and year 125 is "125".
2120                     // The reason for not doing (year % 100) is for Taiwan calendar.
2121                     // If year 125, then output 125 and not 25.
2122                     // Note: OS uses "yyyy" for Taiwan calendar by default.
2123                     case (CalendarId.JAPAN):
2124                     case (CalendarId.TAIWAN):
2125                         return true;
2126                 }
2127                 return false;
2128             }
2129         }
2130
2131         // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
2132         internal Boolean HasYearMonthAdjustment
2133         {
2134             get
2135             {
2136                 return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
2137             }
2138         }
2139
2140         // This is a callback that the parser can make back into the DTFI to let it fiddle with special
2141         // cases associated with that culture or calendar. Currently this only has special cases for
2142         // the Hebrew calendar, but this could be extended to other cultures.
2143         //
2144         // The return value is whether the year and month are actually valid for this calendar.
2145         internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName)
2146         {
2147             if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
2148             {
2149                 // Special rules to fix up the Hebrew year/month
2150
2151                 // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
2152                 // E.g. if the year is 5763, we only format as 763.
2153                 if (year < 1000)
2154                 {
2155                     year += 5000;
2156                 }
2157
2158                 // Because we need to calculate leap year, we should fall out now for an invalid year.
2159                 if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime))
2160                 {
2161                     return false;
2162                 }
2163
2164                 // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
2165                 // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
2166                 // shuffle the other months down.
2167                 if (parsedMonthName)
2168                 {
2169                     if (!Calendar.IsLeapYear(year))
2170                     {
2171                         if (month >= 8)
2172                         {
2173                             month--;
2174                         }
2175                         else if (month == 7)
2176                         {
2177                             return false;
2178                         }
2179                     }
2180                 }
2181             }
2182             return true;
2183         }
2184
2185         //
2186         // DateTimeFormatInfo tokenizer.  This is used by DateTime.Parse() to break input string into tokens.
2187         //
2188         private TokenHashValue[] _dtfiTokenHash;
2189
2190         private const int TOKEN_HASH_SIZE = 199;
2191         private const int SECOND_PRIME = 197;
2192         private const String dateSeparatorOrTimeZoneOffset = "-";
2193         private const String invariantDateSeparator = "/";
2194         private const String invariantTimeSeparator = ":";
2195
2196         //
2197         // Common Ignorable Symbols
2198         //
2199         internal const String IgnorablePeriod = ".";
2200         internal const String IgnorableComma = ",";
2201
2202         //
2203         // Year/Month/Day suffixes
2204         //
2205         internal const String CJKYearSuff = "\u5e74";
2206         internal const String CJKMonthSuff = "\u6708";
2207         internal const String CJKDaySuff = "\u65e5";
2208
2209         internal const String KoreanYearSuff = "\ub144";
2210         internal const String KoreanMonthSuff = "\uc6d4";
2211         internal const String KoreanDaySuff = "\uc77c";
2212
2213         internal const String KoreanHourSuff = "\uc2dc";
2214         internal const String KoreanMinuteSuff = "\ubd84";
2215         internal const String KoreanSecondSuff = "\ucd08";
2216
2217         internal const String CJKHourSuff = "\u6642";
2218         internal const String ChineseHourSuff = "\u65f6";
2219
2220         internal const String CJKMinuteSuff = "\u5206";
2221         internal const String CJKSecondSuff = "\u79d2";
2222
2223         internal const String LocalTimeMark = "T";
2224
2225         internal const String GMTName = "GMT";
2226         internal const String ZuluName = "Z";
2227
2228         internal const String KoreanLangName = "ko";
2229         internal const String JapaneseLangName = "ja";
2230         internal const String EnglishLangName = "en";
2231
2232         private static volatile DateTimeFormatInfo s_jajpDTFI;
2233         private static volatile DateTimeFormatInfo s_zhtwDTFI;
2234
2235         //
2236         // Create a Japanese DTFI which uses JapaneseCalendar.  This is used to parse
2237         // date string with Japanese era name correctly even when the supplied DTFI
2238         // does not use Japanese calendar.
2239         // The created instance is stored in global s_jajpDTFI.
2240         //
2241         internal static DateTimeFormatInfo GetJapaneseCalendarDTFI()
2242         {
2243             DateTimeFormatInfo temp = s_jajpDTFI;
2244             if (temp == null)
2245             {
2246                 temp = new CultureInfo("ja-JP", false).DateTimeFormat;
2247                 temp.Calendar = JapaneseCalendar.GetDefaultInstance();
2248                 s_jajpDTFI = temp;
2249             }
2250             return (temp);
2251         }
2252
2253         // Create a Taiwan DTFI which uses TaiwanCalendar.  This is used to parse
2254         // date string with era name correctly even when the supplied DTFI
2255         // does not use Taiwan calendar.
2256         // The created instance is stored in global s_zhtwDTFI.
2257         internal static DateTimeFormatInfo GetTaiwanCalendarDTFI()
2258         {
2259             DateTimeFormatInfo temp = s_zhtwDTFI;
2260             if (temp == null)
2261             {
2262                 temp = new CultureInfo("zh-TW", false).DateTimeFormat;
2263                 temp.Calendar = TaiwanCalendar.GetDefaultInstance();
2264                 s_zhtwDTFI = temp;
2265             }
2266             return (temp);
2267         }
2268
2269
2270         // DTFI properties should call this when the setter are called.
2271         private void ClearTokenHashTable()
2272         {
2273             _dtfiTokenHash = null;
2274             formatFlags = DateTimeFormatFlags.NotInitialized;
2275         }
2276
2277         internal TokenHashValue[] CreateTokenHashTable()
2278         {
2279             TokenHashValue[] temp = _dtfiTokenHash;
2280             if (temp == null)
2281             {
2282                 temp = new TokenHashValue[TOKEN_HASH_SIZE];
2283
2284                 bool koreanLanguage = LanguageName.Equals(KoreanLangName);
2285
2286                 string sep = this.TimeSeparator.Trim();
2287                 if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
2288                 if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
2289
2290                 if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep)
2291                 {
2292                     //
2293                     // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
2294                     // ja-JP and ko-KR.  In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
2295                     // DateTime.Parse behavior.  For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
2296                     // instead of SEP_HourSuff.
2297                     //
2298                     InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
2299                 }
2300
2301                 InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2302                 InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2303
2304                 // TODO: This ignores similar custom cultures
2305                 if (LanguageName.Equals("sq"))
2306                 {
2307                     // Albanian allows time formats like "12:00.PD"
2308                     InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2309                     InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2310                 }
2311
2312                 // CJK suffix
2313                 InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
2314                 InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
2315                 InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
2316                 InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
2317                 InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
2318                 InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
2319
2320                 InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
2321                 InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
2322                 InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2323                 InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
2324
2325                 // TODO: This ignores other custom cultures that might want to do something similar
2326                 if (koreanLanguage)
2327                 {
2328                     // Korean suffix
2329                     InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
2330                     InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2331                     InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
2332                 }
2333
2334                 if (LanguageName.Equals("ky"))
2335                 {
2336                     // For some cultures, the date separator works more like a comma, being allowed before or after any date part
2337                     InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
2338                 }
2339                 else
2340                 {
2341                     InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
2342                 }
2343
2344                 String[] dateWords = null;
2345                 DateTimeFormatInfoScanner scanner = null;
2346
2347                 // We need to rescan the date words since we're always synthetic
2348                 scanner = new DateTimeFormatInfoScanner();
2349                 dateWords = scanner.GetDateWordsOfDTFI(this);
2350                 // Ensure the formatflags is initialized.
2351                 DateTimeFormatFlags flag = FormatFlags;
2352
2353                 // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
2354                 // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
2355                 // This is determined in DateTimeFormatInfoScanner.  Use this flag to determine if we should treat date separator as ignorable symbol.
2356                 bool useDateSepAsIgnorableSymbol = false;
2357
2358                 String monthPostfix = null;
2359                 if (dateWords != null)
2360                 {
2361                     // There are DateWords.  It could be a real date word (such as "de"), or a monthPostfix.
2362                     // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
2363                     for (int i = 0; i < dateWords.Length; i++)
2364                     {
2365                         switch (dateWords[i][0])
2366                         {
2367                             // This is a month postfix
2368                             case DateTimeFormatInfoScanner.MonthPostfixChar:
2369                                 // Get the real month postfix.
2370                                 monthPostfix = dateWords[i].Substring(1);
2371                                 // Add the month name + postfix into the token.
2372                                 AddMonthNames(temp, monthPostfix);
2373                                 break;
2374                             case DateTimeFormatInfoScanner.IgnorableSymbolChar:
2375                                 String symbol = dateWords[i].Substring(1);
2376                                 InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
2377                                 if (this.DateSeparator.Trim(null).Equals(symbol))
2378                                 {
2379                                     // The date separator is the same as the ignorable symbol.
2380                                     useDateSepAsIgnorableSymbol = true;
2381                                 }
2382                                 break;
2383                             default:
2384                                 InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
2385                                 // TODO: This ignores similar custom cultures
2386                                 if (LanguageName.Equals("eu"))
2387                                 {
2388                                     // Basque has date words with leading dots
2389                                     InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
2390                                 }
2391                                 break;
2392                         }
2393                     }
2394                 }
2395
2396                 if (!useDateSepAsIgnorableSymbol)
2397                 {
2398                     // Use the normal date separator.
2399                     InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
2400                 }
2401                 // Add the regular month names.
2402                 AddMonthNames(temp, null);
2403
2404                 // Add the abbreviated month names.
2405                 for (int i = 1; i <= 13; i++)
2406                 {
2407                     InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
2408                 }
2409
2410
2411                 if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
2412                 {
2413                     for (int i = 1; i <= 13; i++)
2414                     {
2415                         String str;
2416                         str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
2417                         InsertHash(temp, str, TokenType.MonthToken, i);
2418                     }
2419                 }
2420
2421                 if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
2422                 {
2423                     for (int i = 1; i <= 13; i++)
2424                     {
2425                         String str;
2426                         str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
2427                         InsertHash(temp, str, TokenType.MonthToken, i);
2428                     }
2429                 }
2430
2431                 for (int i = 0; i < 7; i++)
2432                 {
2433                     //String str = GetDayOfWeekNames()[i];
2434                     // We have to call public methods here to work with inherited DTFI.
2435                     String str = GetDayName((DayOfWeek)i);
2436                     InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2437
2438                     str = GetAbbreviatedDayName((DayOfWeek)i);
2439                     InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2440                 }
2441
2442                 int[] eras = calendar.Eras;
2443                 for (int i = 1; i <= eras.Length; i++)
2444                 {
2445                     InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
2446                     InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
2447                 }
2448
2449                 // TODO: This ignores other cultures that might want to do something similar
2450                 if (LanguageName.Equals(JapaneseLangName))
2451                 {
2452                     // Japanese allows day of week forms like: "(Tue)"
2453                     for (int i = 0; i < 7; i++)
2454                     {
2455                         String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
2456                         InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
2457                     }
2458                     if (this.Calendar.GetType() != typeof(JapaneseCalendar))
2459                     {
2460                         // Special case for Japanese.  If this is a Japanese DTFI, and the calendar is not Japanese calendar,
2461                         // we will check Japanese Era name as well when the calendar is Gregorian.
2462                         DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
2463                         for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++)
2464                         {
2465                             InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
2466                             InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
2467                             // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2468                             InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i);
2469                         }
2470                     }
2471                 }
2472                 // TODO: This prohibits similar custom cultures, but we hard coded the name
2473                 else if (CultureName.Equals("zh-TW"))
2474                 {
2475                     DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
2476                     for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++)
2477                     {
2478                         if (twDtfi.GetEraName(i).Length > 0)
2479                         {
2480                             InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
2481                         }
2482                     }
2483                 }
2484
2485                 InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2486                 InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2487
2488                 // Add invariant month names and day names.
2489                 for (int i = 1; i <= 12; i++)
2490                 {
2491                     String str;
2492                     // We have to call public methods here to work with inherited DTFI.
2493                     // Insert the month name first, so that they are at the front of abbreviated
2494                     // month names.
2495                     str = InvariantInfo.GetMonthName(i);
2496                     InsertHash(temp, str, TokenType.MonthToken, i);
2497                     str = InvariantInfo.GetAbbreviatedMonthName(i);
2498                     InsertHash(temp, str, TokenType.MonthToken, i);
2499                 }
2500
2501                 for (int i = 0; i < 7; i++)
2502                 {
2503                     // We have to call public methods here to work with inherited DTFI.
2504                     String str = InvariantInfo.GetDayName((DayOfWeek)i);
2505                     InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2506
2507                     str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
2508                     InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2509                 }
2510
2511                 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
2512                 {
2513                     // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2514                     InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
2515                 }
2516
2517                 InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
2518                 InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0);
2519                 InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0);
2520
2521                 InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
2522                 InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
2523
2524                 _dtfiTokenHash = temp;
2525             }
2526             return (temp);
2527         }
2528
2529         private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
2530         {
2531             for (int i = 1; i <= 13; i++)
2532             {
2533                 String str;
2534                 //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
2535                 // We have to call public methods here to work with inherited DTFI.
2536                 // Insert the month name first, so that they are at the front of abbreviated
2537                 // month names.
2538                 str = GetMonthName(i);
2539                 if (str.Length > 0)
2540                 {
2541                     if (monthPostfix != null)
2542                     {
2543                         // Insert the month name with the postfix first, so it can be matched first.
2544                         InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
2545                     }
2546                     else
2547                     {
2548                         InsertHash(temp, str, TokenType.MonthToken, i);
2549                     }
2550                 }
2551                 str = GetAbbreviatedMonthName(i);
2552                 InsertHash(temp, str, TokenType.MonthToken, i);
2553             }
2554         }
2555
2556         ////////////////////////////////////////////////////////////////////////
2557         //
2558         // Actions:
2559         // Try to parse the current word to see if it is a Hebrew number.
2560         // Tokens will be updated accordingly.
2561         // This is called by the Lexer of DateTime.Parse().
2562         //
2563         // Unlike most of the functions in this class, the return value indicates
2564         // whether or not it started to parse. The badFormat parameter indicates
2565         // if parsing began, but the format was bad.
2566         //
2567         ////////////////////////////////////////////////////////////////////////
2568
2569         private static bool TryParseHebrewNumber(
2570             ref __DTString str,
2571             out Boolean badFormat,
2572             out int number)
2573         {
2574             number = -1;
2575             badFormat = false;
2576
2577             int i = str.Index;
2578             if (!HebrewNumber.IsDigit(str.Value[i]))
2579             {
2580                 // If the current character is not a Hebrew digit, just return false.
2581                 // There is no chance that we can parse a valid Hebrew number from here.
2582                 return (false);
2583             }
2584             // The current character is a Hebrew digit.  Try to parse this word as a Hebrew number.
2585             HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
2586             HebrewNumberParsingState state;
2587
2588             do
2589             {
2590                 state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
2591                 switch (state)
2592                 {
2593                     case HebrewNumberParsingState.InvalidHebrewNumber:    // Not a valid Hebrew number.
2594                     case HebrewNumberParsingState.NotHebrewDigit:         // The current character is not a Hebrew digit character.
2595                         // Break out so that we don't continue to try parse this as a Hebrew number.
2596                         return (false);
2597                 }
2598             } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
2599
2600             // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
2601             Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
2602                 "Invalid returned state from HebrewNumber.ParseByChar()");
2603
2604             if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)
2605             {
2606                 // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
2607                 return (false);
2608             }
2609
2610             // We have found a valid Hebrew number.  Update the index.
2611             str.Advance(i - str.Index);
2612
2613             // Get the final Hebrew number value from the HebrewNumberParsingContext.
2614             number = context.result;
2615
2616             return (true);
2617         }
2618
2619         private static bool IsHebrewChar(char ch)
2620         {
2621             return (ch >= '\x0590' && ch <= '\x05ff');
2622         }
2623
2624         internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
2625                                ref __DTString str)
2626         {
2627             tokenType = TokenType.UnknownToken;
2628             tokenValue = 0;
2629
2630             TokenHashValue value;
2631             Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
2632
2633             char ch = str.m_current;
2634             bool isLetter = Char.IsLetter(ch);
2635             if (isLetter)
2636             {
2637                 ch = this.Culture.TextInfo.ToLower(ch);
2638                 if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask)
2639                 {
2640                     bool badFormat;
2641                     if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue))
2642                     {
2643                         if (badFormat)
2644                         {
2645                             tokenType = TokenType.UnknownToken;
2646                             return (false);
2647                         }
2648                         // This is a Hebrew number.
2649                         // Do nothing here.  TryParseHebrewNumber() will update token accordingly.
2650                         tokenType = TokenType.HebrewNumber;
2651                         return (true);
2652                     }
2653                 }
2654             }
2655
2656
2657             int hashcode = ch % TOKEN_HASH_SIZE;
2658             int hashProbe = 1 + ch % SECOND_PRIME;
2659             int remaining = str.Length - str.Index;
2660             int i = 0;
2661
2662             TokenHashValue[] hashTable = _dtfiTokenHash;
2663             if (hashTable == null)
2664             {
2665                 hashTable = CreateTokenHashTable();
2666             }
2667             do
2668             {
2669                 value = hashTable[hashcode];
2670                 if (value == null)
2671                 {
2672                     // Not found.
2673                     break;
2674                 }
2675                 // Check this value has the right category (regular token or separator token) that we are looking for.
2676                 if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining)
2677                 {
2678                     bool compareStrings = true;
2679                     if (isLetter)
2680                     {
2681                         // If this token starts with a letter, make sure that we won't allow partial match.  So you can't tokenize "MarchWed" separately.
2682                         // Also an optimization to avoid string comparison
2683                         int nextCharIndex = str.Index + value.tokenString.Length;
2684                         if (nextCharIndex > str.Length)
2685                         {
2686                             compareStrings = false;
2687                         }
2688                         else if (nextCharIndex < str.Length)
2689                         {
2690                             // Check word boundary.  The next character should NOT be a letter.
2691                             char nextCh = str.Value[nextCharIndex];
2692                             compareStrings = !(Char.IsLetter(nextCh));
2693                         }
2694                     }
2695                     
2696                     if (compareStrings &&
2697                         ((value.tokenString.Length == 1 && str.Value[str.Index] == value.tokenString[0]) ||
2698                          Culture.CompareInfo.Compare(str.Value.Slice(str.Index, value.tokenString.Length), value.tokenString, CompareOptions.IgnoreCase) == 0))
2699                     {
2700                         tokenType = value.tokenType & TokenMask;
2701                         tokenValue = value.tokenValue;
2702                         str.Advance(value.tokenString.Length);
2703                         return (true);
2704                     }
2705                     else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) ||
2706                              (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames))
2707                     {
2708                         // For month or day token, we will match the names which have spaces.
2709                         int matchStrLen = 0;
2710                         if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen))
2711                         {
2712                             tokenType = value.tokenType & TokenMask;
2713                             tokenValue = value.tokenValue;
2714                             str.Advance(matchStrLen);
2715                             return (true);
2716                         }
2717                     }
2718                 }
2719                 i++;
2720                 hashcode += hashProbe;
2721                 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2722             } while (i < TOKEN_HASH_SIZE);
2723
2724             return (false);
2725         }
2726
2727         private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe)
2728         {
2729             // Remember the current slot.
2730             TokenHashValue previousNode = hashTable[hashcode];
2731
2732             //// Console.WriteLine("   Insert Key: {0} in {1}", str, slotToInsert);
2733             // Insert the new node into the current slot.
2734             hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); ;
2735
2736             while (++pos < TOKEN_HASH_SIZE)
2737             {
2738                 hashcode += hashProbe;
2739                 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2740                 // Remember this slot
2741                 TokenHashValue temp = hashTable[hashcode];
2742
2743                 if (temp != null && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != ch)
2744                 {
2745                     continue;
2746                 }
2747                 // Put the previous slot into this slot.
2748                 hashTable[hashcode] = previousNode;
2749                 //// Console.WriteLine("  Move {0} to slot {1}", previousNode.tokenString, hashcode);
2750                 if (temp == null)
2751                 {
2752                     // Done
2753                     return;
2754                 }
2755                 previousNode = temp;
2756             };
2757             Debug.Fail("The hashtable is full.  This should not happen.");
2758         }
2759
2760         private void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue)
2761         {
2762             // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
2763             if (str == null || str.Length == 0)
2764             {
2765                 return;
2766             }
2767             TokenHashValue value;
2768             int i = 0;
2769             // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
2770             // DateTime.Parse().
2771             if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1]))
2772             {
2773                 str = str.Trim(null);   // Trim white space characters.
2774                 // Could have space for separators
2775                 if (str.Length == 0)
2776                     return;
2777             }
2778             char ch = this.Culture.TextInfo.ToLower(str[0]);
2779             int hashcode = ch % TOKEN_HASH_SIZE;
2780             int hashProbe = 1 + ch % SECOND_PRIME;
2781             do
2782             {
2783                 value = hashTable[hashcode];
2784                 if (value == null)
2785                 {
2786                     //// Console.WriteLine("   Put Key: {0} in {1}", str, hashcode);
2787                     hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
2788                     return;
2789                 }
2790                 else
2791                 {
2792                     // Collision happens. Find another slot.
2793                     if (str.Length >= value.tokenString.Length)
2794                     {
2795                         // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
2796                         // the shorter ones.
2797                         if (this.CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length))
2798                         {
2799                             if (str.Length > value.tokenString.Length)
2800                             {
2801                                 // The str to be inserted has the same prefix as the current token, and str is longer.
2802                                 // Insert str into this node, and shift every node behind it.
2803                                 InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
2804                                 return;
2805                             }
2806                             else
2807                             {
2808                                 // Same token.  If they have different types (regular token vs separator token).  Add them.
2809                                 // If we have the same regular token or separator token in the hash already, do NOT update the hash.
2810                                 // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
2811
2812
2813                                 //
2814                                 // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , and insert the tokenType into the hash ONLY when we don't have a RegularToken yet.
2815                                 // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), and insert the tokenType into the hash ONLY when we don't have the SeparatorToken yet.
2816                                 //
2817
2818                                 int nTokenType = (int)tokenType;
2819                                 int nCurrentTokenTypeInHash = (int)value.tokenType;
2820
2821                                 //
2822                                 // The folowing is the fix for the issue of throwing FormatException when "mar" is passed in string of the short date format dd/MMM/yyyy for es-MX
2823                                 //
2824
2825                                 if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
2826                                     ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
2827                                 {
2828                                     value.tokenType |= tokenType;
2829                                     if (tokenValue != 0)
2830                                     {
2831                                         value.tokenValue = tokenValue;
2832                                     }
2833                                 }
2834                                 // The token to be inserted is already in the table.  Skip it.
2835                                 return;
2836                             }
2837                         }
2838                     }
2839                 }
2840                 //// Console.WriteLine("  COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
2841                 i++;
2842                 hashcode += hashProbe;
2843                 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2844             } while (i < TOKEN_HASH_SIZE);
2845             Debug.Fail("The hashtable is full.  This should not happen.");
2846         }
2847
2848         private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2)
2849         {
2850             // Optimize for one character cases which are common due to date and time separators (/ and :)
2851             if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2])
2852             {
2853                 return true;
2854             }
2855
2856             return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0);
2857         }
2858
2859         // class DateTimeFormatInfo
2860
2861         internal class TokenHashValue
2862         {
2863             internal String tokenString;
2864             internal TokenType tokenType;
2865             internal int tokenValue;
2866
2867             internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue)
2868             {
2869                 this.tokenString = tokenString;
2870                 this.tokenType = tokenType;
2871                 this.tokenValue = tokenValue;
2872             }
2873         }
2874     }
2875 }