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.
6 namespace System.Globalization {
9 using System.Threading;
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Runtime.Serialization;
13 using System.Security.Permissions;
14 using System.Runtime.InteropServices;
15 using System.Runtime.Versioning;
17 using System.Diagnostics.Contracts;
20 // Flags used to indicate different styles of month names.
21 // This is an internal flag used by internalGetMonthName().
22 // Use flag here in case that we need to provide a combination of these styles
23 // (such as month name of a leap year in genitive form. Not likely for now,
24 // but would like to keep the option open).
28 internal enum MonthNameStyles {
30 Genitive = 0x00000001,
31 LeapYear = 0x00000002,
35 // Flags used to indicate special rule used in parsing/formatting
36 // for a specific DateTimeFormatInfo instance.
37 // This is an internal flag.
39 // This flag is different from MonthNameStyles because this flag
40 // can be expanded to accomodate parsing behaviors like CJK month names
41 // or alternative month names, etc.
44 internal enum DateTimeFormatFlags {
46 UseGenitiveMonth = 0x00000001,
47 UseLeapYearMonth = 0x00000002,
48 UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
49 UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule.
50 UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
51 UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
58 [System.Runtime.InteropServices.ComVisible(true)]
59 public sealed class DateTimeFormatInfo : ICloneable, IFormatProvider
62 // Note, some fields are derived so don't really need to be serialized, but we can't mark as
63 // optional because Whidbey was expecting them. Post-Arrowhead we could fix that
64 // once Whidbey serialization is no longer necessary.
67 // cache for the invariant culture.
68 // invariantInfo is constant irrespective of your current culture.
69 private static volatile DateTimeFormatInfo invariantInfo;
71 // an index which points to a record in Culture Data Table.
72 [NonSerialized]private CultureData m_cultureData;
74 // The culture name used to create this DTFI.
75 [OptionalField(VersionAdded = 2)]
76 internal String m_name = null;
78 // The language name of the culture used to create this DTFI.
79 [NonSerialized]private String m_langName = null;
81 // CompareInfo usually used by the parser.
82 [NonSerialized]private CompareInfo m_compareInfo = null;
84 // Culture matches current DTFI. mainly used for string comparisons during parsing.
85 [NonSerialized]private CultureInfo m_cultureInfo = null;
88 // Caches for various properties.
91 internal String amDesignator = null;
92 internal String pmDesignator = null;
93 [OptionalField(VersionAdded = 1)]
94 internal String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
95 [OptionalField(VersionAdded = 1)]
96 internal String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
97 [OptionalField(VersionAdded = 1)]
98 internal String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
99 [OptionalField(VersionAdded = 1)]
100 internal String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
101 internal String monthDayPattern = null;
102 [OptionalField(VersionAdded = 2)] // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
103 internal String dateTimeOffsetPattern = null;
106 // The following are constant values.
108 internal const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
110 // The sortable pattern is based on ISO 8601.
111 internal const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
112 internal const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
115 // The following are affected by calendar settings.
117 internal Calendar calendar = null;
119 internal int firstDayOfWeek = -1;
120 internal int calendarWeekRule = -1;
122 [OptionalField(VersionAdded = 1)]
123 internal String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
125 internal String[] abbreviatedDayNames = null;
127 [OptionalField(VersionAdded = 2)]
128 internal String[] m_superShortDayNames = null;
130 internal String[] dayNames = null;
131 internal String[] abbreviatedMonthNames = null;
132 internal String[] monthNames = null;
133 // Cache the genitive month names that we retrieve from the data table.
134 [OptionalField(VersionAdded = 2)]
135 internal String[] genitiveMonthNames = null;
137 // Cache the abbreviated genitive month names that we retrieve from the data table.
138 [OptionalField(VersionAdded = 2)]
139 internal String[] m_genitiveAbbreviatedMonthNames = null;
141 // Cache the month names of a leap year that we retrieve from the data table.
142 [OptionalField(VersionAdded = 2)]
143 internal String[] leapYearMonthNames = null;
145 // For our "patterns" arrays we have 2 variables, a string and a string[]
147 // The string[] contains the list of patterns, EXCEPT the default may not be included.
148 // The string contains the default pattern.
149 // When we initially construct our string[], we set the string to string[0]
151 // The "default" Date/time patterns
152 internal String longDatePattern = null;
153 internal String shortDatePattern = null;
154 internal String yearMonthPattern = null;
155 internal String longTimePattern = null;
156 internal String shortTimePattern = null;
158 // These are Whidbey-serialization compatable arrays (eg: default not included)
159 // "all" is a bit of a misnomer since the "default" pattern stored above isn't
160 // necessarily a member of the list
161 [OptionalField(VersionAdded = 3)]
162 private String[] allYearMonthPatterns = null; // This was wasn't serialized in Whidbey
163 internal String[] allShortDatePatterns = null;
164 internal String[] allLongDatePatterns = null;
165 internal String[] allShortTimePatterns = null;
166 internal String[] allLongTimePatterns = null;
168 // Cache the era names for this DateTimeFormatInfo instance.
169 internal String[] m_eraNames = null;
170 internal String[] m_abbrevEraNames = null;
171 internal String[] m_abbrevEnglishEraNames = null;
173 internal int[] optionalCalendars = null;
175 private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
177 // CultureInfo updates this
178 internal bool m_isReadOnly=false;
180 // This flag gives hints about if formatting/parsing should perform special code path for things like
181 // genitive form or leap year month names.
182 [OptionalField(VersionAdded = 2)]
183 internal DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
184 internal static bool preferExistingTokens = InitPreferExistingTokens();
187 [System.Security.SecuritySafeCritical]
188 static bool InitPreferExistingTokens()
192 ret = DateTime.LegacyParseMode();
197 private String CultureName
203 m_name = this.m_cultureData.CultureName;
209 private CultureInfo Culture
213 if (m_cultureInfo == null)
215 m_cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
217 return m_cultureInfo;
221 private String LanguageName
223 [System.Security.SecurityCritical] // auto-generated
226 if (m_langName == null)
228 m_langName = this.m_cultureData.SISO639LANGNAME;
234 ////////////////////////////////////////////////////////////////////////////
236 // Create an array of string which contains the abbreviated day names.
238 ////////////////////////////////////////////////////////////////////////////
240 private String[] internalGetAbbreviatedDayOfWeekNames()
242 if (this.abbreviatedDayNames == null)
244 // Get the abbreviated day names for our current calendar
245 this.abbreviatedDayNames = this.m_cultureData.AbbreviatedDayNames(Calendar.ID);
246 Contract.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week");
248 return (this.abbreviatedDayNames);
251 ////////////////////////////////////////////////////////////////////////
253 // Action: Returns the string array of the one-letter day of week names.
255 // an array of one-letter day of week names
261 ////////////////////////////////////////////////////////////////////////
263 private String[] internalGetSuperShortDayNames()
265 if (this.m_superShortDayNames == null)
267 // Get the super short day names for our current calendar
268 this.m_superShortDayNames = this.m_cultureData.SuperShortDayNames(Calendar.ID);
269 Contract.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week");
271 return (this.m_superShortDayNames);
274 ////////////////////////////////////////////////////////////////////////////
276 // Create an array of string which contains the day names.
278 ////////////////////////////////////////////////////////////////////////////
280 private String[] internalGetDayOfWeekNames()
282 if (this.dayNames == null)
284 // Get the day names for our current calendar
285 this.dayNames = this.m_cultureData.DayNames(Calendar.ID);
286 Contract.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
288 return (this.dayNames);
291 ////////////////////////////////////////////////////////////////////////////
293 // Create an array of string which contains the abbreviated month names.
295 ////////////////////////////////////////////////////////////////////////////
297 private String[] internalGetAbbreviatedMonthNames()
299 if (this.abbreviatedMonthNames == null)
301 // Get the month names for our current calendar
302 this.abbreviatedMonthNames = this.m_cultureData.AbbreviatedMonthNames(Calendar.ID);
303 Contract.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13,
304 "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
306 return (this.abbreviatedMonthNames);
310 ////////////////////////////////////////////////////////////////////////////
312 // Create an array of string which contains the month names.
314 ////////////////////////////////////////////////////////////////////////////
316 private String[] internalGetMonthNames()
318 if (this.monthNames == null)
320 // Get the month names for our current calendar
321 this.monthNames = this.m_cultureData.MonthNames(Calendar.ID);
322 Contract.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13,
323 "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
326 return (this.monthNames);
331 // Invariant DateTimeFormatInfo doesn't have user-overriden values
332 // Default calendar is gregorian
333 public DateTimeFormatInfo()
334 : this(CultureInfo.InvariantCulture.m_cultureData,
335 GregorianCalendar.GetDefaultInstance())
339 internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
341 Contract.Requires(cultureData != null);
342 Contract.Requires(cal != null);
344 // Remember our culture
345 this.m_cultureData = cultureData;
347 // m_isDefaultCalendar is set in the setter of Calendar below.
352 [System.Security.SecuritySafeCritical]
354 private void InitializeOverridableProperties(CultureData cultureData, int calendarID)
356 // Silverlight 2.0 never took a snapshot of the user's overridable properties
357 // This has a substantial performance impact so skip when CoreCLR
358 Contract.Requires(cultureData != null);
359 Contract.Assert(calendarID > 0, "[DateTimeFormatInfo.Populate] Expected Calendar.ID > 0");
361 if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
362 if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
364 if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
365 if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
366 if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
367 if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarID); }
369 this.allLongTimePatterns = this.m_cultureData.LongTimes;
370 Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
372 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
373 Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
375 this.allLongDatePatterns = cultureData.LongDates(calendarID);
376 Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
378 this.allShortDatePatterns = cultureData.ShortDates(calendarID);
379 Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
381 this.allYearMonthPatterns = cultureData.YearMonths(calendarID);
382 Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
385 #region Serialization
386 // The following fields are defined to keep the serialization compatibility with .NET V1.0/V1.1.
387 [OptionalField(VersionAdded = 1)]
388 private int CultureID;
389 [OptionalField(VersionAdded = 1)]
390 private bool m_useUserOverride;
391 [OptionalField(VersionAdded = 1)]
392 private bool bUseCalendarInfo;
393 [OptionalField(VersionAdded = 1)]
394 private int nDataItem;
395 [OptionalField(VersionAdded = 2)]
396 internal bool m_isDefaultCalendar; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
397 [OptionalField(VersionAdded = 2)]
398 private static volatile Hashtable s_calendarNativeNames; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
400 // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string
401 // Now we always synthesize so its not helpful
402 [OptionalField(VersionAdded = 1)]
403 internal String[] m_dateWords = null; // calculated, no need to serialze (whidbey expects, arrowhead doesn't)
406 private void OnDeserialized(StreamingContext ctx)
408 if (this.m_name != null)
410 m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
412 if (this.m_cultureData == null)
413 throw new CultureNotFoundException(
414 "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
416 // Note: This is for Everett compatibility
420 m_cultureData = CultureData.GetCultureData(CultureID, m_useUserOverride);
422 if (calendar == null)
424 calendar = (Calendar) GregorianCalendar.GetDefaultInstance().Clone();
425 calendar.SetReadOnlyState(m_isReadOnly);
429 CultureInfo.CheckDomainSafetyObject(calendar, this);
431 InitializeOverridableProperties(m_cultureData, calendar.ID);
434 // turn off read only state till we finish initializing all fields and then store read only state after we are done.
436 bool isReadOnly = m_isReadOnly;
437 m_isReadOnly = false;
439 // If we deserialized defaults ala Whidbey, make sure they're still defaults
440 // Whidbey's arrays could get a bit mixed up.
441 if (longDatePattern != null) this.LongDatePattern = longDatePattern;
442 if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern;
443 if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern;
444 if (longTimePattern != null) this.LongTimePattern = longTimePattern;
445 if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern;
447 m_isReadOnly = isReadOnly;
451 private void OnSerializing(StreamingContext ctx)
454 CultureID = this.m_cultureData.ILANGUAGE; // Used for serialization compatibility with Whidbey which didn't always serialize the name
456 m_useUserOverride = this.m_cultureData.UseUserOverride;
458 // make sure the m_name is initialized.
459 m_name = this.CultureName;
462 if (s_calendarNativeNames == null)
463 s_calendarNativeNames = new Hashtable();
464 #endif // FEATURE_CORECLR
466 // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey
467 // because Whidbey try to initialize some of these fields using calendar data which could be null values
468 // and then we get exceptions. So we call the accessors to force the caches to get loaded.
470 o = this.LongTimePattern;
471 o = this.LongDatePattern;
472 o = this.ShortTimePattern;
473 o = this.ShortDatePattern;
474 o = this.YearMonthPattern;
475 o = this.AllLongTimePatterns;
476 o = this.AllLongDatePatterns;
477 o = this.AllShortTimePatterns;
478 o = this.AllShortDatePatterns;
479 o = this.AllYearMonthPatterns;
481 #endregion Serialization
483 // Returns a default DateTimeFormatInfo that will be universally
484 // supported and constant irrespective of the current culture.
485 // Used by FromString methods.
488 public static DateTimeFormatInfo InvariantInfo {
490 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
491 if (invariantInfo == null)
493 DateTimeFormatInfo info = new DateTimeFormatInfo();
494 info.Calendar.SetReadOnlyState(true);
495 info.m_isReadOnly = true;
496 invariantInfo = info;
498 return (invariantInfo);
502 // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
505 public static DateTimeFormatInfo CurrentInfo {
507 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
508 System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
509 if (!culture.m_isInherited) {
510 DateTimeFormatInfo info = culture.dateTimeInfo;
515 return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
520 public static DateTimeFormatInfo GetInstance(IFormatProvider provider) {
521 // Fast case for a regular CultureInfo
522 DateTimeFormatInfo info;
523 CultureInfo cultureProvider = provider as CultureInfo;
524 if (cultureProvider != null && !cultureProvider.m_isInherited)
526 return cultureProvider.DateTimeFormat;
528 // Fast case for a DTFI;
529 info = provider as DateTimeFormatInfo;
533 // Wasn't cultureInfo or DTFI, do it the slower way
534 if (provider != null) {
535 info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo;
540 // Couldn't get anything, just use currentInfo as fallback
545 public Object GetFormat(Type formatType)
547 return (formatType == typeof(DateTimeFormatInfo)? this: null);
551 public Object Clone()
553 DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
554 // We can use the data member calendar in the setter, instead of the property Calendar,
555 // since the cloned copy should have the same state as the original copy.
556 n.calendar = (Calendar) this.Calendar.Clone();
557 n.m_isReadOnly = false;
562 public String AMDesignator
565 [System.Security.SecuritySafeCritical] // auto-generated
570 if (this.amDesignator == null)
572 this.amDesignator = this.m_cultureData.SAM1159;
575 Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
576 return (this.amDesignator);
582 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
585 throw new ArgumentNullException("value",
586 Environment.GetResourceString("ArgumentNull_String"));
588 Contract.EndContractBlock();
589 ClearTokenHashTable();
590 amDesignator = value;
595 public Calendar Calendar {
597 Contract.Ensures(Contract.Result<Calendar>() != null);
599 Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
600 return (this.calendar);
605 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
607 throw new ArgumentNullException("value",
608 Environment.GetResourceString("ArgumentNull_Obj"));
610 Contract.EndContractBlock();
611 if (value == calendar) {
616 // Because the culture is agile object which can be attached to a thread and then thread can travel
617 // to another app domain then we prevent attaching any customized object to culture that we cannot contol.
619 CultureInfo.CheckDomainSafetyObject(value, this);
621 for (int i = 0; i < this.OptionalCalendars.Length; i++)
623 if (this.OptionalCalendars[i] == value.ID)
625 // We can use this one, so do so.
627 // Clean related properties if we already had a calendar set
628 if (calendar != null)
630 // clean related properties which are affected by the calendar setting,
631 // so that they will be refreshed when they are accessed next time.
634 // These properites are in the order as appearing in calendar.xml.
636 m_abbrevEraNames = null;
637 m_abbrevEnglishEraNames = null;
639 monthDayPattern = null;
642 abbreviatedDayNames = null;
643 m_superShortDayNames = null;
645 abbreviatedMonthNames = null;
646 genitiveMonthNames = null;
647 m_genitiveAbbreviatedMonthNames = null;
648 leapYearMonthNames = null;
649 formatFlags = DateTimeFormatFlags.NotInitialized;
651 allShortDatePatterns = null;
652 allLongDatePatterns = null;
653 allYearMonthPatterns = null;
654 dateTimeOffsetPattern = null;
656 // The defaults need reset as well:
657 longDatePattern = null;
658 shortDatePattern = null;
659 yearMonthPattern = null;
661 // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
662 fullDateTimePattern = null; // Long date + long time
663 generalShortTimePattern = null; // short date + short time
664 generalLongTimePattern = null; // short date + long time
666 // Derived item that changes
667 dateSeparator = null;
669 // We don't need to do these because they are not changed by changing calendar
677 // We don't need to clear these because they're only used for whidbey compat serialization
678 // the only values we use are the all...Patterns[0]
683 // remember to reload tokens
684 ClearTokenHashTable();
687 // Remember the new calendar
689 InitializeOverridableProperties(m_cultureData, calendar.ID);
691 // We succeeded, return
696 // The assigned calendar is not a valid calendar for this culture, throw
697 throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("Argument_InvalidCalendar"));
701 private int[] OptionalCalendars {
703 if (this.optionalCalendars == null) {
704 this.optionalCalendars = this.m_cultureData.CalendarIds;
706 return (this.optionalCalendars);
710 /*=================================GetEra==========================
711 **Action: Get the era value by parsing the name of the era.
712 **Returns: The era value for the specified era name.
713 ** -1 if the name of the era is not valid or not supported.
714 **Arguments: eraName the name of the era.
716 ============================================================================*/
719 public int GetEra(String eraName) {
720 if (eraName == null) {
721 throw new ArgumentNullException("eraName",
722 Environment.GetResourceString("ArgumentNull_String"));
724 Contract.EndContractBlock();
726 // For Geo-Political reasons, the Era Name and Abbreviated Era Name
727 // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
728 // would be matched below) but we don't want the empty string to give
730 // confer 85900 DTFI.GetEra("") should fail on all cultures
731 if (eraName.Length == 0) {
735 // The following is based on the assumption that the era value is starting from 1, and has a
737 // If that ever changes, the code has to be changed.
739 // The calls to String.Compare should use the current culture for the string comparisons, but the
740 // invariant culture when comparing against the english names.
741 for (int i = 0; i < EraNames.Length; i++) {
742 // Compare the era name in a case-insensitive way for the appropriate culture.
743 if (m_eraNames[i].Length > 0) {
744 if (String.Compare(eraName, m_eraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
749 for (int i = 0; i < AbbreviatedEraNames.Length; i++) {
750 // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
751 if (String.Compare(eraName, m_abbrevEraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
755 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
756 // this comparison should use the InvariantCulture. The English name could have linguistically
757 // interesting characters.
758 if (String.Compare(eraName, m_abbrevEnglishEraNames[i], StringComparison.InvariantCultureIgnoreCase)==0) {
765 internal String[] EraNames
769 if (this.m_eraNames == null)
771 this.m_eraNames = this.m_cultureData.EraNames(Calendar.ID);;
773 return (this.m_eraNames);
777 /*=================================GetEraName==========================
778 **Action: Get the name of the era for the specified era value.
779 **Returns: The name of the specified era.
781 ** era the era value.
783 ** ArguementException if the era valie is invalid.
784 ============================================================================*/
786 // Era names are 1 indexed
787 public String GetEraName(int era) {
788 if (era == Calendar.CurrentEra) {
789 era = Calendar.CurrentEraValue;
792 // The following is based on the assumption that the era value is starting from 1, and has a
794 // If that ever changes, the code has to be changed.
795 if ((--era) < EraNames.Length && (era >= 0)) {
796 return (m_eraNames[era]);
798 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
801 internal String[] AbbreviatedEraNames
805 if (this.m_abbrevEraNames == null)
807 this.m_abbrevEraNames = this.m_cultureData.AbbrevEraNames(Calendar.ID);
809 return (this.m_abbrevEraNames);
813 // Era names are 1 indexed
814 public String GetAbbreviatedEraName(int era) {
815 if (AbbreviatedEraNames.Length == 0) {
816 // If abbreviation era name is not used in this culture,
817 // return the full era name.
818 return (GetEraName(era));
820 if (era == Calendar.CurrentEra) {
821 era = Calendar.CurrentEraValue;
823 if ((--era) < m_abbrevEraNames.Length && (era >= 0)) {
824 return (m_abbrevEraNames[era]);
826 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
829 internal String[] AbbreviatedEnglishEraNames
833 if (this.m_abbrevEnglishEraNames == null)
835 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
836 this.m_abbrevEnglishEraNames = this.m_cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
838 return (this.m_abbrevEnglishEraNames);
843 // Note that cultureData derives this from the short date format (unless someone's set this previously)
844 // Note that this property is quite undesirable.
845 public String DateSeparator
850 if (this.dateSeparator == null)
852 this.dateSeparator = this.m_cultureData.DateSeparator(Calendar.ID);
855 Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
856 return (this.dateSeparator);
862 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
865 throw new ArgumentNullException("value",
866 Environment.GetResourceString("ArgumentNull_String"));
868 Contract.EndContractBlock();
869 ClearTokenHashTable();
870 this.dateSeparator = value;
875 public DayOfWeek FirstDayOfWeek
880 if (this.firstDayOfWeek == -1)
882 this.firstDayOfWeek = this.m_cultureData.IFIRSTDAYOFWEEK;
885 Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
887 return ((DayOfWeek)this.firstDayOfWeek);
892 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
893 if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) {
894 firstDayOfWeek = (int)value;
896 throw new ArgumentOutOfRangeException(
897 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
898 DayOfWeek.Sunday, DayOfWeek.Saturday));
904 public CalendarWeekRule CalendarWeekRule
909 if (this.calendarWeekRule == -1)
911 this.calendarWeekRule = this.m_cultureData.IFIRSTWEEKOFYEAR;
914 Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
915 return ((CalendarWeekRule)this.calendarWeekRule);
920 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
921 if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) {
922 calendarWeekRule = (int)value;
924 throw new ArgumentOutOfRangeException(
925 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
926 CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
933 public String FullDateTimePattern
937 if (fullDateTimePattern == null)
939 fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
941 return (fullDateTimePattern);
946 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
948 throw new ArgumentNullException("value",
949 Environment.GetResourceString("ArgumentNull_String"));
951 Contract.EndContractBlock();
952 fullDateTimePattern = value;
957 // For our "patterns" arrays we have 2 variables, a string and a string[]
959 // The string[] contains the list of patterns, EXCEPT the default may not be included.
960 // The string contains the default pattern.
961 // When we initially construct our string[], we set the string to string[0]
962 public String LongDatePattern
966 // Initialize our long date pattern from the 1st array value if not set
967 if (this.longDatePattern == null)
969 // Initialize our data
970 this.longDatePattern = this.UnclonedLongDatePatterns[0];
973 return this.longDatePattern;
978 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
980 throw new ArgumentNullException("value",
981 Environment.GetResourceString("ArgumentNull_String"));
983 Contract.EndContractBlock();
985 // Remember the new string
986 this.longDatePattern = value;
988 // Clear the token hash table
989 ClearTokenHashTable();
991 // Clean up cached values that will be affected by this property.
992 this.fullDateTimePattern = null;
996 // For our "patterns" arrays we have 2 variables, a string and a string[]
998 // The string[] contains the list of patterns, EXCEPT the default may not be included.
999 // The string contains the default pattern.
1000 // When we initially construct our string[], we set the string to string[0]
1001 public String LongTimePattern
1005 // Initialize our long time pattern from the 1st array value if not set
1006 if (this.longTimePattern == null)
1008 // Initialize our data
1009 this.longTimePattern = this.UnclonedLongTimePatterns[0];
1012 return this.longTimePattern;
1017 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1018 if (value == null) {
1019 throw new ArgumentNullException("value",
1020 Environment.GetResourceString("ArgumentNull_String"));
1022 Contract.EndContractBlock();
1024 // Remember the new string
1025 this.longTimePattern = value;
1027 // Clear the token hash table
1028 ClearTokenHashTable();
1030 // Clean up cached values that will be affected by this property.
1031 this.fullDateTimePattern = null; // Full date = long date + long Time
1032 this.generalLongTimePattern = null; // General long date = short date + long Time
1033 this.dateTimeOffsetPattern = null;
1038 // Note: just to be confusing there's only 1 month day pattern, not a whole list
1039 public String MonthDayPattern
1043 if (this.monthDayPattern == null)
1045 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
1046 this.monthDayPattern = this.m_cultureData.MonthDay(Calendar.ID);
1048 Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
1049 return (this.monthDayPattern);
1054 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1055 if (value == null) {
1056 throw new ArgumentNullException("value",
1057 Environment.GetResourceString("ArgumentNull_String"));
1059 Contract.EndContractBlock();
1061 this.monthDayPattern = value;
1066 public String PMDesignator
1069 [System.Security.SecuritySafeCritical] // auto-generated
1074 if (this.pmDesignator == null)
1076 this.pmDesignator = this.m_cultureData.SPM2359;
1079 Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
1080 return (this.pmDesignator);
1085 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1086 if (value == null) {
1087 throw new ArgumentNullException("value",
1088 Environment.GetResourceString("ArgumentNull_String"));
1090 Contract.EndContractBlock();
1091 ClearTokenHashTable();
1093 pmDesignator = value;
1099 public String RFC1123Pattern
1103 return (rfc1123Pattern);
1107 // For our "patterns" arrays we have 2 variables, a string and a string[]
1109 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1110 // The string contains the default pattern.
1111 // When we initially construct our string[], we set the string to string[0]
1112 public String ShortDatePattern
1116 // Initialize our short date pattern from the 1st array value if not set
1117 if (this.shortDatePattern == null)
1119 // Initialize our data
1120 this.shortDatePattern = this.UnclonedShortDatePatterns[0];
1123 return this.shortDatePattern;
1129 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1131 throw new ArgumentNullException("value",
1132 Environment.GetResourceString("ArgumentNull_String"));
1133 Contract.EndContractBlock();
1135 // Remember the new string
1136 this.shortDatePattern = value;
1138 // Clear the token hash table, note that even short dates could require this
1139 ClearTokenHashTable();
1141 // Clean up cached values that will be affected by this property.
1142 generalLongTimePattern = null; // General long time = short date + long time
1143 generalShortTimePattern = null; // General short time = short date + short Time
1144 dateTimeOffsetPattern = null;
1149 // For our "patterns" arrays we have 2 variables, a string and a string[]
1151 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1152 // The string contains the default pattern.
1153 // When we initially construct our string[], we set the string to string[0]
1154 public String ShortTimePattern
1158 // Initialize our short time pattern from the 1st array value if not set
1159 if (this.shortTimePattern == null)
1161 // Initialize our data
1162 this.shortTimePattern = this.UnclonedShortTimePatterns[0];
1164 return this.shortTimePattern;
1169 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1170 if (value == null) {
1171 throw new ArgumentNullException("value",
1172 Environment.GetResourceString("ArgumentNull_String"));
1174 Contract.EndContractBlock();
1176 // Remember the new string
1177 this.shortTimePattern= value;
1179 // Clear the token hash table, note that even short times could require this
1180 ClearTokenHashTable();
1182 // Clean up cached values that will be affected by this property.
1183 generalShortTimePattern = null; // General short date = short date + short time.
1188 public String SortableDateTimePattern {
1190 return (sortableDateTimePattern);
1194 /*=================================GeneralShortTimePattern=====================
1195 **Property: Return the pattern for 'g' general format: shortDate + short time
1196 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1197 ** We put this internal property here so that we can avoid doing the
1198 ** concatation every time somebody asks for the general format.
1199 ==============================================================================*/
1201 internal String GeneralShortTimePattern {
1203 if (generalShortTimePattern == null) {
1204 generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
1206 return (generalShortTimePattern);
1210 /*=================================GeneralLongTimePattern=====================
1211 **Property: Return the pattern for 'g' general format: shortDate + Long time
1212 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1213 ** We put this internal property here so that we can avoid doing the
1214 ** concatation every time somebody asks for the general format.
1215 ==============================================================================*/
1217 internal String GeneralLongTimePattern {
1219 if (generalLongTimePattern == null) {
1220 generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
1222 return (generalLongTimePattern);
1226 /*=================================DateTimeOffsetPattern==========================
1227 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1228 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1229 ** We put this internal property here so that we can avoid doing the
1230 ** concatation every time somebody uses this form
1231 ==============================================================================*/
1233 /*=================================DateTimeOffsetPattern==========================
1234 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1235 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1236 ** We put this internal property here so that we can avoid doing the
1237 ** concatation every time somebody uses this form
1238 ==============================================================================*/
1240 internal String DateTimeOffsetPattern {
1242 if (dateTimeOffsetPattern == null) {
1244 string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
1246 /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
1248 bool foundZ = false;
1249 bool inQuote = false;
1251 for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) {
1252 switch (LongTimePattern[i]) {
1254 /* if we aren't in a quote, we've found a z */
1256 /* we'll fall out of the loop now because the test includes !foundZ */
1260 if (inQuote && (quote == LongTimePattern[i])) {
1261 /* we were in a quote and found a matching exit quote, so we are outside a quote now */
1263 } else if (!inQuote) {
1264 quote = LongTimePattern[i];
1267 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
1272 i++; /* skip next character that is escaped by this backslash */
1280 dateTimePattern = dateTimePattern + " zzz";
1283 dateTimeOffsetPattern = dateTimePattern;
1285 return (dateTimeOffsetPattern);
1289 // Note that cultureData derives this from the long time format (unless someone's set this previously)
1290 // Note that this property is quite undesirable.
1291 public String TimeSeparator
1296 if (timeSeparator == null)
1298 timeSeparator = this.m_cultureData.TimeSeparator;
1301 Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
1302 return (timeSeparator);
1308 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1311 throw new ArgumentNullException("value",
1312 Environment.GetResourceString("ArgumentNull_String"));
1314 Contract.EndContractBlock();
1315 ClearTokenHashTable();
1317 timeSeparator = value;
1322 public String UniversalSortableDateTimePattern
1326 return (universalSortableDateTimePattern);
1330 // For our "patterns" arrays we have 2 variables, a string and a string[]
1332 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1333 // The string contains the default pattern.
1334 // When we initially construct our string[], we set the string to string[0]
1335 public String YearMonthPattern
1339 // Initialize our year/month pattern from the 1st array value if not set
1340 if (this.yearMonthPattern == null)
1342 // Initialize our data
1343 this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
1345 return this.yearMonthPattern;
1350 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1351 if (value == null) {
1352 throw new ArgumentNullException("value",
1353 Environment.GetResourceString("ArgumentNull_String"));
1355 Contract.EndContractBlock();
1357 // Remember the new string
1358 this.yearMonthPattern = value;
1360 // Clear the token hash table, note that even short times could require this
1361 ClearTokenHashTable();
1366 // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
1368 static private void CheckNullValue(String[] values, int length) {
1369 Contract.Requires(values != null, "value != null");
1370 Contract.Requires(values.Length >= length);
1371 for (int i = 0; i < length; i++) {
1372 if (values[i] == null) {
1373 throw new ArgumentNullException("value",
1374 Environment.GetResourceString("ArgumentNull_ArrayValue"));
1380 public String[] AbbreviatedDayNames
1384 return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
1389 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1390 if (value == null) {
1391 throw new ArgumentNullException("value",
1392 Environment.GetResourceString("ArgumentNull_Array"));
1394 if (value.Length != 7) {
1395 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1397 Contract.EndContractBlock();
1398 CheckNullValue(value, value.Length);
1399 ClearTokenHashTable();
1401 abbreviatedDayNames = value;
1406 // Returns the string array of the one-letter day of week names.
1407 [System.Runtime.InteropServices.ComVisible(false)]
1408 public String[] ShortestDayNames
1412 return ((String[])internalGetSuperShortDayNames().Clone());
1417 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1418 if (value == null) {
1419 throw new ArgumentNullException("value",
1420 Environment.GetResourceString("ArgumentNull_Array"));
1422 if (value.Length != 7)
1424 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1426 Contract.EndContractBlock();
1427 CheckNullValue(value, value.Length);
1428 this.m_superShortDayNames = value;
1433 public String[] DayNames
1437 return ((String[])internalGetDayOfWeekNames().Clone());
1442 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1443 if (value == null) {
1444 throw new ArgumentNullException("value",
1445 Environment.GetResourceString("ArgumentNull_Array"));
1447 if (value.Length != 7)
1449 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1451 Contract.EndContractBlock();
1452 CheckNullValue(value, value.Length);
1453 ClearTokenHashTable();
1460 public String[] AbbreviatedMonthNames {
1462 return ((String[])internalGetAbbreviatedMonthNames().Clone());
1467 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1468 if (value == null) {
1469 throw new ArgumentNullException("value",
1470 Environment.GetResourceString("ArgumentNull_Array"));
1472 if (value.Length != 13)
1474 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1476 Contract.EndContractBlock();
1477 CheckNullValue(value, value.Length - 1);
1478 ClearTokenHashTable();
1479 abbreviatedMonthNames = value;
1484 public String[] MonthNames
1488 return ((String[])internalGetMonthNames().Clone());
1493 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1494 if (value == null) {
1495 throw new ArgumentNullException("value",
1496 Environment.GetResourceString("ArgumentNull_Array"));
1498 if (value.Length != 13)
1500 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1502 Contract.EndContractBlock();
1503 CheckNullValue(value, value.Length - 1);
1505 ClearTokenHashTable();
1509 // Whitespaces that we allow in the month names.
1510 // U+00a0 is non-breaking space.
1511 static char[] MonthSpaces = {' ', '\u00a0'};
1513 internal bool HasSpacesInMonthNames {
1515 return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
1519 internal bool HasSpacesInDayNames {
1521 return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
1527 // internalGetMonthName
1529 // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
1533 // style To indicate a form like regular/genitive/month name in a leap year.
1534 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1536 // ArgumentOutOfRangeException When month name is invalid.
1538 internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) {
1540 // Right now, style is mutual exclusive, but I make the style to be flag so that
1541 // maybe we can combine flag if there is such a need.
1543 String[] monthNamesArray = null;
1545 case MonthNameStyles.Genitive:
1546 monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
1548 case MonthNameStyles.LeapYear:
1549 monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
1552 monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames(): internalGetMonthNames());
1555 // The month range is from 1 ~ this.m_monthNames.Length
1556 // (actually is 13 right now for all cases)
1557 if ((month < 1) || (month > monthNamesArray.Length)) {
1558 throw new ArgumentOutOfRangeException(
1559 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1560 1, monthNamesArray.Length));
1562 return (monthNamesArray[month-1]);
1566 // internalGetGenitiveMonthNames
1568 // Action: Retrieve the array which contains the month names in genitive form.
1569 // If this culture does not use the gentive form, the normal month name is returned.
1571 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1573 private String[] internalGetGenitiveMonthNames(bool abbreviated) {
1575 if (this.m_genitiveAbbreviatedMonthNames == null)
1577 this.m_genitiveAbbreviatedMonthNames = this.m_cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
1578 Contract.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
1579 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
1581 return (this.m_genitiveAbbreviatedMonthNames);
1584 if (this.genitiveMonthNames == null)
1586 this.genitiveMonthNames = this.m_cultureData.GenitiveMonthNames(this.Calendar.ID);
1587 Contract.Assert(this.genitiveMonthNames.Length == 13,
1588 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
1590 return (this.genitiveMonthNames);
1594 // internalGetLeapYearMonthNames
1596 // Actions: Retrieve the month names used in a leap year.
1597 // If this culture does not have different month names in a leap year, the normal month name is returned.
1598 // Agruments: None. (can use abbreviated later if needed)
1600 internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) {
1601 if (this.leapYearMonthNames == null)
1603 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
1604 this.leapYearMonthNames = this.m_cultureData.LeapYearMonthNames(Calendar.ID);
1605 Contract.Assert(this.leapYearMonthNames.Length == 13,
1606 "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
1608 return (leapYearMonthNames);
1612 public String GetAbbreviatedDayName(DayOfWeek dayofweek)
1615 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1616 throw new ArgumentOutOfRangeException(
1617 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1618 DayOfWeek.Sunday, DayOfWeek.Saturday));
1620 Contract.EndContractBlock();
1622 // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
1623 // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
1625 return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
1629 // Returns the super short day of week names for the specified day of week.
1630 [System.Runtime.InteropServices.ComVisible(false)]
1631 public String GetShortestDayName(DayOfWeek dayOfWeek)
1634 if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) {
1635 throw new ArgumentOutOfRangeException(
1636 "dayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1637 DayOfWeek.Sunday, DayOfWeek.Saturday));
1639 Contract.EndContractBlock();
1641 // Don't call the public property SuperShortDayNames here since a clone is needed in that
1642 // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
1644 return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
1647 // Get all possible combination of inputs
1648 static private String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
1650 Contract.Requires(patterns1 != null);
1651 Contract.Requires(patterns2 != null);
1654 String[] result = new String[patterns1.Length * patterns2.Length];
1656 // Counter of actual results
1658 for (int i = 0; i < patterns1.Length; i++)
1660 for (int j = 0; j < patterns2.Length; j++)
1662 // Can't combine if null or empty
1663 result[k++] = patterns1[i] + connectString + patterns2[j];
1667 // Return the combinations
1672 public String[] GetAllDateTimePatterns()
1674 List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
1676 for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
1678 String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
1679 for (int j = 0; j < strings.Length; j++)
1681 results.Add(strings[j]);
1684 return results.ToArray();
1688 public String[] GetAllDateTimePatterns(char format)
1690 Contract.Ensures(Contract.Result<String[]>() != null);
1691 String [] result = null;
1696 result = this.AllShortDatePatterns;
1699 result = this.AllLongDatePatterns;
1702 result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
1706 result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
1709 result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
1712 result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
1716 result = new String[] {MonthDayPattern};
1720 result = new String[] {DateTimeFormat.RoundtripFormat};
1724 result = new String[] {rfc1123Pattern};
1727 result = new String[] {sortableDateTimePattern};
1730 result = this.AllShortTimePatterns;
1733 result = this.AllLongTimePatterns;
1736 result = new String[] {UniversalSortableDateTimePattern};
1740 result = this.AllYearMonthPatterns;
1743 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
1749 public String GetDayName(DayOfWeek dayofweek)
1751 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1752 throw new ArgumentOutOfRangeException(
1753 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1754 DayOfWeek.Sunday, DayOfWeek.Saturday));
1756 Contract.EndContractBlock();
1758 // Use the internal one so that we don't clone the array unnecessarily
1759 return (internalGetDayOfWeekNames()[(int)dayofweek]);
1764 public String GetAbbreviatedMonthName(int month)
1766 if (month < 1 || month > 13) {
1767 throw new ArgumentOutOfRangeException(
1768 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1771 Contract.EndContractBlock();
1772 // Use the internal one so we don't clone the array unnecessarily
1773 return (internalGetAbbreviatedMonthNames()[month-1]);
1777 public String GetMonthName(int month)
1779 if (month < 1 || month > 13) {
1780 throw new ArgumentOutOfRangeException(
1781 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1784 Contract.EndContractBlock();
1785 // Use the internal one so we don't clone the array unnecessarily
1786 return (internalGetMonthNames()[month-1]);
1789 // For our "patterns" arrays we have 2 variables, a string and a string[]
1791 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1792 // The string contains the default pattern.
1793 // When we initially construct our string[], we set the string to string[0]
1795 // The resulting [] can get returned to the calling app, so clone it.
1796 private static string[] GetMergedPatterns(string [] patterns, string defaultPattern)
1798 Contract.Assert(patterns != null && patterns.Length > 0,
1799 "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
1800 Contract.Assert(defaultPattern != null,
1801 "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
1803 // If the default happens to be the first in the list just return (a cloned) copy
1804 if (defaultPattern == patterns[0])
1806 return (string[])patterns.Clone();
1809 // We either need a bigger list, or the pattern from the list.
1811 for (i = 0; i < patterns.Length; i++)
1813 // Stop if we found it
1814 if (defaultPattern == patterns[i])
1818 // Either way we're going to need a new array
1819 string[] newPatterns;
1822 if (i < patterns.Length)
1824 // Found it, output will be same size
1825 newPatterns = (string[])patterns.Clone();
1827 // Have to move [0] item to [i] so we can re-write default at [0]
1828 // (remember defaultPattern == [i] so this is OK)
1829 newPatterns[i] = newPatterns[0];
1833 // Not found, make room for it
1834 newPatterns = new String[patterns.Length + 1];
1836 // Copy existing array
1837 Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
1840 // Remember the default
1841 newPatterns[0] = defaultPattern;
1843 // Return the reconstructed list
1847 // Default string isn't necessarily in our string array, so get the
1848 // merged patterns of both
1849 private String[] AllYearMonthPatterns
1853 return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
1857 private String[] AllShortDatePatterns
1861 return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
1865 private String[] AllShortTimePatterns
1869 return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
1873 private String[] AllLongDatePatterns
1877 return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
1881 private String[] AllLongTimePatterns
1885 return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
1889 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1890 // This won't include default, call AllYearMonthPatterns
1891 private String[] UnclonedYearMonthPatterns
1895 if (this.allYearMonthPatterns == null)
1897 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
1898 this.allYearMonthPatterns = this.m_cultureData.YearMonths(this.Calendar.ID);
1899 Contract.Assert(this.allYearMonthPatterns.Length > 0,
1900 "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
1903 return this.allYearMonthPatterns;
1908 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1909 // This won't include default, call AllShortDatePatterns
1910 private String [] UnclonedShortDatePatterns
1914 if (allShortDatePatterns == null)
1916 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
1917 this.allShortDatePatterns = this.m_cultureData.ShortDates(this.Calendar.ID);
1918 Contract.Assert(this.allShortDatePatterns.Length > 0,
1919 "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
1922 return this.allShortDatePatterns;
1926 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1927 // This won't include default, call AllLongDatePatterns
1928 private String[] UnclonedLongDatePatterns
1932 if (allLongDatePatterns == null)
1934 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
1935 this.allLongDatePatterns = this.m_cultureData.LongDates(this.Calendar.ID);
1936 Contract.Assert(this.allLongDatePatterns.Length > 0,
1937 "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
1940 return this.allLongDatePatterns;
1944 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1945 // This won't include default, call AllShortTimePatterns
1946 private String[] UnclonedShortTimePatterns
1950 if (this.allShortTimePatterns == null)
1952 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
1953 Contract.Assert(this.allShortTimePatterns.Length > 0,
1954 "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
1957 return this.allShortTimePatterns;
1961 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1962 // This won't include default, call AllLongTimePatterns
1963 private String[] UnclonedLongTimePatterns
1967 if (this.allLongTimePatterns == null)
1969 this.allLongTimePatterns = this.m_cultureData.LongTimes;
1970 Contract.Assert(this.allLongTimePatterns.Length > 0,
1971 "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
1974 return this.allLongTimePatterns;
1978 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) {
1980 throw new ArgumentNullException("dtfi",
1981 Environment.GetResourceString("ArgumentNull_Obj"));
1983 Contract.EndContractBlock();
1984 if (dtfi.IsReadOnly) {
1987 DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
1988 // We can use the data member calendar in the setter, instead of the property Calendar,
1989 // since the cloned copy should have the same state as the original copy.
1990 newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
1991 newInfo.m_isReadOnly = true;
1996 public bool IsReadOnly {
1998 return (m_isReadOnly);
2002 // Return the native name for the calendar in DTFI.Calendar. The native name is referred to
2003 // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese.
2004 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
2005 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
2006 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
2007 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
2008 [System.Runtime.InteropServices.ComVisible(false)]
2009 public String NativeCalendarName
2013 return m_cultureData.CalendarName(Calendar.ID);
2018 // Used by custom cultures and others to set the list of available formats. Note that none of them are
2019 // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
2022 // Most of the format characters that can be used in GetAllDateTimePatterns are
2023 // not really needed since they are one of the following:
2025 // r/R/s/u locale-independent constants -- cannot be changed!
2026 // m/M/y/Y fields with a single string in them -- that can be set through props directly
2027 // f/F/g/G/U derived fields based on combinations of various of the below formats
2029 // NOTE: No special validation is done here beyond what is done when the actual respective fields
2030 // are used (what would be the point of disallowing here what we allow in the appropriate property?)
2032 // WARNING: If more validation is ever done in one place, it should be done in the other.
2035 [System.Runtime.InteropServices.ComVisible(false)]
2036 public void SetAllDateTimePatterns(String[] patterns, char format)
2039 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2040 if (patterns == null) {
2041 throw new ArgumentNullException("patterns",
2042 Environment.GetResourceString("ArgumentNull_Array"));
2045 if (patterns.Length == 0)
2047 throw new ArgumentException(Environment.GetResourceString("Arg_ArrayZeroError"), "patterns");
2049 Contract.EndContractBlock();
2051 for (int i=0; i<patterns.Length; i++)
2053 if (patterns[i] == null)
2055 throw new ArgumentNullException("patterns[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayValue"));
2059 // Remember the patterns, and use the 1st as default
2063 this.allShortDatePatterns = patterns;
2064 this.shortDatePattern = this.allShortDatePatterns[0];
2068 this.allLongDatePatterns = patterns;
2069 this.longDatePattern = this.allLongDatePatterns[0];
2073 this.allShortTimePatterns = patterns;
2074 this.shortTimePattern = this.allShortTimePatterns[0];
2078 this.allLongTimePatterns = patterns;
2079 this.longTimePattern = this.allLongTimePatterns[0];
2084 this.allYearMonthPatterns = patterns;
2085 this.yearMonthPattern = this.allYearMonthPatterns[0];
2089 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
2092 // Clear the token hash table, note that even short dates could require this
2093 ClearTokenHashTable();
2098 [System.Runtime.InteropServices.ComVisible(false)]
2099 public String[] AbbreviatedMonthGenitiveNames
2103 return ((String[])internalGetGenitiveMonthNames(true).Clone());
2109 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2112 throw new ArgumentNullException("value",
2113 Environment.GetResourceString("ArgumentNull_Array"));
2115 if (value.Length != 13)
2117 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2119 Contract.EndContractBlock();
2120 CheckNullValue(value, value.Length - 1);
2121 ClearTokenHashTable();
2122 this.m_genitiveAbbreviatedMonthNames= value;
2126 [System.Runtime.InteropServices.ComVisible(false)]
2127 public String[] MonthGenitiveNames
2131 return ((String[])internalGetGenitiveMonthNames(false).Clone());
2137 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2140 throw new ArgumentNullException("value",
2141 Environment.GetResourceString("ArgumentNull_Array"));
2143 if (value.Length != 13)
2145 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2147 Contract.EndContractBlock();
2148 CheckNullValue(value, value.Length - 1);
2149 genitiveMonthNames= value;
2150 ClearTokenHashTable();
2155 // Positive TimeSpan Pattern
2158 private string m_fullTimeSpanPositivePattern;
2159 internal String FullTimeSpanPositivePattern
2163 if (m_fullTimeSpanPositivePattern == null)
2165 CultureData cultureDataWithoutUserOverrides;
2166 if (m_cultureData.UseUserOverride)
2167 cultureDataWithoutUserOverrides = CultureData.GetCultureData(m_cultureData.CultureName, false);
2169 cultureDataWithoutUserOverrides = m_cultureData;
2170 String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
2172 m_fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
2174 return m_fullTimeSpanPositivePattern;
2179 // Negative TimeSpan Pattern
2182 private string m_fullTimeSpanNegativePattern;
2183 internal String FullTimeSpanNegativePattern
2187 if (m_fullTimeSpanNegativePattern == null)
2188 m_fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
2189 return m_fullTimeSpanNegativePattern;
2194 // Get suitable CompareInfo from current DTFI object.
2196 internal CompareInfo CompareInfo
2200 if (m_compareInfo == null)
2202 // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
2203 // CompareInfo cache. otherwise we would just create CompareInfo using m_cultureData.
2204 m_compareInfo = CompareInfo.GetCompareInfo(m_cultureData.SCOMPAREINFO);
2207 return m_compareInfo;
2212 internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
2213 | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
2214 | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
2215 | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
2217 internal static void ValidateStyles(DateTimeStyles style, String parameterName) {
2218 if ((style & InvalidDateTimeStyles) != 0) {
2219 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidDateTimeStyles"), parameterName);
2221 if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) {
2222 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeStyles"), parameterName);
2224 Contract.EndContractBlock();
2225 if (((style & DateTimeStyles.RoundtripKind) != 0)
2226 && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) {
2227 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeRoundtripStyles"), parameterName);
2232 // Actions: Return the internal flag used in formatting and parsing.
2233 // 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.
2235 internal DateTimeFormatFlags FormatFlags
2239 if (formatFlags == DateTimeFormatFlags.NotInitialized)
2241 // Build the format flags from the data in this DTFI
2242 formatFlags = DateTimeFormatFlags.None;
2243 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
2244 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2245 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
2246 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2247 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames);
2248 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
2250 return (formatFlags);
2254 internal Boolean HasForceTwoDigitYears {
2256 switch (calendar.ID)
2258 // If is y/yy, do not get (year % 100). "y" will print
2259 // year without leading zero. "yy" will print year with two-digit in leading zero.
2260 // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
2261 // So year 5 is "05", and year 125 is "125".
2262 // The reason for not doing (year % 100) is for Taiwan calendar.
2263 // If year 125, then output 125 and not 25.
2264 // Note: OS uses "yyyy" for Taiwan calendar by default.
2265 case (Calendar.CAL_JAPAN):
2266 case (Calendar.CAL_TAIWAN):
2273 // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
2274 internal Boolean HasYearMonthAdjustment {
2276 return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
2280 // This is a callback that the parser can make back into the DTFI to let it fiddle with special
2281 // cases associated with that culture or calendar. Currently this only has special cases for
2282 // the Hebrew calendar, but this could be extended to other cultures.
2284 // The return value is whether the year and month are actually valid for this calendar.
2285 internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) {
2286 if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
2288 // Special rules to fix up the Hebrew year/month
2290 // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
2291 // E.g. if the year is 5763, we only format as 763.
2296 // Because we need to calculate leap year, we should fall out now for an invalid year.
2297 if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) {
2301 // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
2302 // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
2303 // shuffle the other months down.
2304 if (parsedMonthName) {
2305 if (!Calendar.IsLeapYear(year)) {
2309 else if (month == 7) {
2319 // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
2322 TokenHashValue[] m_dtfiTokenHash;
2324 private const int TOKEN_HASH_SIZE = 199;
2325 private const int SECOND_PRIME = 197;
2326 private const String dateSeparatorOrTimeZoneOffset = "-";
2327 private const String invariantDateSeparator = "/";
2328 private const String invariantTimeSeparator = ":";
2331 // Common Ignorable Symbols
2333 internal const String IgnorablePeriod = ".";
2334 internal const String IgnorableComma = ",";
2337 // Year/Month/Day suffixes
2339 internal const String CJKYearSuff = "\u5e74";
2340 internal const String CJKMonthSuff = "\u6708";
2341 internal const String CJKDaySuff = "\u65e5";
2343 internal const String KoreanYearSuff = "\ub144";
2344 internal const String KoreanMonthSuff = "\uc6d4";
2345 internal const String KoreanDaySuff = "\uc77c";
2347 internal const String KoreanHourSuff = "\uc2dc";
2348 internal const String KoreanMinuteSuff = "\ubd84";
2349 internal const String KoreanSecondSuff = "\ucd08";
2351 internal const String CJKHourSuff = "\u6642";
2352 internal const String ChineseHourSuff = "\u65f6";
2354 internal const String CJKMinuteSuff = "\u5206";
2355 internal const String CJKSecondSuff = "\u79d2";
2357 internal const String LocalTimeMark = "T";
2359 internal const String KoreanLangName = "ko";
2360 internal const String JapaneseLangName = "ja";
2361 internal const String EnglishLangName = "en";
2363 private static volatile DateTimeFormatInfo s_jajpDTFI;
2364 private static volatile DateTimeFormatInfo s_zhtwDTFI;
2367 // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse
2368 // date string with Japanese era name correctly even when the supplied DTFI
2369 // does not use Japanese calendar.
2370 // The created instance is stored in global s_jajpDTFI.
2372 internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() {
2373 DateTimeFormatInfo temp = s_jajpDTFI;
2375 temp = new CultureInfo("ja-JP", false).DateTimeFormat;
2376 temp.Calendar = JapaneseCalendar.GetDefaultInstance();
2381 internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() {
2382 DateTimeFormatInfo temp = s_zhtwDTFI;
2384 temp = new CultureInfo("zh-TW", false).DateTimeFormat;
2385 temp.Calendar = TaiwanCalendar.GetDefaultInstance();
2392 // DTFI properties should call this when the setter are called.
2393 private void ClearTokenHashTable()
2395 m_dtfiTokenHash = null;
2396 formatFlags = DateTimeFormatFlags.NotInitialized;
2399 [System.Security.SecurityCritical] // auto-generated
2400 internal TokenHashValue[] CreateTokenHashTable() {
2401 TokenHashValue[] temp = m_dtfiTokenHash;
2403 temp = new TokenHashValue[TOKEN_HASH_SIZE];
2405 bool koreanLanguage = LanguageName.Equals(KoreanLangName);
2407 string sep = this.TimeSeparator.Trim();
2408 if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
2409 if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
2411 if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) {
2413 // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
2414 // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
2415 // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
2416 // instead of SEP_HourSuff.
2418 InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
2421 InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2422 InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2424 if (LanguageName.Equals("sq")) {
2425 // Albanian allows time formats like "12:00.PD"
2426 InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2427 InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2431 InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
2432 InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
2433 InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
2434 InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
2435 InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
2436 InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
2438 InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
2439 InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
2440 InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2441 InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
2443 if (koreanLanguage) {
2445 InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
2446 InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2447 InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
2450 if ( LanguageName.Equals("ky")) {
2451 // For some cultures, the date separator works more like a comma, being allowed before or after any date part
2452 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
2455 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
2458 String[] dateWords = null;
2459 DateTimeFormatInfoScanner scanner = null;
2461 // We need to rescan the date words since we're always synthetic
2462 scanner = new DateTimeFormatInfoScanner();
2463 // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
2464 // The only reason they're being assigned to m_dateWords is for Whidbey Deserialization
2465 m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this);
2466 // Ensure the formatflags is initialized.
2467 DateTimeFormatFlags flag = FormatFlags;
2469 // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
2470 // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
2471 // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
2472 bool useDateSepAsIgnorableSymbol = false;
2474 String monthPostfix = null;
2475 if (dateWords != null)
2477 // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
2478 // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
2479 for (int i = 0; i < dateWords.Length; i++)
2481 switch (dateWords[i][0])
2483 // This is a month postfix
2484 case DateTimeFormatInfoScanner.MonthPostfixChar:
2485 // Get the real month postfix.
2486 monthPostfix = dateWords[i].Substring(1);
2487 // Add the month name + postfix into the token.
2488 AddMonthNames(temp, monthPostfix);
2490 case DateTimeFormatInfoScanner.IgnorableSymbolChar:
2491 String symbol = dateWords[i].Substring(1);
2492 InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
2493 if (this.DateSeparator.Trim(null).Equals(symbol))
2495 // The date separator is the same as the ingorable symbol.
2496 useDateSepAsIgnorableSymbol = true;
2500 InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
2501 if (LanguageName.Equals("eu")) {
2502 // Basque has date words with leading dots
2503 InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
2510 if (!useDateSepAsIgnorableSymbol)
2512 // Use the normal date separator.
2513 InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
2515 // Add the regular month names.
2516 AddMonthNames(temp, null);
2518 // Add the abbreviated month names.
2519 for (int i = 1; i <= 13; i++) {
2520 InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
2524 if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) {
2525 for (int i = 1; i <= 13; i++) {
2527 str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
2528 InsertHash(temp, str, TokenType.MonthToken, i);
2532 if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
2533 for (int i = 1; i <= 13; i++) {
2535 str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
2536 InsertHash(temp, str, TokenType.MonthToken, i);
2540 for (int i = 0; i < 7; i++) {
2541 //String str = GetDayOfWeekNames()[i];
2542 // We have to call public methods here to work with inherited DTFI.
2543 String str = GetDayName((DayOfWeek)i);
2544 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2546 str = GetAbbreviatedDayName((DayOfWeek)i);
2547 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2551 int[] eras = calendar.Eras;
2552 for (int i = 1; i <= eras.Length; i++) {
2553 InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
2554 InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
2557 if (LanguageName.Equals(JapaneseLangName)) {
2558 // Japanese allows day of week forms like: "(Tue)"
2559 for (int i = 0; i < 7; i++) {
2560 String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
2561 InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
2563 if (this.Calendar.GetType() != typeof(JapaneseCalendar)) {
2564 // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar,
2565 // we will check Japanese Era name as well when the calendar is Gregorian.
2566 DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
2567 for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) {
2568 InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
2569 InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
2570 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2571 InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i-1], TokenType.JapaneseEraToken, i);
2575 else if (CultureName.Equals("zh-TW")) {
2576 DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
2577 for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) {
2578 if (twDtfi.GetEraName(i).Length > 0) {
2579 InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
2584 InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2585 InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2587 // Add invariant month names and day names.
2588 for (int i = 1; i <= 12; i++) {
2590 // We have to call public methods here to work with inherited DTFI.
2591 // Insert the month name first, so that they are at the front of abbrevaited
2593 str = InvariantInfo.GetMonthName(i);
2594 InsertHash(temp, str, TokenType.MonthToken, i);
2595 str = InvariantInfo.GetAbbreviatedMonthName(i);
2596 InsertHash(temp, str, TokenType.MonthToken, i);
2599 for (int i = 0; i < 7; i++) {
2600 // We have to call public methods here to work with inherited DTFI.
2601 String str = InvariantInfo.GetDayName((DayOfWeek)i);
2602 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2604 str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
2605 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2609 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
2610 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2611 InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
2614 InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
2615 InsertHash(temp, DateTimeParse.GMTName, TokenType.TimeZoneToken, 0);
2616 InsertHash(temp, DateTimeParse.ZuluName, TokenType.TimeZoneToken, 0);
2618 InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
2619 InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
2621 m_dtfiTokenHash = temp;
2626 private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
2628 for (int i = 1; i <= 13; i++) {
2630 //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
2631 // We have to call public methods here to work with inherited DTFI.
2632 // Insert the month name first, so that they are at the front of abbrevaited
2634 str = GetMonthName(i);
2635 if (str.Length > 0) {
2636 if (monthPostfix != null) {
2637 // Insert the month name with the postfix first, so it can be matched first.
2638 InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
2641 InsertHash(temp, str, TokenType.MonthToken, i);
2644 str = GetAbbreviatedMonthName(i);
2645 InsertHash(temp, str, TokenType.MonthToken, i);
2650 ////////////////////////////////////////////////////////////////////////
2653 // Try to parse the current word to see if it is a Hebrew number.
2654 // Tokens will be updated accordingly.
2655 // This is called by the Lexer of DateTime.Parse().
2657 // Unlike most of the functions in this class, the return value indicates
2658 // whether or not it started to parse. The badFormat parameter indicates
2659 // if parsing began, but the format was bad.
2661 ////////////////////////////////////////////////////////////////////////
2663 private static bool TryParseHebrewNumber(
2665 out Boolean badFormat,
2672 if (!HebrewNumber.IsDigit(str.Value[i])) {
2673 // If the current character is not a Hebrew digit, just return false.
2674 // There is no chance that we can parse a valid Hebrew number from here.
2677 // The current character is a Hebrew digit. Try to parse this word as a Hebrew number.
2678 HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
2679 HebrewNumberParsingState state;
2682 state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
2684 case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number.
2685 case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character.
2686 // Break out so that we don't continue to try parse this as a Hebrew number.
2689 } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
2691 // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
2692 Contract.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
2693 "Invalid returned state from HebrewNumber.ParseByChar()");
2695 if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) {
2696 // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
2700 // We have found a valid Hebrew number. Update the index.
2701 str.Advance(i - str.Index);
2703 // Get the final Hebrew number value from the HebrewNumberParsingContext.
2704 number = context.result;
2709 private static bool IsHebrewChar(char ch) {
2710 return (ch >= '\x0590' && ch <= '\x05ff');
2713 [System.Security.SecurityCritical] // auto-generated
2714 internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, ref __DTString str) {
2715 tokenType = TokenType.UnknownToken;
2718 TokenHashValue value;
2719 Contract.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
2721 char ch = str.m_current;
2722 bool isLetter = Char.IsLetter(ch);
2724 ch = Char.ToLower(ch, this.Culture);
2725 if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) {
2727 if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) {
2729 tokenType = TokenType.UnknownToken;
2732 // This is a Hebrew number.
2733 // Do nothing here. TryParseHebrewNumber() will update token accordingly.
2734 tokenType = TokenType.HebrewNumber;
2741 int hashcode = ch % TOKEN_HASH_SIZE;
2742 int hashProbe = 1 + ch % SECOND_PRIME;
2743 int remaining = str.len - str.Index;
2746 TokenHashValue[] hashTable = m_dtfiTokenHash;
2747 if (hashTable == null) {
2748 hashTable = CreateTokenHashTable();
2751 value = hashTable[hashcode];
2752 if (value == null) {
2756 // Check this value has the right category (regular token or separator token) that we are looking for.
2757 if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) {
2758 if (String.Compare(str.Value, str.Index, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase)==0) {
2760 // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately.
2762 if ((nextCharIndex = str.Index + value.tokenString.Length) < str.len) {
2763 // Check word boundary. The next character should NOT be a letter.
2764 char nextCh = str.Value[nextCharIndex];
2765 if (Char.IsLetter(nextCh)) {
2770 tokenType = value.tokenType & TokenMask;
2771 tokenValue = value.tokenValue;
2772 str.Advance(value.tokenString.Length);
2774 } else if (value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) {
2775 // For month token, we will match the month names which have spaces.
2776 int matchStrLen = 0;
2777 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2778 tokenType = value.tokenType & TokenMask;
2779 tokenValue = value.tokenValue;
2780 str.Advance(matchStrLen);
2783 } else if (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames) {
2784 // For month token, we will match the month names which have spaces.
2785 int matchStrLen = 0;
2786 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2787 tokenType = value.tokenType & TokenMask;
2788 tokenValue = value.tokenValue;
2789 str.Advance(matchStrLen);
2795 hashcode += hashProbe;
2796 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2797 }while (i < TOKEN_HASH_SIZE);
2802 void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) {
2803 // Remember the current slot.
2804 TokenHashValue previousNode = hashTable[hashcode];
2806 //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert);
2807 // Insert the new node into the current slot.
2808 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);;
2810 while (++pos < TOKEN_HASH_SIZE) {
2811 hashcode += hashProbe;
2812 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2813 // Remember this slot
2814 TokenHashValue temp = hashTable[hashcode];
2816 if (temp != null && Char.ToLower(temp.tokenString[0], this.Culture) != ch) {
2819 // Put the previous slot into this slot.
2820 hashTable[hashcode] = previousNode;
2821 //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
2826 previousNode = temp;
2828 Contract.Assert(false, "The hashtable is full. This should not happen.");
2831 void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) {
2832 // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
2833 if (str == null || str.Length == 0) {
2836 TokenHashValue value;
2838 // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
2839 // DateTime.Parse().
2840 if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) {
2841 str = str.Trim(null); // Trim white space characters.
2842 // Could have space for separators
2843 if (str.Length == 0)
2846 char ch = Char.ToLower(str[0], this.Culture);
2847 int hashcode = ch % TOKEN_HASH_SIZE;
2848 int hashProbe = 1 + ch % SECOND_PRIME;
2850 value = hashTable[hashcode];
2851 if (value == null) {
2852 //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
2853 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
2856 // Collision happens. Find another slot.
2857 if (str.Length >= value.tokenString.Length) {
2858 // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
2859 // the shorter ones.
2860 if (String.Compare(str, 0, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase) == 0) {
2861 if (str.Length > value.tokenString.Length) {
2862 // The str to be inserted has the same prefix as the current token, and str is longer.
2863 // Insert str into this node, and shift every node behind it.
2864 InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
2867 // Same token. If they have different types (regular token vs separator token). Add them.
2868 // If we have the same regular token or separator token in the hash already, do NOT update the hash.
2869 // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
2873 // 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.
2874 // 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.
2877 int nTokenType = (int)tokenType;
2878 int nCurrentTokenTypeInHash = (int)value.tokenType;
2880 // The idea behind this check is:
2881 // - if the app is targetting 4.5.1 or above OR the compat flag is set, use the correct behavior by default.
2882 // - if the app is targetting 4.5 or below AND the compat switch is set, use the correct behavior
2883 // - if the app is targetting 4.5 or below AND the compat switch is NOT set, use the incorrect behavior
2884 if (preferExistingTokens || BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
2886 if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
2887 ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
2889 value.tokenType |= tokenType;
2890 if (tokenValue != 0)
2892 value.tokenValue = tokenValue;
2898 // The following logic is incorrect and causes updates to happen depending on the bitwise relationship between the existing token type and the
2899 // the stored token type. It was this way in .NET 4 RTM. The behavior above is correct and will be adopted going forward.
2901 if ((((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.RegularTokenMask) == nTokenType) ||
2902 (((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.SeparatorTokenMask) == nTokenType))
2904 value.tokenType |= tokenType;
2905 if (tokenValue != 0)
2907 value.tokenValue = tokenValue;
2911 // The token to be inserted is already in the table. Skip it.
2916 //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
2918 hashcode += hashProbe;
2919 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2920 } while (i < TOKEN_HASH_SIZE);
2921 Contract.Assert(false, "The hashtable is full. This should not happen.");
2923 } // class DateTimeFormatInfo
2925 internal class TokenHashValue {
2926 internal String tokenString;
2927 internal TokenType tokenType;
2928 internal int tokenValue;
2930 internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) {
2931 this.tokenString = tokenString;
2932 this.tokenType = tokenType;
2933 this.tokenValue = tokenValue;