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.
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
9 namespace System.Globalization
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).
20 internal enum MonthNameStyles
23 Genitive = 0x00000001,
24 LeapYear = 0x00000002,
28 // Flags used to indicate special rule used in parsing/formatting
29 // for a specific DateTimeFormatInfo instance.
30 // This is an internal flag.
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.
37 internal enum DateTimeFormatFlags
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.
51 public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
53 // cache for the invariant culture.
54 // invariantInfo is constant irrespective of your current culture.
55 private static volatile DateTimeFormatInfo s_invariantInfo;
57 // an index which points to a record in Culture Data Table.
58 private CultureData _cultureData;
60 // The culture name used to create this DTFI.
61 private String _name = null;
63 // The language name of the culture used to create this DTFI.
64 private String _langName = null;
66 // CompareInfo usually used by the parser.
67 private CompareInfo _compareInfo = null;
69 // Culture matches current DTFI. mainly used for string comparisons during parsing.
70 private CultureInfo _cultureInfo = null;
73 // Caches for various properties.
76 private String amDesignator = null;
77 private String pmDesignator = null;
79 private String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
81 private String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
83 private String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
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;
91 // The following are constant values.
93 private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
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'";
100 // The following are affected by calendar settings.
102 private Calendar calendar = null;
104 private int firstDayOfWeek = -1;
105 private int calendarWeekRule = -1;
108 private String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
110 private String[] abbreviatedDayNames = null;
113 private String[] m_superShortDayNames = null;
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.
120 private String[] genitiveMonthNames = null;
122 // Cache the abbreviated genitive month names that we retrieve from the data table.
124 private String[] m_genitiveAbbreviatedMonthNames = null;
126 // Cache the month names of a leap year that we retrieve from the data table.
128 private String[] leapYearMonthNames = null;
130 // For our "patterns" arrays we have 2 variables, a string and a string[]
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]
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;
143 private String[] allYearMonthPatterns = null;
145 private String[] allShortDatePatterns = null;
146 private String[] allLongDatePatterns = null;
147 private String[] allShortTimePatterns = null;
148 private String[] allLongTimePatterns = null;
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;
155 private CalendarId[] optionalCalendars = null;
157 private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
159 // CultureInfo updates this
160 internal bool _isReadOnly = false;
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.
165 private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
167 private String CultureName
173 _name = _cultureData.CultureName;
179 private CultureInfo Culture
183 if (_cultureInfo == null)
185 _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
191 // TODO: This ignores other cultures that might want to do something similar
192 private String LanguageName
196 if (_langName == null)
198 _langName = _cultureData.SISO639LANGNAME;
204 ////////////////////////////////////////////////////////////////////////////
206 // Create an array of string which contains the abbreviated day names.
208 ////////////////////////////////////////////////////////////////////////////
210 private string[] internalGetAbbreviatedDayOfWeekNames() => this.abbreviatedDayNames ?? internalGetAbbreviatedDayOfWeekNamesCore();
211 [MethodImpl(MethodImplOptions.NoInlining)]
212 private string[] internalGetAbbreviatedDayOfWeekNamesCore()
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;
220 ////////////////////////////////////////////////////////////////////////
222 // Action: Returns the string array of the one-letter day of week names.
224 // an array of one-letter day of week names
230 ////////////////////////////////////////////////////////////////////////
232 private string[] internalGetSuperShortDayNames() => this.m_superShortDayNames ?? internalGetSuperShortDayNamesCore();
233 [MethodImpl(MethodImplOptions.NoInlining)]
234 private string[] internalGetSuperShortDayNamesCore()
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;
242 ////////////////////////////////////////////////////////////////////////////
244 // Create an array of string which contains the day names.
246 ////////////////////////////////////////////////////////////////////////////
248 private string[] internalGetDayOfWeekNames() => this.dayNames ?? internalGetDayOfWeekNamesCore();
249 [MethodImpl(MethodImplOptions.NoInlining)]
250 private string[] internalGetDayOfWeekNamesCore()
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;
258 ////////////////////////////////////////////////////////////////////////////
260 // Create an array of string which contains the abbreviated month names.
262 ////////////////////////////////////////////////////////////////////////////
264 private String[] internalGetAbbreviatedMonthNames() => this.abbreviatedMonthNames ?? internalGetAbbreviatedMonthNamesCore();
265 [MethodImpl(MethodImplOptions.NoInlining)]
266 private String[] internalGetAbbreviatedMonthNamesCore()
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;
276 ////////////////////////////////////////////////////////////////////////////
278 // Create an array of string which contains the month names.
280 ////////////////////////////////////////////////////////////////////////////
282 private string[] internalGetMonthNames() => this.monthNames ?? internalGetMonthNamesCore();
283 [MethodImpl(MethodImplOptions.NoInlining)]
284 private string[] internalGetMonthNamesCore()
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;
295 // Invariant DateTimeFormatInfo doesn't have user-overriden values
296 // Default calendar is gregorian
297 public DateTimeFormatInfo()
298 : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance())
302 internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
304 Debug.Assert(cultureData != null);
305 Debug.Assert(cal != null);
307 // Remember our culture
308 _cultureData = cultureData;
313 private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId)
315 Debug.Assert(cultureData != null);
316 Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId");
318 if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
319 if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
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); }
326 this.allLongTimePatterns = _cultureData.LongTimes;
327 Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
329 this.allShortTimePatterns = _cultureData.ShortTimes;
330 Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
332 this.allLongDatePatterns = cultureData.LongDates(calendarId);
333 Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
335 this.allShortDatePatterns = cultureData.ShortDates(calendarId);
336 Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
338 this.allYearMonthPatterns = cultureData.YearMonths(calendarId);
339 Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
342 // Returns a default DateTimeFormatInfo that will be universally
343 // supported and constant irrespective of the current culture.
344 // Used by FromString methods.
347 public static DateTimeFormatInfo InvariantInfo
351 if (s_invariantInfo == null)
353 DateTimeFormatInfo info = new DateTimeFormatInfo();
354 info.Calendar.SetReadOnlyState(true);
355 info._isReadOnly = true;
356 s_invariantInfo = info;
358 return (s_invariantInfo);
362 // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
365 public static DateTimeFormatInfo CurrentInfo
369 System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
370 if (!culture._isInherited)
372 DateTimeFormatInfo info = culture.dateTimeInfo;
378 return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
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
389 public Object GetFormat(Type formatType)
391 return (formatType == typeof(DateTimeFormatInfo) ? this : null);
395 public Object Clone()
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;
406 public String AMDesignator
410 if (this.amDesignator == null)
412 this.amDesignator = _cultureData.SAM1159;
414 Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
415 return (this.amDesignator);
421 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
424 throw new ArgumentNullException(nameof(value),
425 SR.ArgumentNull_String);
427 ClearTokenHashTable();
428 amDesignator = value;
433 public Calendar Calendar
437 Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
438 return (this.calendar);
444 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
447 throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj);
449 if (value == calendar)
454 for (int i = 0; i < this.OptionalCalendars.Length; i++)
456 if (this.OptionalCalendars[i] == value.ID)
458 // We can use this one, so do so.
460 // Clean related properties if we already had a calendar set
461 if (calendar != null)
463 // clean related properties which are affected by the calendar setting,
464 // so that they will be refreshed when they are accessed next time.
467 // These properites are in the order as appearing in calendar.xml.
469 m_abbrevEraNames = null;
470 m_abbrevEnglishEraNames = null;
472 monthDayPattern = null;
475 abbreviatedDayNames = null;
476 m_superShortDayNames = null;
478 abbreviatedMonthNames = null;
479 genitiveMonthNames = null;
480 m_genitiveAbbreviatedMonthNames = null;
481 leapYearMonthNames = null;
482 formatFlags = DateTimeFormatFlags.NotInitialized;
484 allShortDatePatterns = null;
485 allLongDatePatterns = null;
486 allYearMonthPatterns = null;
487 dateTimeOffsetPattern = null;
489 // The defaults need reset as well:
490 longDatePattern = null;
491 shortDatePattern = null;
492 yearMonthPattern = null;
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
499 // Derived item that changes
500 dateSeparator = null;
502 // We don't need to do these because they are not changed by changing calendar
510 // remember to reload tokens
511 ClearTokenHashTable();
514 // Remember the new calendar
516 InitializeOverridableProperties(_cultureData, calendar.ID);
518 // We succeeded, return
523 // The assigned calendar is not a valid calendar for this culture, throw
524 throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar);
528 private CalendarId[] OptionalCalendars
532 if (this.optionalCalendars == null)
534 this.optionalCalendars = _cultureData.CalendarIds;
536 return (this.optionalCalendars);
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.
546 ============================================================================*/
549 public int GetEra(String eraName)
553 throw new ArgumentNullException(nameof(eraName),
554 SR.ArgumentNull_String);
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
561 // confer 85900 DTFI.GetEra("") should fail on all cultures
562 if (eraName.Length == 0)
567 // The following is based on the assumption that the era value is starting from 1, and has a
569 // If that ever changes, the code has to be changed.
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++)
575 // Compare the era name in a case-insensitive way for the appropriate culture.
576 if (m_eraNames[i].Length > 0)
578 if (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0)
584 for (int i = 0; i < AbbreviatedEraNames.Length; i++)
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)
592 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
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)
605 internal String[] EraNames
609 if (this.m_eraNames == null)
611 this.m_eraNames = _cultureData.EraNames(Calendar.ID); ;
613 return (this.m_eraNames);
617 /*=================================GetEraName==========================
618 **Action: Get the name of the era for the specified era value.
619 **Returns: The name of the specified era.
621 ** era the era value.
623 ** ArguementException if the era valie is invalid.
624 ============================================================================*/
626 // Era names are 1 indexed
627 public String GetEraName(int era)
629 if (era == Calendar.CurrentEra)
631 era = Calendar.CurrentEraValue;
634 // The following is based on the assumption that the era value is starting from 1, and has a
636 // If that ever changes, the code has to be changed.
637 if ((--era) < EraNames.Length && (era >= 0))
639 return (m_eraNames[era]);
641 throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
644 internal String[] AbbreviatedEraNames
648 if (this.m_abbrevEraNames == null)
650 this.m_abbrevEraNames = _cultureData.AbbrevEraNames(Calendar.ID);
652 return (this.m_abbrevEraNames);
656 // Era names are 1 indexed
657 public String GetAbbreviatedEraName(int era)
659 if (AbbreviatedEraNames.Length == 0)
661 // If abbreviation era name is not used in this culture,
662 // return the full era name.
663 return (GetEraName(era));
665 if (era == Calendar.CurrentEra)
667 era = Calendar.CurrentEraValue;
669 if ((--era) < m_abbrevEraNames.Length && (era >= 0))
671 return (m_abbrevEraNames[era]);
673 throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
676 internal String[] AbbreviatedEnglishEraNames
680 if (this.m_abbrevEnglishEraNames == null)
682 Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
683 this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
685 return (this.m_abbrevEnglishEraNames);
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
695 if (dateSeparator == null)
697 dateSeparator = _cultureData.DateSeparator(Calendar.ID);
699 Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
700 return dateSeparator;
705 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
709 throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
711 ClearTokenHashTable();
712 dateSeparator = value;
716 public DayOfWeek FirstDayOfWeek
720 if (this.firstDayOfWeek == -1)
722 this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK;
724 Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
726 return ((DayOfWeek)this.firstDayOfWeek);
732 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
733 if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday)
735 firstDayOfWeek = (int)value;
739 throw new ArgumentOutOfRangeException(
740 nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
741 DayOfWeek.Sunday, DayOfWeek.Saturday));
746 public CalendarWeekRule CalendarWeekRule
750 if (this.calendarWeekRule == -1)
752 this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR;
754 Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
755 return ((CalendarWeekRule)this.calendarWeekRule);
761 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
762 if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek)
764 calendarWeekRule = (int)value;
768 throw new ArgumentOutOfRangeException(
769 nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
770 CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
775 public String FullDateTimePattern
779 if (fullDateTimePattern == null)
781 fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
783 return (fullDateTimePattern);
789 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
792 throw new ArgumentNullException(nameof(value),
793 SR.ArgumentNull_String);
795 fullDateTimePattern = value;
800 // For our "patterns" arrays we have 2 variables, a string and a string[]
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
809 // Initialize our long date pattern from the 1st array value if not set
810 if (this.longDatePattern == null)
812 // Initialize our data
813 this.longDatePattern = this.UnclonedLongDatePatterns[0];
816 return this.longDatePattern;
822 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
825 throw new ArgumentNullException(nameof(value),
826 SR.ArgumentNull_String);
829 // Remember the new string
830 this.longDatePattern = value;
832 // Clear the token hash table
833 ClearTokenHashTable();
835 // Clean up cached values that will be affected by this property.
836 this.fullDateTimePattern = null;
840 // For our "patterns" arrays we have 2 variables, a string and a string[]
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
849 // Initialize our long time pattern from the 1st array value if not set
850 if (this.longTimePattern == null)
852 // Initialize our data
853 this.longTimePattern = this.UnclonedLongTimePatterns[0];
856 return this.longTimePattern;
862 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
865 throw new ArgumentNullException(nameof(value),
866 SR.ArgumentNull_String);
869 // Remember the new string
870 this.longTimePattern = value;
872 // Clear the token hash table
873 ClearTokenHashTable();
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;
883 // Note: just to be confusing there's only 1 month day pattern, not a whole list
884 public String MonthDayPattern
888 if (this.monthDayPattern == null)
890 Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
891 this.monthDayPattern = _cultureData.MonthDay(Calendar.ID);
893 Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
894 return (this.monthDayPattern);
900 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
903 throw new ArgumentNullException(nameof(value),
904 SR.ArgumentNull_String);
907 this.monthDayPattern = value;
912 public String PMDesignator
916 if (this.pmDesignator == null)
918 this.pmDesignator = _cultureData.SPM2359;
920 Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
921 return (this.pmDesignator);
927 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
930 throw new ArgumentNullException(nameof(value),
931 SR.ArgumentNull_String);
933 ClearTokenHashTable();
935 pmDesignator = value;
940 public String RFC1123Pattern
944 return (rfc1123Pattern);
948 // For our "patterns" arrays we have 2 variables, a string and a string[]
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
957 // Initialize our short date pattern from the 1st array value if not set
958 if (this.shortDatePattern == null)
960 // Initialize our data
961 this.shortDatePattern = this.UnclonedShortDatePatterns[0];
964 return this.shortDatePattern;
970 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
972 throw new ArgumentNullException(nameof(value),
973 SR.ArgumentNull_String);
975 // Remember the new string
976 this.shortDatePattern = value;
978 // Clear the token hash table, note that even short dates could require this
979 ClearTokenHashTable();
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;
989 // For our "patterns" arrays we have 2 variables, a string and a string[]
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
998 // Initialize our short time pattern from the 1st array value if not set
999 if (this.shortTimePattern == null)
1001 // Initialize our data
1002 this.shortTimePattern = this.UnclonedShortTimePatterns[0];
1004 return this.shortTimePattern;
1010 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1013 throw new ArgumentNullException(nameof(value),
1014 SR.ArgumentNull_String);
1017 // Remember the new string
1018 this.shortTimePattern = value;
1020 // Clear the token hash table, note that even short times could require this
1021 ClearTokenHashTable();
1023 // Clean up cached values that will be affected by this property.
1024 generalShortTimePattern = null; // General short date = short date + short time.
1029 public String SortableDateTimePattern
1033 return (sortableDateTimePattern);
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 ==============================================================================*/
1044 internal String GeneralShortTimePattern
1048 if (generalShortTimePattern == null)
1050 generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
1052 return (generalShortTimePattern);
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 ==============================================================================*/
1063 internal String GeneralLongTimePattern
1067 if (generalLongTimePattern == null)
1069 generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
1071 return (generalLongTimePattern);
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 ==============================================================================*/
1082 internal String DateTimeOffsetPattern
1086 if (dateTimeOffsetPattern == null)
1088 string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
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 */
1092 bool foundZ = false;
1093 bool inQuote = false;
1095 for (int i = 0; !foundZ && i < LongTimePattern.Length; i++)
1097 switch (LongTimePattern[i])
1100 /* if we aren't in a quote, we've found a z */
1102 /* we'll fall out of the loop now because the test includes !foundZ */
1106 if (inQuote && (quote == LongTimePattern[i]))
1108 /* we were in a quote and found a matching exit quote, so we are outside a quote now */
1113 quote = LongTimePattern[i];
1118 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
1123 i++; /* skip next character that is escaped by this backslash */
1132 dateTimePattern = dateTimePattern + " zzz";
1135 dateTimeOffsetPattern = dateTimePattern;
1137 return (dateTimeOffsetPattern);
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
1147 if (timeSeparator == null)
1149 timeSeparator = _cultureData.TimeSeparator;
1151 Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
1152 return (timeSeparator);
1158 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1162 throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
1165 ClearTokenHashTable();
1167 timeSeparator = value;
1171 public String UniversalSortableDateTimePattern
1175 return (universalSortableDateTimePattern);
1179 // For our "patterns" arrays we have 2 variables, a string and a string[]
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
1188 // Initialize our year/month pattern from the 1st array value if not set
1189 if (this.yearMonthPattern == null)
1191 // Initialize our data
1192 this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
1194 return this.yearMonthPattern;
1200 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1203 throw new ArgumentNullException(nameof(value),
1204 SR.ArgumentNull_String);
1207 // Remember the new string
1208 this.yearMonthPattern = value;
1210 // Clear the token hash table, note that even short times could require this
1211 ClearTokenHashTable();
1216 // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
1218 private static void CheckNullValue(String[] values, int length)
1220 Debug.Assert(values != null, "value != null");
1221 Debug.Assert(values.Length >= length);
1222 for (int i = 0; i < length; i++)
1224 if (values[i] == null)
1226 throw new ArgumentNullException("value",
1227 SR.ArgumentNull_ArrayValue);
1233 public String[] AbbreviatedDayNames
1237 return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
1243 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1246 throw new ArgumentNullException(nameof(value),
1247 SR.ArgumentNull_Array);
1249 if (value.Length != 7)
1251 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1253 CheckNullValue(value, value.Length);
1254 ClearTokenHashTable();
1256 abbreviatedDayNames = value;
1260 // Returns the string array of the one-letter day of week names.
1261 public String[] ShortestDayNames
1265 return ((String[])internalGetSuperShortDayNames().Clone());
1271 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1274 throw new ArgumentNullException(nameof(value),
1275 SR.ArgumentNull_Array);
1277 if (value.Length != 7)
1279 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1281 CheckNullValue(value, value.Length);
1282 this.m_superShortDayNames = value;
1287 public String[] DayNames
1291 return ((String[])internalGetDayOfWeekNames().Clone());
1297 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1300 throw new ArgumentNullException(nameof(value),
1301 SR.ArgumentNull_Array);
1303 if (value.Length != 7)
1305 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
1307 CheckNullValue(value, value.Length);
1308 ClearTokenHashTable();
1315 public String[] AbbreviatedMonthNames
1319 return ((String[])internalGetAbbreviatedMonthNames().Clone());
1325 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1328 throw new ArgumentNullException(nameof(value),
1329 SR.ArgumentNull_Array);
1331 if (value.Length != 13)
1333 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1335 CheckNullValue(value, value.Length - 1);
1336 ClearTokenHashTable();
1337 abbreviatedMonthNames = value;
1342 public String[] MonthNames
1346 return ((String[])internalGetMonthNames().Clone());
1352 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1355 throw new ArgumentNullException(nameof(value),
1356 SR.ArgumentNull_Array);
1358 if (value.Length != 13)
1360 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1362 CheckNullValue(value, value.Length - 1);
1364 ClearTokenHashTable();
1368 // Whitespaces that we allow in the month names.
1369 // U+00a0 is non-breaking space.
1370 private static readonly char[] s_monthSpaces = { ' ', '\u00a0' };
1372 internal bool HasSpacesInMonthNames
1376 return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
1380 internal bool HasSpacesInDayNames
1384 return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
1390 // internalGetMonthName
1392 // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
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.
1399 // ArgumentOutOfRangeException When month name is invalid.
1401 internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated)
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.
1407 String[] monthNamesArray = null;
1410 case MonthNameStyles.Genitive:
1411 monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
1413 case MonthNameStyles.LeapYear:
1414 monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
1417 monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames() : internalGetMonthNames());
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))
1424 throw new ArgumentOutOfRangeException(
1425 nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1426 1, monthNamesArray.Length));
1428 return (monthNamesArray[month - 1]);
1432 // internalGetGenitiveMonthNames
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.
1437 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1439 private String[] internalGetGenitiveMonthNames(bool abbreviated)
1443 if (this.m_genitiveAbbreviatedMonthNames == null)
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");
1449 return (this.m_genitiveAbbreviatedMonthNames);
1452 if (this.genitiveMonthNames == null)
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");
1458 return (this.genitiveMonthNames);
1462 // internalGetLeapYearMonthNames
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)
1468 internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/)
1470 if (this.leapYearMonthNames == null)
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");
1477 return (leapYearMonthNames);
1481 public String GetAbbreviatedDayName(DayOfWeek dayofweek)
1483 if ((int)dayofweek < 0 || (int)dayofweek > 6)
1485 throw new ArgumentOutOfRangeException(
1486 nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
1487 DayOfWeek.Sunday, DayOfWeek.Saturday));
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.
1493 return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
1496 // Returns the super short day of week names for the specified day of week.
1497 public string GetShortestDayName(DayOfWeek dayOfWeek)
1499 if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6)
1501 throw new ArgumentOutOfRangeException(
1502 nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range,
1503 DayOfWeek.Sunday, DayOfWeek.Saturday));
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.
1509 return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
1512 // Get all possible combination of inputs
1513 private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
1515 Debug.Assert(patterns1 != null);
1516 Debug.Assert(patterns2 != null);
1519 String[] result = new String[patterns1.Length * patterns2.Length];
1521 // Counter of actual results
1523 for (int i = 0; i < patterns1.Length; i++)
1525 for (int j = 0; j < patterns2.Length; j++)
1527 // Can't combine if null or empty
1528 result[k++] = patterns1[i] + connectString + patterns2[j];
1532 // Return the combinations
1536 public string[] GetAllDateTimePatterns()
1538 List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
1540 for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
1542 String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
1543 for (int j = 0; j < strings.Length; j++)
1545 results.Add(strings[j]);
1548 return results.ToArray();
1551 public string[] GetAllDateTimePatterns(char format)
1553 String[] result = null;
1558 result = this.AllShortDatePatterns;
1561 result = this.AllLongDatePatterns;
1564 result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
1568 result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
1571 result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
1574 result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
1578 result = new String[] { MonthDayPattern };
1582 result = new String[] { RoundtripFormat };
1586 result = new String[] { rfc1123Pattern };
1589 result = new String[] { sortableDateTimePattern };
1592 result = this.AllShortTimePatterns;
1595 result = this.AllLongTimePatterns;
1598 result = new String[] { UniversalSortableDateTimePattern };
1602 result = this.AllYearMonthPatterns;
1605 throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format));
1611 public String GetDayName(DayOfWeek dayofweek)
1613 if ((int)dayofweek < 0 || (int)dayofweek > 6)
1615 throw new ArgumentOutOfRangeException(
1616 nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
1617 DayOfWeek.Sunday, DayOfWeek.Saturday));
1620 // Use the internal one so that we don't clone the array unnecessarily
1621 return (internalGetDayOfWeekNames()[(int)dayofweek]);
1624 public String GetAbbreviatedMonthName(int month)
1626 if (month < 1 || month > 13)
1628 throw new ArgumentOutOfRangeException(
1629 nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1632 // Use the internal one so we don't clone the array unnecessarily
1633 return (internalGetAbbreviatedMonthNames()[month - 1]);
1636 public String GetMonthName(int month)
1638 if (month < 1 || month > 13)
1640 throw new ArgumentOutOfRangeException(
1641 nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
1644 // Use the internal one so we don't clone the array unnecessarily
1645 return (internalGetMonthNames()[month - 1]);
1648 // For our "patterns" arrays we have 2 variables, a string and a string[]
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]
1654 // The resulting [] can get returned to the calling app, so clone it.
1655 private static string[] GetMergedPatterns(string[] patterns, string defaultPattern)
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");
1662 // If the default happens to be the first in the list just return (a cloned) copy
1663 if (defaultPattern == patterns[0])
1665 return (string[])patterns.Clone();
1668 // We either need a bigger list, or the pattern from the list.
1670 for (i = 0; i < patterns.Length; i++)
1672 // Stop if we found it
1673 if (defaultPattern == patterns[i])
1677 // Either way we're going to need a new array
1678 string[] newPatterns;
1681 if (i < patterns.Length)
1683 // Found it, output will be same size
1684 newPatterns = (string[])patterns.Clone();
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];
1692 // Not found, make room for it
1693 newPatterns = new String[patterns.Length + 1];
1695 // Copy existing array
1696 Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
1699 // Remember the default
1700 newPatterns[0] = defaultPattern;
1702 // Return the reconstructed list
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";
1710 // Default string isn't necessarily in our string array, so get the
1711 // merged patterns of both
1712 private String[] AllYearMonthPatterns
1716 return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
1720 private String[] AllShortDatePatterns
1724 return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
1728 private String[] AllShortTimePatterns
1732 return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
1736 private String[] AllLongDatePatterns
1740 return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
1744 private String[] AllLongTimePatterns
1748 return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
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
1758 if (allYearMonthPatterns == null)
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");
1766 return allYearMonthPatterns;
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
1777 if (allShortDatePatterns == null)
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");
1785 return this.allShortDatePatterns;
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
1795 if (allLongDatePatterns == null)
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");
1803 return this.allLongDatePatterns;
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
1813 if (this.allShortTimePatterns == null)
1815 this.allShortTimePatterns = _cultureData.ShortTimes;
1816 Debug.Assert(this.allShortTimePatterns.Length > 0,
1817 "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
1820 return this.allShortTimePatterns;
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
1830 if (this.allLongTimePatterns == null)
1832 this.allLongTimePatterns = _cultureData.LongTimes;
1833 Debug.Assert(this.allLongTimePatterns.Length > 0,
1834 "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
1837 return this.allLongTimePatterns;
1841 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
1845 throw new ArgumentNullException(nameof(dtfi),
1846 SR.ArgumentNull_Obj);
1848 if (dtfi.IsReadOnly)
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;
1860 public bool IsReadOnly
1864 return (_isReadOnly);
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
1878 return _cultureData.CalendarName(Calendar.ID);
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
1887 // Most of the format characters that can be used in GetAllDateTimePatterns are
1888 // not really needed since they are one of the following:
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
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?)
1897 // WARNING: If more validation is ever done in one place, it should be done in the other.
1899 public void SetAllDateTimePatterns(String[] patterns, char format)
1902 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1904 if (patterns == null)
1906 throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array);
1909 if (patterns.Length == 0)
1911 throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns));
1915 for (int i = 0; i < patterns.Length; i++)
1917 if (patterns[i] == null)
1919 throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue);
1923 // Remember the patterns, and use the 1st as default
1927 allShortDatePatterns = patterns;
1928 shortDatePattern = allShortDatePatterns[0];
1932 allLongDatePatterns = patterns;
1933 longDatePattern = allLongDatePatterns[0];
1937 allShortTimePatterns = patterns;
1938 shortTimePattern = allShortTimePatterns[0];
1942 allLongTimePatterns = patterns;
1943 longTimePattern = allLongTimePatterns[0];
1948 allYearMonthPatterns = patterns;
1949 yearMonthPattern = allYearMonthPatterns[0];
1953 throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format));
1956 // Clear the token hash table, note that even short dates could require this
1957 ClearTokenHashTable();
1960 public String[] AbbreviatedMonthGenitiveNames
1964 return ((String[])internalGetGenitiveMonthNames(true).Clone());
1970 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1973 throw new ArgumentNullException(nameof(value),
1974 SR.ArgumentNull_Array);
1976 if (value.Length != 13)
1978 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
1980 CheckNullValue(value, value.Length - 1);
1981 ClearTokenHashTable();
1982 this.m_genitiveAbbreviatedMonthNames = value;
1986 public String[] MonthGenitiveNames
1990 return ((String[])internalGetGenitiveMonthNames(false).Clone());
1996 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1999 throw new ArgumentNullException(nameof(value),
2000 SR.ArgumentNull_Array);
2002 if (value.Length != 13)
2004 throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
2006 CheckNullValue(value, value.Length - 1);
2007 genitiveMonthNames = value;
2008 ClearTokenHashTable();
2013 // Positive TimeSpan Pattern
2015 private string _fullTimeSpanPositivePattern;
2016 internal String FullTimeSpanPositivePattern
2020 if (_fullTimeSpanPositivePattern == null)
2022 CultureData cultureDataWithoutUserOverrides;
2023 if (_cultureData.UseUserOverride)
2024 cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false);
2026 cultureDataWithoutUserOverrides = _cultureData;
2027 String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
2029 _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
2031 return _fullTimeSpanPositivePattern;
2036 // Negative TimeSpan Pattern
2038 private string _fullTimeSpanNegativePattern;
2039 internal String FullTimeSpanNegativePattern
2043 if (_fullTimeSpanNegativePattern == null)
2044 _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
2045 return _fullTimeSpanNegativePattern;
2050 // Get suitable CompareInfo from current DTFI object.
2052 internal CompareInfo CompareInfo
2056 if (_compareInfo == null)
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);
2063 return _compareInfo;
2068 internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
2069 | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
2070 | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
2071 | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
2073 internal static void ValidateStyles(DateTimeStyles style, String parameterName)
2075 if ((style & InvalidDateTimeStyles) != 0)
2077 throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
2079 if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
2081 throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
2083 if (((style & DateTimeStyles.RoundtripKind) != 0)
2084 && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0))
2086 throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName);
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.
2094 internal DateTimeFormatFlags FormatFlags => formatFlags != DateTimeFormatFlags.NotInitialized ? formatFlags : InitializeFormatFlags();
2095 [MethodImpl(MethodImplOptions.NoInlining)]
2096 private DateTimeFormatFlags InitializeFormatFlags()
2098 // Build the format flags from the data in this DTFI
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);
2109 internal Boolean HasForceTwoDigitYears
2113 switch (calendar.ID)
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):
2131 // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
2132 internal Boolean HasYearMonthAdjustment
2136 return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
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.
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)
2147 if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
2149 // Special rules to fix up the Hebrew year/month
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.
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))
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)
2169 if (!Calendar.IsLeapYear(year))
2175 else if (month == 7)
2186 // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
2188 private TokenHashValue[] _dtfiTokenHash;
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 = ":";
2197 // Common Ignorable Symbols
2199 internal const String IgnorablePeriod = ".";
2200 internal const String IgnorableComma = ",";
2203 // Year/Month/Day suffixes
2205 internal const String CJKYearSuff = "\u5e74";
2206 internal const String CJKMonthSuff = "\u6708";
2207 internal const String CJKDaySuff = "\u65e5";
2209 internal const String KoreanYearSuff = "\ub144";
2210 internal const String KoreanMonthSuff = "\uc6d4";
2211 internal const String KoreanDaySuff = "\uc77c";
2213 internal const String KoreanHourSuff = "\uc2dc";
2214 internal const String KoreanMinuteSuff = "\ubd84";
2215 internal const String KoreanSecondSuff = "\ucd08";
2217 internal const String CJKHourSuff = "\u6642";
2218 internal const String ChineseHourSuff = "\u65f6";
2220 internal const String CJKMinuteSuff = "\u5206";
2221 internal const String CJKSecondSuff = "\u79d2";
2223 internal const String LocalTimeMark = "T";
2225 internal const String GMTName = "GMT";
2226 internal const String ZuluName = "Z";
2228 internal const String KoreanLangName = "ko";
2229 internal const String JapaneseLangName = "ja";
2230 internal const String EnglishLangName = "en";
2232 private static volatile DateTimeFormatInfo s_jajpDTFI;
2233 private static volatile DateTimeFormatInfo s_zhtwDTFI;
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.
2241 internal static DateTimeFormatInfo GetJapaneseCalendarDTFI()
2243 DateTimeFormatInfo temp = s_jajpDTFI;
2246 temp = new CultureInfo("ja-JP", false).DateTimeFormat;
2247 temp.Calendar = JapaneseCalendar.GetDefaultInstance();
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()
2259 DateTimeFormatInfo temp = s_zhtwDTFI;
2262 temp = new CultureInfo("zh-TW", false).DateTimeFormat;
2263 temp.Calendar = TaiwanCalendar.GetDefaultInstance();
2270 // DTFI properties should call this when the setter are called.
2271 private void ClearTokenHashTable()
2273 _dtfiTokenHash = null;
2274 formatFlags = DateTimeFormatFlags.NotInitialized;
2277 internal TokenHashValue[] CreateTokenHashTable()
2279 TokenHashValue[] temp = _dtfiTokenHash;
2282 temp = new TokenHashValue[TOKEN_HASH_SIZE];
2284 bool koreanLanguage = LanguageName.Equals(KoreanLangName);
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);
2290 if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep)
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.
2298 InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
2301 InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2302 InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2304 // TODO: This ignores similar custom cultures
2305 if (LanguageName.Equals("sq"))
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);
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);
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);
2325 // TODO: This ignores other custom cultures that might want to do something similar
2329 InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
2330 InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2331 InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
2334 if (LanguageName.Equals("ky"))
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);
2341 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
2344 String[] dateWords = null;
2345 DateTimeFormatInfoScanner scanner = null;
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;
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;
2358 String monthPostfix = null;
2359 if (dateWords != null)
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++)
2365 switch (dateWords[i][0])
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);
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))
2379 // The date separator is the same as the ignorable symbol.
2380 useDateSepAsIgnorableSymbol = true;
2384 InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
2385 // TODO: This ignores similar custom cultures
2386 if (LanguageName.Equals("eu"))
2388 // Basque has date words with leading dots
2389 InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
2396 if (!useDateSepAsIgnorableSymbol)
2398 // Use the normal date separator.
2399 InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
2401 // Add the regular month names.
2402 AddMonthNames(temp, null);
2404 // Add the abbreviated month names.
2405 for (int i = 1; i <= 13; i++)
2407 InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
2411 if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
2413 for (int i = 1; i <= 13; i++)
2416 str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
2417 InsertHash(temp, str, TokenType.MonthToken, i);
2421 if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
2423 for (int i = 1; i <= 13; i++)
2426 str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
2427 InsertHash(temp, str, TokenType.MonthToken, i);
2431 for (int i = 0; i < 7; i++)
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);
2438 str = GetAbbreviatedDayName((DayOfWeek)i);
2439 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2442 int[] eras = calendar.Eras;
2443 for (int i = 1; i <= eras.Length; i++)
2445 InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
2446 InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
2449 // TODO: This ignores other cultures that might want to do something similar
2450 if (LanguageName.Equals(JapaneseLangName))
2452 // Japanese allows day of week forms like: "(Tue)"
2453 for (int i = 0; i < 7; i++)
2455 String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
2456 InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
2458 if (this.Calendar.GetType() != typeof(JapaneseCalendar))
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++)
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);
2472 // TODO: This prohibits similar custom cultures, but we hard coded the name
2473 else if (CultureName.Equals("zh-TW"))
2475 DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
2476 for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++)
2478 if (twDtfi.GetEraName(i).Length > 0)
2480 InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
2485 InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2486 InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2488 // Add invariant month names and day names.
2489 for (int i = 1; i <= 12; i++)
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
2495 str = InvariantInfo.GetMonthName(i);
2496 InsertHash(temp, str, TokenType.MonthToken, i);
2497 str = InvariantInfo.GetAbbreviatedMonthName(i);
2498 InsertHash(temp, str, TokenType.MonthToken, i);
2501 for (int i = 0; i < 7; i++)
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);
2507 str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
2508 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2511 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
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);
2517 InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
2518 InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0);
2519 InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0);
2521 InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
2522 InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
2524 _dtfiTokenHash = temp;
2529 private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
2531 for (int i = 1; i <= 13; i++)
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
2538 str = GetMonthName(i);
2541 if (monthPostfix != null)
2543 // Insert the month name with the postfix first, so it can be matched first.
2544 InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
2548 InsertHash(temp, str, TokenType.MonthToken, i);
2551 str = GetAbbreviatedMonthName(i);
2552 InsertHash(temp, str, TokenType.MonthToken, i);
2556 ////////////////////////////////////////////////////////////////////////
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().
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.
2567 ////////////////////////////////////////////////////////////////////////
2569 private static bool TryParseHebrewNumber(
2571 out Boolean badFormat,
2578 if (!HebrewNumber.IsDigit(str.Value[i]))
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.
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;
2590 state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
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.
2598 } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
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()");
2604 if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)
2606 // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
2610 // We have found a valid Hebrew number. Update the index.
2611 str.Advance(i - str.Index);
2613 // Get the final Hebrew number value from the HebrewNumberParsingContext.
2614 number = context.result;
2619 private static bool IsHebrewChar(char ch)
2621 return (ch >= '\x0590' && ch <= '\x05ff');
2624 internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
2627 tokenType = TokenType.UnknownToken;
2630 TokenHashValue value;
2631 Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
2633 char ch = str.m_current;
2634 bool isLetter = Char.IsLetter(ch);
2637 ch = this.Culture.TextInfo.ToLower(ch);
2638 if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask)
2641 if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue))
2645 tokenType = TokenType.UnknownToken;
2648 // This is a Hebrew number.
2649 // Do nothing here. TryParseHebrewNumber() will update token accordingly.
2650 tokenType = TokenType.HebrewNumber;
2657 int hashcode = ch % TOKEN_HASH_SIZE;
2658 int hashProbe = 1 + ch % SECOND_PRIME;
2659 int remaining = str.Length - str.Index;
2662 TokenHashValue[] hashTable = _dtfiTokenHash;
2663 if (hashTable == null)
2665 hashTable = CreateTokenHashTable();
2669 value = hashTable[hashcode];
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)
2678 bool compareStrings = true;
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)
2686 compareStrings = false;
2688 else if (nextCharIndex < str.Length)
2690 // Check word boundary. The next character should NOT be a letter.
2691 char nextCh = str.Value[nextCharIndex];
2692 compareStrings = !(Char.IsLetter(nextCh));
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))
2700 tokenType = value.tokenType & TokenMask;
2701 tokenValue = value.tokenValue;
2702 str.Advance(value.tokenString.Length);
2705 else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) ||
2706 (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames))
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))
2712 tokenType = value.tokenType & TokenMask;
2713 tokenValue = value.tokenValue;
2714 str.Advance(matchStrLen);
2720 hashcode += hashProbe;
2721 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2722 } while (i < TOKEN_HASH_SIZE);
2727 private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe)
2729 // Remember the current slot.
2730 TokenHashValue previousNode = hashTable[hashcode];
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); ;
2736 while (++pos < TOKEN_HASH_SIZE)
2738 hashcode += hashProbe;
2739 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2740 // Remember this slot
2741 TokenHashValue temp = hashTable[hashcode];
2743 if (temp != null && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != ch)
2747 // Put the previous slot into this slot.
2748 hashTable[hashcode] = previousNode;
2749 //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
2755 previousNode = temp;
2757 Debug.Fail("The hashtable is full. This should not happen.");
2760 private void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue)
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)
2767 TokenHashValue value;
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]))
2773 str = str.Trim(null); // Trim white space characters.
2774 // Could have space for separators
2775 if (str.Length == 0)
2778 char ch = this.Culture.TextInfo.ToLower(str[0]);
2779 int hashcode = ch % TOKEN_HASH_SIZE;
2780 int hashProbe = 1 + ch % SECOND_PRIME;
2783 value = hashTable[hashcode];
2786 //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
2787 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
2792 // Collision happens. Find another slot.
2793 if (str.Length >= value.tokenString.Length)
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))
2799 if (str.Length > value.tokenString.Length)
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);
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.
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.
2818 int nTokenType = (int)tokenType;
2819 int nCurrentTokenTypeInHash = (int)value.tokenType;
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
2825 if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
2826 ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
2828 value.tokenType |= tokenType;
2829 if (tokenValue != 0)
2831 value.tokenValue = tokenValue;
2834 // The token to be inserted is already in the table. Skip it.
2840 //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
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.");
2848 private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2)
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])
2856 return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0);
2859 // class DateTimeFormatInfo
2861 internal class TokenHashValue
2863 internal String tokenString;
2864 internal TokenType tokenType;
2865 internal int tokenValue;
2867 internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue)
2869 this.tokenString = tokenString;
2870 this.tokenType = tokenType;
2871 this.tokenValue = tokenValue;