3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 namespace System.Globalization {
10 using System.Threading;
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.Runtime.Serialization;
14 using System.Security.Permissions;
15 using System.Runtime.InteropServices;
16 using System.Runtime.Versioning;
18 using System.Diagnostics.Contracts;
21 // Flags used to indicate different styles of month names.
22 // This is an internal flag used by internalGetMonthName().
23 // Use flag here in case that we need to provide a combination of these styles
24 // (such as month name of a leap year in genitive form. Not likely for now,
25 // but would like to keep the option open).
29 internal enum MonthNameStyles {
31 Genitive = 0x00000001,
32 LeapYear = 0x00000002,
36 // Flags used to indicate special rule used in parsing/formatting
37 // for a specific DateTimeFormatInfo instance.
38 // This is an internal flag.
40 // This flag is different from MonthNameStyles because this flag
41 // can be expanded to accomodate parsing behaviors like CJK month names
42 // or alternative month names, etc.
45 internal enum DateTimeFormatFlags {
47 UseGenitiveMonth = 0x00000001,
48 UseLeapYearMonth = 0x00000002,
49 UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
50 UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule.
51 UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
52 UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
59 [System.Runtime.InteropServices.ComVisible(true)]
60 public sealed class DateTimeFormatInfo : ICloneable, IFormatProvider
63 // Note, some fields are derived so don't really need to be serialized, but we can't mark as
64 // optional because Whidbey was expecting them. Post-Arrowhead we could fix that
65 // once Whidbey serialization is no longer necessary.
68 // cache for the invariant culture.
69 // invariantInfo is constant irrespective of your current culture.
70 private static volatile DateTimeFormatInfo invariantInfo;
72 // an index which points to a record in Culture Data Table.
73 [NonSerialized]private CultureData m_cultureData;
75 // The culture name used to create this DTFI.
76 [OptionalField(VersionAdded = 2)]
77 internal String m_name = null;
79 // The language name of the culture used to create this DTFI.
80 [NonSerialized]private String m_langName = null;
82 // CompareInfo usually used by the parser.
83 [NonSerialized]private CompareInfo m_compareInfo = null;
85 // Culture matches current DTFI. mainly used for string comparisons during parsing.
86 [NonSerialized]private CultureInfo m_cultureInfo = null;
89 // Caches for various properties.
94 internal String amDesignator = null;
95 internal String pmDesignator = null;
96 [OptionalField(VersionAdded = 1)]
97 internal String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
98 [OptionalField(VersionAdded = 1)]
99 internal String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
100 [OptionalField(VersionAdded = 1)]
101 internal String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
102 [OptionalField(VersionAdded = 1)]
103 internal String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
104 internal String monthDayPattern = null;
105 [OptionalField(VersionAdded = 2)] // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
106 internal String dateTimeOffsetPattern = null;
109 // The following are constant values.
111 internal const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
113 // The sortable pattern is based on ISO 8601.
114 internal const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
115 internal const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
118 // The following are affected by calendar settings.
120 internal Calendar calendar = null;
122 internal int firstDayOfWeek = -1;
123 internal int calendarWeekRule = -1;
126 [OptionalField(VersionAdded = 1)]
127 internal String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
129 internal String[] abbreviatedDayNames = null;
131 [OptionalField(VersionAdded = 2)]
132 internal String[] m_superShortDayNames = null;
134 internal String[] dayNames = null;
135 internal String[] abbreviatedMonthNames = null;
136 internal String[] monthNames = null;
137 // Cache the genitive month names that we retrieve from the data table.
138 [OptionalField(VersionAdded = 2)]
139 internal String[] genitiveMonthNames = null;
141 // Cache the abbreviated genitive month names that we retrieve from the data table.
142 [OptionalField(VersionAdded = 2)]
143 internal String[] m_genitiveAbbreviatedMonthNames = null;
145 // Cache the month names of a leap year that we retrieve from the data table.
146 [OptionalField(VersionAdded = 2)]
147 internal String[] leapYearMonthNames = null;
149 // For our "patterns" arrays we have 2 variables, a string and a string[]
151 // The string[] contains the list of patterns, EXCEPT the default may not be included.
152 // The string contains the default pattern.
153 // When we initially construct our string[], we set the string to string[0]
155 // The "default" Date/time patterns
156 internal String longDatePattern = null;
157 internal String shortDatePattern = null;
158 internal String yearMonthPattern = null;
159 internal String longTimePattern = null;
160 internal String shortTimePattern = null;
162 // These are Whidbey-serialization compatable arrays (eg: default not included)
163 // "all" is a bit of a misnomer since the "default" pattern stored above isn't
164 // necessarily a member of the list
165 [OptionalField(VersionAdded = 3)]
166 private String[] allYearMonthPatterns = null; // This was wasn't serialized in Whidbey
167 internal String[] allShortDatePatterns = null;
168 internal String[] allLongDatePatterns = null;
169 internal String[] allShortTimePatterns = null;
170 internal String[] allLongTimePatterns = null;
172 // Cache the era names for this DateTimeFormatInfo instance.
173 internal String[] m_eraNames = null;
174 internal String[] m_abbrevEraNames = null;
175 internal String[] m_abbrevEnglishEraNames = null;
177 internal int[] optionalCalendars = null;
179 private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
181 // CultureInfo updates this
182 internal bool m_isReadOnly=false;
184 // This flag gives hints about if formatting/parsing should perform special code path for things like
185 // genitive form or leap year month names.
186 [OptionalField(VersionAdded = 2)]
187 internal DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
188 internal static bool preferExistingTokens = InitPreferExistingTokens();
191 [System.Security.SecuritySafeCritical]
192 static bool InitPreferExistingTokens()
195 #if !FEATURE_CORECLR && !MONO
196 ret = DateTime.LegacyParseMode();
203 private String CultureName
209 m_name = this.m_cultureData.CultureName;
215 private CultureInfo Culture
219 if (m_cultureInfo == null)
221 m_cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
223 return m_cultureInfo;
228 private String LanguageName
230 [System.Security.SecurityCritical] // auto-generated
233 if (m_langName == null)
235 m_langName = this.m_cultureData.SISO639LANGNAME;
241 ////////////////////////////////////////////////////////////////////////////
243 // Create an array of string which contains the abbreviated day names.
245 ////////////////////////////////////////////////////////////////////////////
247 private String[] internalGetAbbreviatedDayOfWeekNames()
249 if (this.abbreviatedDayNames == null)
251 // Get the abbreviated day names for our current calendar
252 this.abbreviatedDayNames = this.m_cultureData.AbbreviatedDayNames(Calendar.ID);
253 Contract.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week");
255 return (this.abbreviatedDayNames);
258 ////////////////////////////////////////////////////////////////////////
260 // Action: Returns the string array of the one-letter day of week names.
262 // an array of one-letter day of week names
268 ////////////////////////////////////////////////////////////////////////
270 private String[] internalGetSuperShortDayNames()
272 if (this.m_superShortDayNames == null)
274 // Get the super short day names for our current calendar
275 this.m_superShortDayNames = this.m_cultureData.SuperShortDayNames(Calendar.ID);
276 Contract.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week");
278 return (this.m_superShortDayNames);
281 ////////////////////////////////////////////////////////////////////////////
283 // Create an array of string which contains the day names.
285 ////////////////////////////////////////////////////////////////////////////
287 private String[] internalGetDayOfWeekNames()
289 if (this.dayNames == null)
291 // Get the day names for our current calendar
292 this.dayNames = this.m_cultureData.DayNames(Calendar.ID);
293 Contract.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
295 return (this.dayNames);
298 ////////////////////////////////////////////////////////////////////////////
300 // Create an array of string which contains the abbreviated month names.
302 ////////////////////////////////////////////////////////////////////////////
304 private String[] internalGetAbbreviatedMonthNames()
306 if (this.abbreviatedMonthNames == null)
308 // Get the month names for our current calendar
309 this.abbreviatedMonthNames = this.m_cultureData.AbbreviatedMonthNames(Calendar.ID);
310 Contract.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13,
311 "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
313 return (this.abbreviatedMonthNames);
317 ////////////////////////////////////////////////////////////////////////////
319 // Create an array of string which contains the month names.
321 ////////////////////////////////////////////////////////////////////////////
323 private String[] internalGetMonthNames()
325 if (this.monthNames == null)
327 // Get the month names for our current calendar
328 this.monthNames = this.m_cultureData.MonthNames(Calendar.ID);
329 Contract.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13,
330 "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
333 return (this.monthNames);
338 // Invariant DateTimeFormatInfo doesn't have user-overriden values
339 // Default calendar is gregorian
340 public DateTimeFormatInfo()
341 : this(CultureInfo.InvariantCulture.m_cultureData,
342 GregorianCalendar.GetDefaultInstance())
346 internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
348 Contract.Requires(cultureData != null);
349 Contract.Requires(cal != null);
351 // Remember our culture
352 this.m_cultureData = cultureData;
354 // m_isDefaultCalendar is set in the setter of Calendar below.
359 [System.Security.SecuritySafeCritical]
361 private void InitializeOverridableProperties(CultureData cultureData, int calendarID)
364 // Silverlight 2.0 never took a snapshot of the user's overridable properties
365 // This has a substantial performance impact so skip when CoreCLR
366 Contract.Requires(cultureData != null);
367 Contract.Assert(calendarID > 0, "[DateTimeFormatInfo.Populate] Expected Calendar.ID > 0");
369 if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
370 if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
372 if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
373 if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
374 if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
375 if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarID); }
377 this.allLongTimePatterns = this.m_cultureData.LongTimes;
378 Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
380 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
381 Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
383 this.allLongDatePatterns = cultureData.LongDates(calendarID);
384 Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
386 this.allShortDatePatterns = cultureData.ShortDates(calendarID);
387 Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
389 this.allYearMonthPatterns = cultureData.YearMonths(calendarID);
390 Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
394 #region Serialization
395 // The following fields are defined to keep the serialization compatibility with .NET V1.0/V1.1.
396 [OptionalField(VersionAdded = 1)]
397 private int CultureID;
398 [OptionalField(VersionAdded = 1)]
399 private bool m_useUserOverride;
401 [OptionalField(VersionAdded = 1)]
402 private bool bUseCalendarInfo;
403 [OptionalField(VersionAdded = 1)]
404 private int nDataItem;
405 [OptionalField(VersionAdded = 2)]
406 internal bool m_isDefaultCalendar; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
407 [OptionalField(VersionAdded = 2)]
408 private static volatile Hashtable s_calendarNativeNames; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
409 #endif // !FEATURE_CORECLR
411 // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string
412 // Now we always synthesize so its not helpful
413 [OptionalField(VersionAdded = 1)]
414 internal String[] m_dateWords = null; // calculated, no need to serialze (whidbey expects, arrowhead doesn't)
417 private void OnDeserialized(StreamingContext ctx)
419 if (this.m_name != null)
421 m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
423 if (this.m_cultureData == null)
424 throw new CultureNotFoundException(
425 "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
427 // Note: This is for Everett compatibility
431 m_cultureData = CultureData.GetCultureData(CultureID, m_useUserOverride);
433 if (calendar == null)
435 calendar = (Calendar) GregorianCalendar.GetDefaultInstance().Clone();
436 calendar.SetReadOnlyState(m_isReadOnly);
440 CultureInfo.CheckDomainSafetyObject(calendar, this);
442 InitializeOverridableProperties(m_cultureData, calendar.ID);
445 // turn off read only state till we finish initializing all fields and then store read only state after we are done.
447 bool isReadOnly = m_isReadOnly;
448 m_isReadOnly = false;
450 // If we deserialized defaults ala Whidbey, make sure they're still defaults
451 // Whidbey's arrays could get a bit mixed up.
452 if (longDatePattern != null) this.LongDatePattern = longDatePattern;
453 if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern;
454 if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern;
455 if (longTimePattern != null) this.LongTimePattern = longTimePattern;
456 if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern;
458 m_isReadOnly = isReadOnly;
462 private void OnSerializing(StreamingContext ctx)
465 CultureID = this.m_cultureData.ILANGUAGE; // Used for serialization compatibility with Whidbey which didn't always serialize the name
467 m_useUserOverride = this.m_cultureData.UseUserOverride;
469 // make sure the m_name is initialized.
470 m_name = this.CultureName;
473 if (s_calendarNativeNames == null)
474 s_calendarNativeNames = new Hashtable();
475 #endif // FEATURE_CORECLR
477 // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey
478 // because Whidbey try to initialize some of these fields using calendar data which could be null values
479 // and then we get exceptions. So we call the accessors to force the caches to get loaded.
481 o = this.LongTimePattern;
482 o = this.LongDatePattern;
483 o = this.ShortTimePattern;
484 o = this.ShortDatePattern;
485 o = this.YearMonthPattern;
486 o = this.AllLongTimePatterns;
487 o = this.AllLongDatePatterns;
488 o = this.AllShortTimePatterns;
489 o = this.AllShortDatePatterns;
490 o = this.AllYearMonthPatterns;
492 #endregion Serialization
494 // Returns a default DateTimeFormatInfo that will be universally
495 // supported and constant irrespective of the current culture.
496 // Used by FromString methods.
499 public static DateTimeFormatInfo InvariantInfo {
501 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
502 if (invariantInfo == null)
504 DateTimeFormatInfo info = new DateTimeFormatInfo();
505 info.Calendar.SetReadOnlyState(true);
506 info.m_isReadOnly = true;
507 invariantInfo = info;
509 return (invariantInfo);
513 // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
516 public static DateTimeFormatInfo CurrentInfo {
518 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
519 System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
520 if (!culture.m_isInherited) {
521 DateTimeFormatInfo info = culture.dateTimeInfo;
526 return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
531 public static DateTimeFormatInfo GetInstance(IFormatProvider provider) {
532 // Fast case for a regular CultureInfo
533 DateTimeFormatInfo info;
534 CultureInfo cultureProvider = provider as CultureInfo;
535 if (cultureProvider != null && !cultureProvider.m_isInherited)
537 return cultureProvider.DateTimeFormat;
539 // Fast case for a DTFI;
540 info = provider as DateTimeFormatInfo;
544 // Wasn't cultureInfo or DTFI, do it the slower way
545 if (provider != null) {
546 info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo;
551 // Couldn't get anything, just use currentInfo as fallback
556 public Object GetFormat(Type formatType)
558 return (formatType == typeof(DateTimeFormatInfo)? this: null);
562 public Object Clone()
564 DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
565 // We can use the data member calendar in the setter, instead of the property Calendar,
566 // since the cloned copy should have the same state as the original copy.
567 n.calendar = (Calendar) this.Calendar.Clone();
568 n.m_isReadOnly = false;
573 public String AMDesignator
576 [System.Security.SecuritySafeCritical] // auto-generated
581 if (this.amDesignator == null)
583 this.amDesignator = this.m_cultureData.SAM1159;
586 Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
587 return (this.amDesignator);
593 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
596 throw new ArgumentNullException("value",
597 Environment.GetResourceString("ArgumentNull_String"));
599 Contract.EndContractBlock();
600 ClearTokenHashTable();
601 amDesignator = value;
606 public Calendar Calendar {
608 Contract.Ensures(Contract.Result<Calendar>() != null);
610 Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
611 return (this.calendar);
616 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
618 throw new ArgumentNullException("value",
619 Environment.GetResourceString("ArgumentNull_Obj"));
621 Contract.EndContractBlock();
622 if (value == calendar) {
627 // Because the culture is agile object which can be attached to a thread and then thread can travel
628 // to another app domain then we prevent attaching any customized object to culture that we cannot contol.
630 CultureInfo.CheckDomainSafetyObject(value, this);
632 for (int i = 0; i < this.OptionalCalendars.Length; i++)
634 if (this.OptionalCalendars[i] == value.ID)
636 // We can use this one, so do so.
638 // Clean related properties if we already had a calendar set
639 if (calendar != null)
641 // clean related properties which are affected by the calendar setting,
642 // so that they will be refreshed when they are accessed next time.
645 // These properites are in the order as appearing in calendar.xml.
647 m_abbrevEraNames = null;
648 m_abbrevEnglishEraNames = null;
650 monthDayPattern = null;
653 abbreviatedDayNames = null;
654 m_superShortDayNames = null;
656 abbreviatedMonthNames = null;
657 genitiveMonthNames = null;
658 m_genitiveAbbreviatedMonthNames = null;
659 leapYearMonthNames = null;
660 formatFlags = DateTimeFormatFlags.NotInitialized;
662 allShortDatePatterns = null;
663 allLongDatePatterns = null;
664 allYearMonthPatterns = null;
665 dateTimeOffsetPattern = null;
667 // The defaults need reset as well:
668 longDatePattern = null;
669 shortDatePattern = null;
670 yearMonthPattern = null;
672 // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
673 fullDateTimePattern = null; // Long date + long time
674 generalShortTimePattern = null; // short date + short time
675 generalLongTimePattern = null; // short date + long time
677 // Derived item that changes
678 dateSeparator = null;
680 // We don't need to do these because they are not changed by changing calendar
688 // We don't need to clear these because they're only used for whidbey compat serialization
689 // the only values we use are the all...Patterns[0]
694 // remember to reload tokens
695 ClearTokenHashTable();
698 // Remember the new calendar
700 InitializeOverridableProperties(m_cultureData, calendar.ID);
702 // We succeeded, return
707 // The assigned calendar is not a valid calendar for this culture, throw
708 throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("Argument_InvalidCalendar"));
712 private int[] OptionalCalendars {
714 if (this.optionalCalendars == null) {
715 this.optionalCalendars = this.m_cultureData.CalendarIds;
717 return (this.optionalCalendars);
721 /*=================================GetEra==========================
722 **Action: Get the era value by parsing the name of the era.
723 **Returns: The era value for the specified era name.
724 ** -1 if the name of the era is not valid or not supported.
725 **Arguments: eraName the name of the era.
727 ============================================================================*/
730 public int GetEra(String eraName) {
731 if (eraName == null) {
732 throw new ArgumentNullException("eraName",
733 Environment.GetResourceString("ArgumentNull_String"));
735 Contract.EndContractBlock();
737 // For Geo-----al reasons, the Era Name and Abbreviated Era Name
738 // for ---- Calendar on non----- SKU returns empty string (which
739 // would be matched below) but we don't want the empty string to give
741 // confer 85900 DTFI.GetEra("") should fail on all cultures
742 if (eraName.Length == 0) {
746 // The following is based on the assumption that the era value is starting from 1, and has a
748 // If that ever changes, the code has to be changed.
750 // The calls to String.Compare should use the current culture for the string comparisons, but the
751 // invariant culture when comparing against the english names.
752 for (int i = 0; i < EraNames.Length; i++) {
753 // Compare the era name in a case-insensitive way for the appropriate culture.
754 if (m_eraNames[i].Length > 0) {
755 if (String.Compare(eraName, m_eraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
760 for (int i = 0; i < AbbreviatedEraNames.Length; i++) {
761 // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
762 if (String.Compare(eraName, m_abbrevEraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
766 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
767 // this comparison should use the InvariantCulture. The English name could have linguistically
768 // interesting characters.
769 if (String.Compare(eraName, m_abbrevEnglishEraNames[i], StringComparison.InvariantCultureIgnoreCase)==0) {
776 internal String[] EraNames
780 if (this.m_eraNames == null)
782 this.m_eraNames = this.m_cultureData.EraNames(Calendar.ID);;
784 return (this.m_eraNames);
788 /*=================================GetEraName==========================
789 **Action: Get the name of the era for the specified era value.
790 **Returns: The name of the specified era.
792 ** era the era value.
794 ** ArguementException if the era valie is invalid.
795 ============================================================================*/
797 // Era names are 1 indexed
798 public String GetEraName(int era) {
799 if (era == Calendar.CurrentEra) {
800 era = Calendar.CurrentEraValue;
803 // The following is based on the assumption that the era value is starting from 1, and has a
805 // If that ever changes, the code has to be changed.
806 if ((--era) < EraNames.Length && (era >= 0)) {
807 return (m_eraNames[era]);
809 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
812 internal String[] AbbreviatedEraNames
816 if (this.m_abbrevEraNames == null)
818 this.m_abbrevEraNames = this.m_cultureData.AbbrevEraNames(Calendar.ID);
820 return (this.m_abbrevEraNames);
824 // Era names are 1 indexed
825 public String GetAbbreviatedEraName(int era) {
826 if (AbbreviatedEraNames.Length == 0) {
827 // If abbreviation era name is not used in this culture,
828 // return the full era name.
829 return (GetEraName(era));
831 if (era == Calendar.CurrentEra) {
832 era = Calendar.CurrentEraValue;
834 if ((--era) < m_abbrevEraNames.Length && (era >= 0)) {
835 return (m_abbrevEraNames[era]);
837 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
840 internal String[] AbbreviatedEnglishEraNames
844 if (this.m_abbrevEnglishEraNames == null)
846 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
847 this.m_abbrevEnglishEraNames = this.m_cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
849 return (this.m_abbrevEnglishEraNames);
854 // Note that cultureData derives this from the short date format (unless someone's set this previously)
855 // Note that this property is quite undesirable.
856 public String DateSeparator
861 if (this.dateSeparator == null)
863 this.dateSeparator = this.m_cultureData.DateSeparator(Calendar.ID);
866 Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
867 return (this.dateSeparator);
873 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
875 throw new ArgumentNullException("value",
876 Environment.GetResourceString("ArgumentNull_String"));
878 Contract.EndContractBlock();
879 ClearTokenHashTable();
880 this.dateSeparator = value;
886 public DayOfWeek FirstDayOfWeek
891 if (this.firstDayOfWeek == -1)
893 this.firstDayOfWeek = this.m_cultureData.IFIRSTDAYOFWEEK;
896 Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
898 return ((DayOfWeek)this.firstDayOfWeek);
903 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
904 if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) {
905 firstDayOfWeek = (int)value;
907 throw new ArgumentOutOfRangeException(
908 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
909 DayOfWeek.Sunday, DayOfWeek.Saturday));
915 public CalendarWeekRule CalendarWeekRule
920 if (this.calendarWeekRule == -1)
922 this.calendarWeekRule = this.m_cultureData.IFIRSTWEEKOFYEAR;
925 Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
926 return ((CalendarWeekRule)this.calendarWeekRule);
931 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
932 if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) {
933 calendarWeekRule = (int)value;
935 throw new ArgumentOutOfRangeException(
936 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
937 CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
944 public String FullDateTimePattern
948 if (fullDateTimePattern == null)
950 fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
952 return (fullDateTimePattern);
957 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
959 throw new ArgumentNullException("value",
960 Environment.GetResourceString("ArgumentNull_String"));
962 Contract.EndContractBlock();
963 fullDateTimePattern = value;
968 // For our "patterns" arrays we have 2 variables, a string and a string[]
970 // The string[] contains the list of patterns, EXCEPT the default may not be included.
971 // The string contains the default pattern.
972 // When we initially construct our string[], we set the string to string[0]
973 public String LongDatePattern
977 // Initialize our long date pattern from the 1st array value if not set
978 if (this.longDatePattern == null)
980 // Initialize our data
981 this.longDatePattern = this.UnclonedLongDatePatterns[0];
984 return this.longDatePattern;
989 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
991 throw new ArgumentNullException("value",
992 Environment.GetResourceString("ArgumentNull_String"));
994 Contract.EndContractBlock();
996 // Remember the new string
997 this.longDatePattern = value;
999 // Clear the token hash table
1000 ClearTokenHashTable();
1002 // Clean up cached values that will be affected by this property.
1003 this.fullDateTimePattern = null;
1007 // For our "patterns" arrays we have 2 variables, a string and a string[]
1009 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1010 // The string contains the default pattern.
1011 // When we initially construct our string[], we set the string to string[0]
1012 public String LongTimePattern
1016 // Initialize our long time pattern from the 1st array value if not set
1017 if (this.longTimePattern == null)
1019 // Initialize our data
1020 this.longTimePattern = this.UnclonedLongTimePatterns[0];
1023 return this.longTimePattern;
1028 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1029 if (value == null) {
1030 throw new ArgumentNullException("value",
1031 Environment.GetResourceString("ArgumentNull_String"));
1033 Contract.EndContractBlock();
1035 // Remember the new string
1036 this.longTimePattern = value;
1038 // Clear the token hash table
1039 ClearTokenHashTable();
1041 // Clean up cached values that will be affected by this property.
1042 this.fullDateTimePattern = null; // Full date = long date + long Time
1043 this.generalLongTimePattern = null; // General long date = short date + long Time
1044 this.dateTimeOffsetPattern = null;
1049 // Note: just to be confusing there's only 1 month day pattern, not a whole list
1050 public String MonthDayPattern
1054 if (this.monthDayPattern == null)
1056 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
1057 this.monthDayPattern = this.m_cultureData.MonthDay(Calendar.ID);
1059 Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
1060 return (this.monthDayPattern);
1065 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1066 if (value == null) {
1067 throw new ArgumentNullException("value",
1068 Environment.GetResourceString("ArgumentNull_String"));
1070 Contract.EndContractBlock();
1072 this.monthDayPattern = value;
1077 public String PMDesignator
1080 [System.Security.SecuritySafeCritical] // auto-generated
1085 if (this.pmDesignator == null)
1087 this.pmDesignator = this.m_cultureData.SPM2359;
1090 Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
1091 return (this.pmDesignator);
1096 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1097 if (value == null) {
1098 throw new ArgumentNullException("value",
1099 Environment.GetResourceString("ArgumentNull_String"));
1101 Contract.EndContractBlock();
1102 ClearTokenHashTable();
1104 pmDesignator = value;
1110 public String RFC1123Pattern
1114 return (rfc1123Pattern);
1118 // For our "patterns" arrays we have 2 variables, a string and a string[]
1120 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1121 // The string contains the default pattern.
1122 // When we initially construct our string[], we set the string to string[0]
1123 public String ShortDatePattern
1127 // Initialize our short date pattern from the 1st array value if not set
1128 if (this.shortDatePattern == null)
1130 // Initialize our data
1131 this.shortDatePattern = this.UnclonedShortDatePatterns[0];
1134 return this.shortDatePattern;
1140 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1142 throw new ArgumentNullException("value",
1143 Environment.GetResourceString("ArgumentNull_String"));
1144 Contract.EndContractBlock();
1146 // Remember the new string
1147 this.shortDatePattern = value;
1149 // Clear the token hash table, note that even short dates could require this
1150 ClearTokenHashTable();
1152 // Clean up cached values that will be affected by this property.
1153 generalLongTimePattern = null; // General long time = short date + long time
1154 generalShortTimePattern = null; // General short time = short date + short Time
1155 dateTimeOffsetPattern = null;
1160 // For our "patterns" arrays we have 2 variables, a string and a string[]
1162 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1163 // The string contains the default pattern.
1164 // When we initially construct our string[], we set the string to string[0]
1165 public String ShortTimePattern
1169 // Initialize our short time pattern from the 1st array value if not set
1170 if (this.shortTimePattern == null)
1172 // Initialize our data
1173 this.shortTimePattern = this.UnclonedShortTimePatterns[0];
1175 return this.shortTimePattern;
1180 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1181 if (value == null) {
1182 throw new ArgumentNullException("value",
1183 Environment.GetResourceString("ArgumentNull_String"));
1185 Contract.EndContractBlock();
1187 // Remember the new string
1188 this.shortTimePattern= value;
1190 // Clear the token hash table, note that even short times could require this
1191 ClearTokenHashTable();
1193 // Clean up cached values that will be affected by this property.
1194 generalShortTimePattern = null; // General short date = short date + short time.
1199 public String SortableDateTimePattern {
1201 return (sortableDateTimePattern);
1205 /*=================================GeneralShortTimePattern=====================
1206 **Property: Return the pattern for 'g' general format: shortDate + short time
1207 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1208 ** We put this internal property here so that we can avoid doing the
1209 ** concatation every time somebody asks for the general format.
1210 ==============================================================================*/
1212 internal String GeneralShortTimePattern {
1214 if (generalShortTimePattern == null) {
1215 generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
1217 return (generalShortTimePattern);
1221 /*=================================GeneralLongTimePattern=====================
1222 **Property: Return the pattern for 'g' general format: shortDate + Long time
1223 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1224 ** We put this internal property here so that we can avoid doing the
1225 ** concatation every time somebody asks for the general format.
1226 ==============================================================================*/
1228 internal String GeneralLongTimePattern {
1230 if (generalLongTimePattern == null) {
1231 generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
1233 return (generalLongTimePattern);
1237 /*=================================DateTimeOffsetPattern==========================
1238 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1239 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1240 ** We put this internal property here so that we can avoid doing the
1241 ** concatation every time somebody uses this form
1242 ==============================================================================*/
1244 /*=================================DateTimeOffsetPattern==========================
1245 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1246 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1247 ** We put this internal property here so that we can avoid doing the
1248 ** concatation every time somebody uses this form
1249 ==============================================================================*/
1251 internal String DateTimeOffsetPattern {
1253 if (dateTimeOffsetPattern == null) {
1255 dateTimeOffsetPattern = ShortDatePattern + " " + LongTimePattern;
1257 /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
1259 bool foundZ = false;
1260 bool inQuote = false;
1262 for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) {
1263 switch (LongTimePattern[i]) {
1265 /* if we aren't in a quote, we've found a z */
1267 /* we'll fall out of the loop now because the test includes !foundZ */
1271 if (inQuote && (quote == LongTimePattern[i])) {
1272 /* we were in a quote and found a matching exit quote, so we are outside a quote now */
1274 } else if (!inQuote) {
1275 quote = LongTimePattern[i];
1278 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
1283 i++; /* skip next character that is escaped by this backslash */
1291 dateTimeOffsetPattern = dateTimeOffsetPattern + " zzz";
1294 return (dateTimeOffsetPattern);
1298 // Note that cultureData derives this from the long time format (unless someone's set this previously)
1299 // Note that this property is quite undesirable.
1301 public String TimeSeparator
1306 if (timeSeparator == null)
1308 timeSeparator = this.m_cultureData.TimeSeparator;
1311 Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
1312 return (timeSeparator);
1315 #if !FEATURE_CORECLR
1318 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1319 if (value == null) {
1320 throw new ArgumentNullException("value",
1321 Environment.GetResourceString("ArgumentNull_String"));
1323 Contract.EndContractBlock();
1324 ClearTokenHashTable();
1326 timeSeparator = value;
1332 public String UniversalSortableDateTimePattern
1336 return (universalSortableDateTimePattern);
1340 // For our "patterns" arrays we have 2 variables, a string and a string[]
1342 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1343 // The string contains the default pattern.
1344 // When we initially construct our string[], we set the string to string[0]
1345 public String YearMonthPattern
1349 // Initialize our year/month pattern from the 1st array value if not set
1350 if (this.yearMonthPattern == null)
1352 // Initialize our data
1353 this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
1355 return this.yearMonthPattern;
1360 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1361 if (value == null) {
1362 throw new ArgumentNullException("value",
1363 Environment.GetResourceString("ArgumentNull_String"));
1365 Contract.EndContractBlock();
1367 // Remember the new string
1368 this.yearMonthPattern = value;
1370 // Clear the token hash table, note that even short times could require this
1371 ClearTokenHashTable();
1376 // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
1378 static private void CheckNullValue(String[] values, int length) {
1379 Contract.Requires(values != null, "value != null");
1380 Contract.Requires(values.Length >= length);
1381 for (int i = 0; i < length; i++) {
1382 if (values[i] == null) {
1383 throw new ArgumentNullException("value",
1384 Environment.GetResourceString("ArgumentNull_ArrayValue"));
1390 public String[] AbbreviatedDayNames
1394 return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
1399 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1400 if (value == null) {
1401 throw new ArgumentNullException("value",
1402 Environment.GetResourceString("ArgumentNull_Array"));
1404 if (value.Length != 7) {
1405 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1407 Contract.EndContractBlock();
1408 CheckNullValue(value, value.Length);
1409 ClearTokenHashTable();
1411 abbreviatedDayNames = value;
1416 // Returns the string array of the one-letter day of week names.
1417 [System.Runtime.InteropServices.ComVisible(false)]
1418 public String[] ShortestDayNames
1422 return ((String[])internalGetSuperShortDayNames().Clone());
1427 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1428 if (value == null) {
1429 throw new ArgumentNullException("value",
1430 Environment.GetResourceString("ArgumentNull_Array"));
1432 if (value.Length != 7)
1434 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1436 Contract.EndContractBlock();
1437 CheckNullValue(value, value.Length);
1438 this.m_superShortDayNames = value;
1443 public String[] DayNames
1447 return ((String[])internalGetDayOfWeekNames().Clone());
1452 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1453 if (value == null) {
1454 throw new ArgumentNullException("value",
1455 Environment.GetResourceString("ArgumentNull_Array"));
1457 if (value.Length != 7)
1459 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1461 Contract.EndContractBlock();
1462 CheckNullValue(value, value.Length);
1463 ClearTokenHashTable();
1470 public String[] AbbreviatedMonthNames {
1472 return ((String[])internalGetAbbreviatedMonthNames().Clone());
1477 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1478 if (value == null) {
1479 throw new ArgumentNullException("value",
1480 Environment.GetResourceString("ArgumentNull_Array"));
1482 if (value.Length != 13)
1484 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1486 Contract.EndContractBlock();
1487 CheckNullValue(value, value.Length - 1);
1488 ClearTokenHashTable();
1489 abbreviatedMonthNames = value;
1494 public String[] MonthNames
1498 return ((String[])internalGetMonthNames().Clone());
1503 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1504 if (value == null) {
1505 throw new ArgumentNullException("value",
1506 Environment.GetResourceString("ArgumentNull_Array"));
1508 if (value.Length != 13)
1510 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1512 Contract.EndContractBlock();
1513 CheckNullValue(value, value.Length - 1);
1515 ClearTokenHashTable();
1519 // Whitespaces that we allow in the month names.
1520 // U+00a0 is non-breaking space.
1521 static char[] MonthSpaces = {' ', '\u00a0'};
1523 internal bool HasSpacesInMonthNames {
1525 return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
1529 internal bool HasSpacesInDayNames {
1531 return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
1537 // internalGetMonthName
1539 // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
1543 // style To indicate a form like regular/genitive/month name in a leap year.
1544 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1546 // ArgumentOutOfRangeException When month name is invalid.
1548 internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) {
1550 // Right now, style is mutual exclusive, but I make the style to be flag so that
1551 // maybe we can combine flag if there is such a need.
1553 String[] monthNamesArray = null;
1555 case MonthNameStyles.Genitive:
1556 monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
1558 case MonthNameStyles.LeapYear:
1559 monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
1562 monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames(): internalGetMonthNames());
1565 // The month range is from 1 ~ this.m_monthNames.Length
1566 // (actually is 13 right now for all cases)
1567 if ((month < 1) || (month > monthNamesArray.Length)) {
1568 throw new ArgumentOutOfRangeException(
1569 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1570 1, monthNamesArray.Length));
1572 return (monthNamesArray[month-1]);
1576 // internalGetGenitiveMonthNames
1578 // Action: Retrieve the array which contains the month names in genitive form.
1579 // If this culture does not use the gentive form, the normal month name is returned.
1581 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1583 private String[] internalGetGenitiveMonthNames(bool abbreviated) {
1585 if (this.m_genitiveAbbreviatedMonthNames == null)
1587 this.m_genitiveAbbreviatedMonthNames = this.m_cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
1588 Contract.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
1589 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
1591 return (this.m_genitiveAbbreviatedMonthNames);
1594 if (this.genitiveMonthNames == null)
1596 this.genitiveMonthNames = this.m_cultureData.GenitiveMonthNames(this.Calendar.ID);
1597 Contract.Assert(this.genitiveMonthNames.Length == 13,
1598 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
1600 return (this.genitiveMonthNames);
1604 // internalGetLeapYearMonthNames
1606 // Actions: Retrieve the month names used in a leap year.
1607 // If this culture does not have different month names in a leap year, the normal month name is returned.
1608 // Agruments: None. (can use abbreviated later if needed)
1610 internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) {
1611 if (this.leapYearMonthNames == null)
1613 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
1614 this.leapYearMonthNames = this.m_cultureData.LeapYearMonthNames(Calendar.ID);
1615 Contract.Assert(this.leapYearMonthNames.Length == 13,
1616 "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
1618 return (leapYearMonthNames);
1622 public String GetAbbreviatedDayName(DayOfWeek dayofweek)
1625 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1626 throw new ArgumentOutOfRangeException(
1627 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1628 DayOfWeek.Sunday, DayOfWeek.Saturday));
1630 Contract.EndContractBlock();
1632 // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
1633 // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
1635 return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
1639 // Returns the super short day of week names for the specified day of week.
1640 [System.Runtime.InteropServices.ComVisible(false)]
1641 public String GetShortestDayName(DayOfWeek dayOfWeek)
1644 if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) {
1645 throw new ArgumentOutOfRangeException(
1646 "dayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1647 DayOfWeek.Sunday, DayOfWeek.Saturday));
1649 Contract.EndContractBlock();
1651 // Don't call the public property SuperShortDayNames here since a clone is needed in that
1652 // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
1654 return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
1657 // Get all possible combination of inputs
1658 static private String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
1660 Contract.Requires(patterns1 != null);
1661 Contract.Requires(patterns2 != null);
1664 String[] result = new String[patterns1.Length * patterns2.Length];
1666 // Counter of actual results
1668 for (int i = 0; i < patterns1.Length; i++)
1670 for (int j = 0; j < patterns2.Length; j++)
1672 // Can't combine if null or empty
1673 result[k++] = patterns1[i] + connectString + patterns2[j];
1677 // Return the combinations
1682 public String[] GetAllDateTimePatterns()
1684 List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
1686 for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
1688 String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
1689 for (int j = 0; j < strings.Length; j++)
1691 results.Add(strings[j]);
1694 return results.ToArray();
1698 public String[] GetAllDateTimePatterns(char format)
1700 Contract.Ensures(Contract.Result<String[]>() != null);
1701 String [] result = null;
1706 result = this.AllShortDatePatterns;
1709 result = this.AllLongDatePatterns;
1712 result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
1716 result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
1719 result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
1722 result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
1726 result = new String[] {MonthDayPattern};
1730 result = new String[] {DateTimeFormat.RoundtripFormat};
1734 result = new String[] {rfc1123Pattern};
1737 result = new String[] {sortableDateTimePattern};
1740 result = this.AllShortTimePatterns;
1743 result = this.AllLongTimePatterns;
1746 result = new String[] {UniversalSortableDateTimePattern};
1750 result = this.AllYearMonthPatterns;
1753 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
1759 public String GetDayName(DayOfWeek dayofweek)
1761 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1762 throw new ArgumentOutOfRangeException(
1763 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1764 DayOfWeek.Sunday, DayOfWeek.Saturday));
1766 Contract.EndContractBlock();
1768 // Use the internal one so that we don't clone the array unnecessarily
1769 return (internalGetDayOfWeekNames()[(int)dayofweek]);
1774 public String GetAbbreviatedMonthName(int month)
1776 if (month < 1 || month > 13) {
1777 throw new ArgumentOutOfRangeException(
1778 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1781 Contract.EndContractBlock();
1782 // Use the internal one so we don't clone the array unnecessarily
1783 return (internalGetAbbreviatedMonthNames()[month-1]);
1787 public String GetMonthName(int month)
1789 if (month < 1 || month > 13) {
1790 throw new ArgumentOutOfRangeException(
1791 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1794 Contract.EndContractBlock();
1795 // Use the internal one so we don't clone the array unnecessarily
1796 return (internalGetMonthNames()[month-1]);
1799 // For our "patterns" arrays we have 2 variables, a string and a string[]
1801 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1802 // The string contains the default pattern.
1803 // When we initially construct our string[], we set the string to string[0]
1805 // The resulting [] can get returned to the calling app, so clone it.
1806 private static string[] GetMergedPatterns(string [] patterns, string defaultPattern)
1808 Contract.Assert(patterns != null && patterns.Length > 0,
1809 "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
1810 Contract.Assert(defaultPattern != null,
1811 "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
1813 // If the default happens to be the first in the list just return (a cloned) copy
1814 if (defaultPattern == patterns[0])
1816 return (string[])patterns.Clone();
1819 // We either need a bigger list, or the pattern from the list.
1821 for (i = 0; i < patterns.Length; i++)
1823 // Stop if we found it
1824 if (defaultPattern == patterns[i])
1828 // Either way we're going to need a new array
1829 string[] newPatterns;
1832 if (i < patterns.Length)
1834 // Found it, output will be same size
1835 newPatterns = (string[])patterns.Clone();
1837 // Have to move [0] item to [i] so we can re-write default at [0]
1838 // (remember defaultPattern == [i] so this is OK)
1839 newPatterns[i] = newPatterns[0];
1843 // Not found, make room for it
1844 newPatterns = new String[patterns.Length + 1];
1846 // Copy existing array
1847 Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
1850 // Remember the default
1851 newPatterns[0] = defaultPattern;
1853 // Return the reconstructed list
1857 // Default string isn't necessarily in our string array, so get the
1858 // merged patterns of both
1859 private String[] AllYearMonthPatterns
1863 return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
1867 private String[] AllShortDatePatterns
1871 return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
1875 private String[] AllShortTimePatterns
1879 return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
1883 private String[] AllLongDatePatterns
1887 return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
1891 private String[] AllLongTimePatterns
1895 return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
1899 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1900 // This won't include default, call AllYearMonthPatterns
1901 private String[] UnclonedYearMonthPatterns
1905 if (this.allYearMonthPatterns == null)
1907 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
1908 this.allYearMonthPatterns = this.m_cultureData.YearMonths(this.Calendar.ID);
1909 Contract.Assert(this.allYearMonthPatterns.Length > 0,
1910 "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
1913 return this.allYearMonthPatterns;
1918 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1919 // This won't include default, call AllShortDatePatterns
1920 private String [] UnclonedShortDatePatterns
1924 if (allShortDatePatterns == null)
1926 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
1927 this.allShortDatePatterns = this.m_cultureData.ShortDates(this.Calendar.ID);
1928 Contract.Assert(this.allShortDatePatterns.Length > 0,
1929 "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
1932 return this.allShortDatePatterns;
1936 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1937 // This won't include default, call AllLongDatePatterns
1938 private String[] UnclonedLongDatePatterns
1942 if (allLongDatePatterns == null)
1944 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
1945 this.allLongDatePatterns = this.m_cultureData.LongDates(this.Calendar.ID);
1946 Contract.Assert(this.allLongDatePatterns.Length > 0,
1947 "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
1950 return this.allLongDatePatterns;
1954 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1955 // This won't include default, call AllShortTimePatterns
1956 private String[] UnclonedShortTimePatterns
1960 if (this.allShortTimePatterns == null)
1962 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
1963 Contract.Assert(this.allShortTimePatterns.Length > 0,
1964 "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
1967 return this.allShortTimePatterns;
1971 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1972 // This won't include default, call AllLongTimePatterns
1973 private String[] UnclonedLongTimePatterns
1977 if (this.allLongTimePatterns == null)
1979 this.allLongTimePatterns = this.m_cultureData.LongTimes;
1980 Contract.Assert(this.allLongTimePatterns.Length > 0,
1981 "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
1984 return this.allLongTimePatterns;
1988 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) {
1990 throw new ArgumentNullException("dtfi",
1991 Environment.GetResourceString("ArgumentNull_Obj"));
1993 Contract.EndContractBlock();
1994 if (dtfi.IsReadOnly) {
1997 DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
1998 // We can use the data member calendar in the setter, instead of the property Calendar,
1999 // since the cloned copy should have the same state as the original copy.
2000 newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
2001 newInfo.m_isReadOnly = true;
2006 public bool IsReadOnly {
2008 return (m_isReadOnly);
2012 // Return the native name for the calendar in DTFI.Calendar. The native name is referred to
2013 // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese.
2014 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
2015 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
2016 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
2017 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
2018 [System.Runtime.InteropServices.ComVisible(false)]
2019 public String NativeCalendarName
2023 return m_cultureData.CalendarName(Calendar.ID);
2028 // Used by custom cultures and others to set the list of available formats. Note that none of them are
2029 // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
2032 // Most of the format characters that can be used in GetAllDateTimePatterns are
2033 // not really needed since they are one of the following:
2035 // r/R/s/u locale-independent constants -- cannot be changed!
2036 // m/M/y/Y fields with a single string in them -- that can be set through props directly
2037 // f/F/g/G/U derived fields based on combinations of various of the below formats
2039 // NOTE: No special validation is done here beyond what is done when the actual respective fields
2040 // are used (what would be the point of disallowing here what we allow in the appropriate property?)
2042 // WARNING: If more validation is ever done in one place, it should be done in the other.
2045 [System.Runtime.InteropServices.ComVisible(false)]
2046 public void SetAllDateTimePatterns(String[] patterns, char format)
2049 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2050 if (patterns == null) {
2051 throw new ArgumentNullException("patterns",
2052 Environment.GetResourceString("ArgumentNull_Array"));
2055 if (patterns.Length == 0)
2057 throw new ArgumentException(Environment.GetResourceString("Arg_ArrayZeroError"), "patterns");
2059 Contract.EndContractBlock();
2061 for (int i=0; i<patterns.Length; i++)
2063 if (patterns[i] == null)
2065 throw new ArgumentNullException(Environment.GetResourceString("ArgumentNull_ArrayValue"));
2069 // Remember the patterns, and use the 1st as default
2073 this.allShortDatePatterns = patterns;
2074 this.shortDatePattern = this.allShortDatePatterns[0];
2078 this.allLongDatePatterns = patterns;
2079 this.longDatePattern = this.allLongDatePatterns[0];
2083 this.allShortTimePatterns = patterns;
2084 this.shortTimePattern = this.allShortTimePatterns[0];
2088 this.allLongTimePatterns = patterns;
2089 this.longTimePattern = this.allLongTimePatterns[0];
2094 this.allYearMonthPatterns = patterns;
2095 this.yearMonthPattern = this.allYearMonthPatterns[0];
2099 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
2102 // Clear the token hash table, note that even short dates could require this
2103 ClearTokenHashTable();
2108 [System.Runtime.InteropServices.ComVisible(false)]
2109 public String[] AbbreviatedMonthGenitiveNames
2113 return ((String[])internalGetGenitiveMonthNames(true).Clone());
2119 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2122 throw new ArgumentNullException("value",
2123 Environment.GetResourceString("ArgumentNull_Array"));
2125 if (value.Length != 13)
2127 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2129 Contract.EndContractBlock();
2130 CheckNullValue(value, value.Length - 1);
2131 ClearTokenHashTable();
2132 this.m_genitiveAbbreviatedMonthNames= value;
2136 [System.Runtime.InteropServices.ComVisible(false)]
2137 public String[] MonthGenitiveNames
2141 return ((String[])internalGetGenitiveMonthNames(false).Clone());
2147 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2150 throw new ArgumentNullException("value",
2151 Environment.GetResourceString("ArgumentNull_Array"));
2153 if (value.Length != 13)
2155 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2157 Contract.EndContractBlock();
2158 CheckNullValue(value, value.Length - 1);
2159 genitiveMonthNames= value;
2160 ClearTokenHashTable();
2165 // Positive TimeSpan Pattern
2168 private string m_fullTimeSpanPositivePattern;
2169 internal String FullTimeSpanPositivePattern
2173 if (m_fullTimeSpanPositivePattern == null)
2175 CultureData cultureDataWithoutUserOverrides;
2176 if (m_cultureData.UseUserOverride)
2177 cultureDataWithoutUserOverrides = CultureData.GetCultureData(m_cultureData.CultureName, false);
2179 cultureDataWithoutUserOverrides = m_cultureData;
2180 String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
2182 m_fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
2184 return m_fullTimeSpanPositivePattern;
2189 // Negative TimeSpan Pattern
2192 private string m_fullTimeSpanNegativePattern;
2193 internal String FullTimeSpanNegativePattern
2197 if (m_fullTimeSpanNegativePattern == null)
2198 m_fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
2199 return m_fullTimeSpanNegativePattern;
2204 // Get suitable CompareInfo from current DTFI object.
2206 internal CompareInfo CompareInfo
2210 if (m_compareInfo == null)
2212 // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
2213 // CompareInfo cache. otherwise we would just create CompareInfo using m_cultureData.
2214 m_compareInfo = CompareInfo.GetCompareInfo(m_cultureData.SCOMPAREINFO);
2217 return m_compareInfo;
2221 internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
2222 | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
2223 | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
2224 | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
2226 internal static void ValidateStyles(DateTimeStyles style, String parameterName) {
2227 if ((style & InvalidDateTimeStyles) != 0) {
2228 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidDateTimeStyles"), parameterName);
2230 if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) {
2231 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeStyles"), parameterName);
2233 Contract.EndContractBlock();
2234 if (((style & DateTimeStyles.RoundtripKind) != 0)
2235 && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) {
2236 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeRoundtripStyles"), parameterName);
2241 // Actions: Return the internal flag used in formatting and parsing.
2242 // 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.
2244 internal DateTimeFormatFlags FormatFlags
2248 if (formatFlags == DateTimeFormatFlags.NotInitialized)
2250 // Build the format flags from the data in this DTFI
2251 formatFlags = DateTimeFormatFlags.None;
2252 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
2253 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2254 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
2255 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2256 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames);
2257 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
2259 return (formatFlags);
2263 internal Boolean HasForceTwoDigitYears {
2265 switch (calendar.ID)
2268 // If is y/yy, do not get (year % 100). "y" will print
2269 // year without leading zero. "yy" will print year with two-digit in leading zero.
2270 // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
2271 // So year 5 is "05", and year 125 is "125".
2272 // The reason for not doing (year % 100) is for Taiwan calendar.
2273 // If year 125, then output 125 and not 25.
2274 // Note: OS uses "yyyy" for Taiwan calendar by default.
2275 case (Calendar.CAL_JAPAN):
2276 case (Calendar.CAL_TAIWAN):
2283 // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
2284 internal Boolean HasYearMonthAdjustment {
2286 return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
2290 // This is a callback that the parser can make back into the DTFI to let it fiddle with special
2291 // cases associated with that culture or calendar. Currently this only has special cases for
2292 // the Hebrew calendar, but this could be extended to other cultures.
2294 // The return value is whether the year and month are actually valid for this calendar.
2295 internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) {
2296 if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
2298 // Special rules to fix up the Hebrew year/month
2300 // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
2301 // E.g. if the year is 5763, we only format as 763.
2306 // Because we need to calculate leap year, we should fall out now for an invalid year.
2307 if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) {
2311 // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
2312 // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
2313 // shuffle the other months down.
2314 if (parsedMonthName) {
2315 if (!Calendar.IsLeapYear(year)) {
2319 else if (month == 7) {
2329 // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
2332 TokenHashValue[] m_dtfiTokenHash;
2334 private const int TOKEN_HASH_SIZE = 199;
2335 private const int SECOND_PRIME = 197;
2336 private const String dateSeparatorOrTimeZoneOffset = "-";
2337 private const String invariantDateSeparator = "/";
2338 private const String invariantTimeSeparator = ":";
2341 // Common Ignorable Symbols
2343 internal const String IgnorablePeriod = ".";
2344 internal const String IgnorableComma = ",";
2347 // Year/Month/Day suffixes
2349 internal const String CJKYearSuff = "\u5e74";
2350 internal const String CJKMonthSuff = "\u6708";
2351 internal const String CJKDaySuff = "\u65e5";
2353 internal const String KoreanYearSuff = "\ub144";
2354 internal const String KoreanMonthSuff = "\uc6d4";
2355 internal const String KoreanDaySuff = "\uc77c";
2357 internal const String KoreanHourSuff = "\uc2dc";
2358 internal const String KoreanMinuteSuff = "\ubd84";
2359 internal const String KoreanSecondSuff = "\ucd08";
2361 internal const String CJKHourSuff = "\u6642";
2362 internal const String ChineseHourSuff = "\u65f6";
2364 internal const String CJKMinuteSuff = "\u5206";
2365 internal const String CJKSecondSuff = "\u79d2";
2367 internal const String LocalTimeMark = "T";
2369 internal const String KoreanLangName = "ko";
2370 internal const String JapaneseLangName = "ja";
2371 internal const String EnglishLangName = "en";
2373 private static volatile DateTimeFormatInfo s_jajpDTFI;
2374 private static volatile DateTimeFormatInfo s_zhtwDTFI;
2377 // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse
2378 // date string with Japanese era name correctly even when the supplied DTFI
2379 // does not use Japanese calendar.
2380 // The created instance is stored in global s_jajpDTFI.
2382 internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() {
2383 DateTimeFormatInfo temp = s_jajpDTFI;
2385 temp = new CultureInfo("ja-JP", false).DateTimeFormat;
2386 temp.Calendar = JapaneseCalendar.GetDefaultInstance();
2399 internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() {
2400 DateTimeFormatInfo temp = s_zhtwDTFI;
2402 temp = new CultureInfo("zh-TW", false).DateTimeFormat;
2403 temp.Calendar = TaiwanCalendar.GetDefaultInstance();
2410 // DTFI properties should call this when the setter are called.
2411 private void ClearTokenHashTable()
2413 m_dtfiTokenHash = null;
2414 formatFlags = DateTimeFormatFlags.NotInitialized;
2417 [System.Security.SecurityCritical] // auto-generated
2418 internal TokenHashValue[] CreateTokenHashTable() {
2419 TokenHashValue[] temp = m_dtfiTokenHash;
2421 temp = new TokenHashValue[TOKEN_HASH_SIZE];
2423 bool koreanLanguage = LanguageName.Equals(KoreanLangName);
2425 string sep = this.TimeSeparator.Trim();
2426 if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
2427 if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
2429 if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) {
2431 // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
2432 // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
2433 // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
2434 // instead of SEP_HourSuff.
2436 InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
2439 InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2440 InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2443 if (LanguageName.Equals("sq")) {
2444 // Albanian allows time formats like "12:00.PD"
2445 InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2446 InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2450 InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
2451 InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
2452 InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
2453 InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
2454 InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
2455 InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
2457 InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
2458 InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
2459 InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2460 InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
2463 if (koreanLanguage) {
2465 InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
2466 InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2467 InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
2470 if ( LanguageName.Equals("ky")) {
2471 // For some cultures, the date separator works more like a comma, being allowed before or after any date part
2472 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
2475 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
2478 String[] dateWords = null;
2479 DateTimeFormatInfoScanner scanner = null;
2481 // We need to rescan the date words since we're always synthetic
2482 scanner = new DateTimeFormatInfoScanner();
2483 // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
2484 // The only reason they're being assigned to m_dateWords is for Whidbey Deserialization
2485 m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this);
2486 // Ensure the formatflags is initialized.
2487 DateTimeFormatFlags flag = FormatFlags;
2489 // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
2490 // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
2491 // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
2492 bool useDateSepAsIgnorableSymbol = false;
2494 String monthPostfix = null;
2495 if (dateWords != null)
2497 // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
2498 // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
2499 for (int i = 0; i < dateWords.Length; i++)
2501 switch (dateWords[i][0])
2503 // This is a month postfix
2504 case DateTimeFormatInfoScanner.MonthPostfixChar:
2505 // Get the real month postfix.
2506 monthPostfix = dateWords[i].Substring(1);
2507 // Add the month name + postfix into the token.
2508 AddMonthNames(temp, monthPostfix);
2510 case DateTimeFormatInfoScanner.IgnorableSymbolChar:
2511 String symbol = dateWords[i].Substring(1);
2512 InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
2513 if (this.DateSeparator.Trim(null).Equals(symbol))
2515 // The date separator is the same as the ingorable symbol.
2516 useDateSepAsIgnorableSymbol = true;
2520 InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
2522 if (LanguageName.Equals("eu")) {
2523 // Basque has date words with leading dots
2524 InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
2531 if (!useDateSepAsIgnorableSymbol)
2533 // Use the normal date separator.
2534 InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
2536 // Add the regular month names.
2537 AddMonthNames(temp, null);
2539 // Add the abbreviated month names.
2540 for (int i = 1; i <= 13; i++) {
2541 InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
2545 if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) {
2546 for (int i = 1; i <= 13; i++) {
2548 str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
2549 InsertHash(temp, str, TokenType.MonthToken, i);
2553 if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
2554 for (int i = 1; i <= 13; i++) {
2556 str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
2557 InsertHash(temp, str, TokenType.MonthToken, i);
2561 for (int i = 0; i < 7; i++) {
2562 //String str = GetDayOfWeekNames()[i];
2563 // We have to call public methods here to work with inherited DTFI.
2564 String str = GetDayName((DayOfWeek)i);
2565 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2567 str = GetAbbreviatedDayName((DayOfWeek)i);
2568 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2572 int[] eras = calendar.Eras;
2573 for (int i = 1; i <= eras.Length; i++) {
2574 InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
2575 InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
2579 if (LanguageName.Equals(JapaneseLangName)) {
2580 // Japanese allows day of week forms like: "(Tue)"
2581 for (int i = 0; i < 7; i++) {
2582 String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
2583 InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
2585 if (this.Calendar.GetType() != typeof(JapaneseCalendar)) {
2586 // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar,
2587 // we will check Japanese Era name as well when the calendar is Gregorian.
2588 DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
2589 for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) {
2590 InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
2591 InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
2592 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2593 InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i-1], TokenType.JapaneseEraToken, i);
2598 else if (CultureName.Equals("zh-TW")) {
2599 DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
2600 for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) {
2601 if (twDtfi.GetEraName(i).Length > 0) {
2602 InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
2607 InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2608 InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2610 // Add invariant month names and day names.
2611 for (int i = 1; i <= 12; i++) {
2613 // We have to call public methods here to work with inherited DTFI.
2614 // Insert the month name first, so that they are at the front of abbrevaited
2616 str = InvariantInfo.GetMonthName(i);
2617 InsertHash(temp, str, TokenType.MonthToken, i);
2618 str = InvariantInfo.GetAbbreviatedMonthName(i);
2619 InsertHash(temp, str, TokenType.MonthToken, i);
2622 for (int i = 0; i < 7; i++) {
2623 // We have to call public methods here to work with inherited DTFI.
2624 String str = InvariantInfo.GetDayName((DayOfWeek)i);
2625 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2627 str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
2628 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2632 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
2633 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2634 InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
2637 InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
2638 InsertHash(temp, DateTimeParse.GMTName, TokenType.TimeZoneToken, 0);
2639 InsertHash(temp, DateTimeParse.ZuluName, TokenType.TimeZoneToken, 0);
2641 InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
2642 InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
2644 m_dtfiTokenHash = temp;
2649 private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
2651 for (int i = 1; i <= 13; i++) {
2653 //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
2654 // We have to call public methods here to work with inherited DTFI.
2655 // Insert the month name first, so that they are at the front of abbrevaited
2657 str = GetMonthName(i);
2658 if (str.Length > 0) {
2659 if (monthPostfix != null) {
2660 // Insert the month name with the postfix first, so it can be matched first.
2661 InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
2664 InsertHash(temp, str, TokenType.MonthToken, i);
2667 str = GetAbbreviatedMonthName(i);
2668 InsertHash(temp, str, TokenType.MonthToken, i);
2673 ////////////////////////////////////////////////////////////////////////
2676 // Try to parse the current word to see if it is a Hebrew number.
2677 // Tokens will be updated accordingly.
2678 // This is called by the Lexer of DateTime.Parse().
2680 // Unlike most of the functions in this class, the return value indicates
2681 // whether or not it started to parse. The badFormat parameter indicates
2682 // if parsing began, but the format was bad.
2684 ////////////////////////////////////////////////////////////////////////
2686 private static bool TryParseHebrewNumber(
2688 out Boolean badFormat,
2695 if (!HebrewNumber.IsDigit(str.Value[i])) {
2696 // If the current character is not a Hebrew digit, just return false.
2697 // There is no chance that we can parse a valid Hebrew number from here.
2700 // The current character is a Hebrew digit. Try to parse this word as a Hebrew number.
2701 HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
2702 HebrewNumberParsingState state;
2705 state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
2707 case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number.
2708 case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character.
2709 // Break out so that we don't continue to try parse this as a Hebrew number.
2712 } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
2714 // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
2715 Contract.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
2716 "Invalid returned state from HebrewNumber.ParseByChar()");
2718 if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) {
2719 // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
2723 // We have found a valid Hebrew number. Update the index.
2724 str.Advance(i - str.Index);
2726 // Get the final Hebrew number value from the HebrewNumberParsingContext.
2727 number = context.result;
2732 private static bool IsHebrewChar(char ch) {
2733 return (ch >= '\x0590' && ch <= '\x05ff');
2736 [System.Security.SecurityCritical] // auto-generated
2737 internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, ref __DTString str) {
2738 tokenType = TokenType.UnknownToken;
2741 TokenHashValue value;
2742 Contract.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
2744 char ch = str.m_current;
2745 bool isLetter = Char.IsLetter(ch);
2747 ch = Char.ToLower(ch, this.Culture);
2748 if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) {
2750 if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) {
2752 tokenType = TokenType.UnknownToken;
2755 // This is a Hebrew number.
2756 // Do nothing here. TryParseHebrewNumber() will update token accordingly.
2757 tokenType = TokenType.HebrewNumber;
2764 int hashcode = ch % TOKEN_HASH_SIZE;
2765 int hashProbe = 1 + ch % SECOND_PRIME;
2766 int remaining = str.len - str.Index;
2769 TokenHashValue[] hashTable = m_dtfiTokenHash;
2770 if (hashTable == null) {
2771 hashTable = CreateTokenHashTable();
2774 value = hashTable[hashcode];
2775 if (value == null) {
2779 // Check this value has the right category (regular token or separator token) that we are looking for.
2780 if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) {
2781 if (String.Compare(str.Value, str.Index, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase)==0) {
2783 // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately.
2785 if ((nextCharIndex = str.Index + value.tokenString.Length) < str.len) {
2786 // Check word boundary. The next character should NOT be a letter.
2787 char nextCh = str.Value[nextCharIndex];
2788 if (Char.IsLetter(nextCh)) {
2793 tokenType = value.tokenType & TokenMask;
2794 tokenValue = value.tokenValue;
2795 str.Advance(value.tokenString.Length);
2797 } else if (value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) {
2798 // For month token, we will match the month names which have spaces.
2799 int matchStrLen = 0;
2800 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2801 tokenType = value.tokenType & TokenMask;
2802 tokenValue = value.tokenValue;
2803 str.Advance(matchStrLen);
2806 } else if (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames) {
2807 // For month token, we will match the month names which have spaces.
2808 int matchStrLen = 0;
2809 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2810 tokenType = value.tokenType & TokenMask;
2811 tokenValue = value.tokenValue;
2812 str.Advance(matchStrLen);
2818 hashcode += hashProbe;
2819 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2820 }while (i < TOKEN_HASH_SIZE);
2825 void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) {
2826 // Remember the current slot.
2827 TokenHashValue previousNode = hashTable[hashcode];
2829 //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert);
2830 // Insert the new node into the current slot.
2831 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);;
2833 while (++pos < TOKEN_HASH_SIZE) {
2834 hashcode += hashProbe;
2835 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2836 // Remember this slot
2837 TokenHashValue temp = hashTable[hashcode];
2839 if (temp != null && Char.ToLower(temp.tokenString[0], this.Culture) != ch) {
2842 // Put the previous slot into this slot.
2843 hashTable[hashcode] = previousNode;
2844 //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
2849 previousNode = temp;
2851 Contract.Assert(true, "The hashtable is full. This should not happen.");
2854 void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) {
2855 // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
2856 if (str == null || str.Length == 0) {
2859 TokenHashValue value;
2861 // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
2862 // DateTime.Parse().
2863 if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) {
2864 str = str.Trim(null); // Trim white space characters.
2865 // Could have space for separators
2866 if (str.Length == 0)
2869 char ch = Char.ToLower(str[0], this.Culture);
2870 int hashcode = ch % TOKEN_HASH_SIZE;
2871 int hashProbe = 1 + ch % SECOND_PRIME;
2873 value = hashTable[hashcode];
2874 if (value == null) {
2875 //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
2876 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
2879 // Collision happens. Find another slot.
2880 if (str.Length >= value.tokenString.Length) {
2881 // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
2882 // the shorter ones.
2883 if (String.Compare(str, 0, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase) == 0) {
2884 if (str.Length > value.tokenString.Length) {
2885 // The str to be inserted has the same prefix as the current token, and str is longer.
2886 // Insert str into this node, and shift every node behind it.
2887 InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
2890 // Same token. If they have different types (regular token vs separator token). Add them.
2891 // If we have the same regular token or separator token in the hash already, do NOT update the hash.
2892 // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
2896 // 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.
2897 // 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.
2900 int nTokenType = (int)tokenType;
2901 int nCurrentTokenTypeInHash = (int)value.tokenType;
2903 // The idea behind this check is:
2904 // - if the app is targetting 4.5.1 or above OR the compat flag is set, use the correct behavior by default.
2905 // - if the app is targetting 4.5 or below AND the compat switch is set, use the correct behavior
2906 // - if the app is targetting 4.5 or below AND the compat switch is NOT set, use the incorrect behavior
2907 if (preferExistingTokens || BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
2909 if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
2910 ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
2912 value.tokenType |= tokenType;
2913 if (tokenValue != 0)
2915 value.tokenValue = tokenValue;
2921 // The following logic is incorrect and causes updates to happen depending on the bitwise relationship between the existing token type and the
2922 // the stored token type. It was this way in .NET 4 RTM. The behavior above is correct and will be adopted going forward.
2924 if ((((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.RegularTokenMask) == nTokenType) ||
2925 (((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.SeparatorTokenMask) == nTokenType))
2927 value.tokenType |= tokenType;
2928 if (tokenValue != 0)
2930 value.tokenValue = tokenValue;
2934 // The token to be inserted is already in the table. Skip it.
2939 //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
2941 hashcode += hashProbe;
2942 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2943 } while (i < TOKEN_HASH_SIZE);
2944 Contract.Assert(true, "The hashtable is full. This should not happen.");
2946 } // class DateTimeFormatInfo
2948 internal class TokenHashValue {
2949 internal String tokenString;
2950 internal TokenType tokenType;
2951 internal int tokenValue;
2953 internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) {
2954 this.tokenString = tokenString;
2955 this.tokenType = tokenType;
2956 this.tokenValue = tokenValue;