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)
357 // Silverlight 2.0 never took a snapshot of the user's overridable properties
358 // This has a substantial performance impact so skip when CoreCLR
359 Contract.Requires(cultureData != null);
360 Contract.Assert(calendarID > 0, "[DateTimeFormatInfo.Populate] Expected Calendar.ID > 0");
362 if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
363 if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
365 if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
366 if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
367 if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
368 if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarID); }
370 this.allLongTimePatterns = this.m_cultureData.LongTimes;
371 Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
373 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
374 Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
376 this.allLongDatePatterns = cultureData.LongDates(calendarID);
377 Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
379 this.allShortDatePatterns = cultureData.ShortDates(calendarID);
380 Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
382 this.allYearMonthPatterns = cultureData.YearMonths(calendarID);
383 Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
387 #region Serialization
388 // The following fields are defined to keep the serialization compatibility with .NET V1.0/V1.1.
389 [OptionalField(VersionAdded = 1)]
390 private int CultureID;
391 [OptionalField(VersionAdded = 1)]
392 private bool m_useUserOverride;
394 [OptionalField(VersionAdded = 1)]
395 private bool bUseCalendarInfo;
396 [OptionalField(VersionAdded = 1)]
397 private int nDataItem;
398 [OptionalField(VersionAdded = 2)]
399 internal bool m_isDefaultCalendar; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
400 [OptionalField(VersionAdded = 2)]
401 private static volatile Hashtable s_calendarNativeNames; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
402 #endif // !FEATURE_CORECLR
404 // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string
405 // Now we always synthesize so its not helpful
406 [OptionalField(VersionAdded = 1)]
407 internal String[] m_dateWords = null; // calculated, no need to serialze (whidbey expects, arrowhead doesn't)
410 private void OnDeserialized(StreamingContext ctx)
412 if (this.m_name != null)
414 m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
416 if (this.m_cultureData == null)
417 throw new CultureNotFoundException(
418 "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
420 // Note: This is for Everett compatibility
424 m_cultureData = CultureData.GetCultureData(CultureID, m_useUserOverride);
426 if (calendar == null)
428 calendar = (Calendar) GregorianCalendar.GetDefaultInstance().Clone();
429 calendar.SetReadOnlyState(m_isReadOnly);
433 CultureInfo.CheckDomainSafetyObject(calendar, this);
435 InitializeOverridableProperties(m_cultureData, calendar.ID);
438 // turn off read only state till we finish initializing all fields and then store read only state after we are done.
440 bool isReadOnly = m_isReadOnly;
441 m_isReadOnly = false;
443 // If we deserialized defaults ala Whidbey, make sure they're still defaults
444 // Whidbey's arrays could get a bit mixed up.
445 if (longDatePattern != null) this.LongDatePattern = longDatePattern;
446 if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern;
447 if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern;
448 if (longTimePattern != null) this.LongTimePattern = longTimePattern;
449 if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern;
451 m_isReadOnly = isReadOnly;
455 private void OnSerializing(StreamingContext ctx)
458 CultureID = this.m_cultureData.ILANGUAGE; // Used for serialization compatibility with Whidbey which didn't always serialize the name
460 m_useUserOverride = this.m_cultureData.UseUserOverride;
462 // make sure the m_name is initialized.
463 m_name = this.CultureName;
466 if (s_calendarNativeNames == null)
467 s_calendarNativeNames = new Hashtable();
468 #endif // FEATURE_CORECLR
470 // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey
471 // because Whidbey try to initialize some of these fields using calendar data which could be null values
472 // and then we get exceptions. So we call the accessors to force the caches to get loaded.
474 o = this.LongTimePattern;
475 o = this.LongDatePattern;
476 o = this.ShortTimePattern;
477 o = this.ShortDatePattern;
478 o = this.YearMonthPattern;
479 o = this.AllLongTimePatterns;
480 o = this.AllLongDatePatterns;
481 o = this.AllShortTimePatterns;
482 o = this.AllShortDatePatterns;
483 o = this.AllYearMonthPatterns;
485 #endregion Serialization
487 // Returns a default DateTimeFormatInfo that will be universally
488 // supported and constant irrespective of the current culture.
489 // Used by FromString methods.
492 public static DateTimeFormatInfo InvariantInfo {
494 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
495 if (invariantInfo == null)
497 DateTimeFormatInfo info = new DateTimeFormatInfo();
498 info.Calendar.SetReadOnlyState(true);
499 info.m_isReadOnly = true;
500 invariantInfo = info;
502 return (invariantInfo);
506 // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
509 public static DateTimeFormatInfo CurrentInfo {
511 Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
512 System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
513 if (!culture.m_isInherited) {
514 DateTimeFormatInfo info = culture.dateTimeInfo;
519 return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
524 public static DateTimeFormatInfo GetInstance(IFormatProvider provider) {
525 // Fast case for a regular CultureInfo
526 DateTimeFormatInfo info;
527 CultureInfo cultureProvider = provider as CultureInfo;
528 if (cultureProvider != null && !cultureProvider.m_isInherited)
530 return cultureProvider.DateTimeFormat;
532 // Fast case for a DTFI;
533 info = provider as DateTimeFormatInfo;
537 // Wasn't cultureInfo or DTFI, do it the slower way
538 if (provider != null) {
539 info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo;
544 // Couldn't get anything, just use currentInfo as fallback
549 public Object GetFormat(Type formatType)
551 return (formatType == typeof(DateTimeFormatInfo)? this: null);
555 public Object Clone()
557 DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
558 // We can use the data member calendar in the setter, instead of the property Calendar,
559 // since the cloned copy should have the same state as the original copy.
560 n.calendar = (Calendar) this.Calendar.Clone();
561 n.m_isReadOnly = false;
566 public String AMDesignator
569 [System.Security.SecuritySafeCritical] // auto-generated
574 if (this.amDesignator == null)
576 this.amDesignator = this.m_cultureData.SAM1159;
579 Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
580 return (this.amDesignator);
586 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
589 throw new ArgumentNullException("value",
590 Environment.GetResourceString("ArgumentNull_String"));
592 Contract.EndContractBlock();
593 ClearTokenHashTable();
594 amDesignator = value;
599 public Calendar Calendar {
601 Contract.Ensures(Contract.Result<Calendar>() != null);
603 Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
604 return (this.calendar);
609 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
611 throw new ArgumentNullException("value",
612 Environment.GetResourceString("ArgumentNull_Obj"));
614 Contract.EndContractBlock();
615 if (value == calendar) {
620 // Because the culture is agile object which can be attached to a thread and then thread can travel
621 // to another app domain then we prevent attaching any customized object to culture that we cannot contol.
623 CultureInfo.CheckDomainSafetyObject(value, this);
625 for (int i = 0; i < this.OptionalCalendars.Length; i++)
627 if (this.OptionalCalendars[i] == value.ID)
629 // We can use this one, so do so.
631 // Clean related properties if we already had a calendar set
632 if (calendar != null)
634 // clean related properties which are affected by the calendar setting,
635 // so that they will be refreshed when they are accessed next time.
638 // These properites are in the order as appearing in calendar.xml.
640 m_abbrevEraNames = null;
641 m_abbrevEnglishEraNames = null;
643 monthDayPattern = null;
646 abbreviatedDayNames = null;
647 m_superShortDayNames = null;
649 abbreviatedMonthNames = null;
650 genitiveMonthNames = null;
651 m_genitiveAbbreviatedMonthNames = null;
652 leapYearMonthNames = null;
653 formatFlags = DateTimeFormatFlags.NotInitialized;
655 allShortDatePatterns = null;
656 allLongDatePatterns = null;
657 allYearMonthPatterns = null;
658 dateTimeOffsetPattern = null;
660 // The defaults need reset as well:
661 longDatePattern = null;
662 shortDatePattern = null;
663 yearMonthPattern = null;
665 // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
666 fullDateTimePattern = null; // Long date + long time
667 generalShortTimePattern = null; // short date + short time
668 generalLongTimePattern = null; // short date + long time
670 // Derived item that changes
671 dateSeparator = null;
673 // We don't need to do these because they are not changed by changing calendar
681 // We don't need to clear these because they're only used for whidbey compat serialization
682 // the only values we use are the all...Patterns[0]
687 // remember to reload tokens
688 ClearTokenHashTable();
691 // Remember the new calendar
693 InitializeOverridableProperties(m_cultureData, calendar.ID);
695 // We succeeded, return
700 // The assigned calendar is not a valid calendar for this culture, throw
701 throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("Argument_InvalidCalendar"));
705 private int[] OptionalCalendars {
707 if (this.optionalCalendars == null) {
708 this.optionalCalendars = this.m_cultureData.CalendarIds;
710 return (this.optionalCalendars);
714 /*=================================GetEra==========================
715 **Action: Get the era value by parsing the name of the era.
716 **Returns: The era value for the specified era name.
717 ** -1 if the name of the era is not valid or not supported.
718 **Arguments: eraName the name of the era.
720 ============================================================================*/
723 public int GetEra(String eraName) {
724 if (eraName == null) {
725 throw new ArgumentNullException("eraName",
726 Environment.GetResourceString("ArgumentNull_String"));
728 Contract.EndContractBlock();
730 // For Geo-Political reasons, the Era Name and Abbreviated Era Name
731 // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
732 // would be matched below) but we don't want the empty string to give
734 // confer 85900 DTFI.GetEra("") should fail on all cultures
735 if (eraName.Length == 0) {
739 // The following is based on the assumption that the era value is starting from 1, and has a
741 // If that ever changes, the code has to be changed.
743 // The calls to String.Compare should use the current culture for the string comparisons, but the
744 // invariant culture when comparing against the english names.
745 for (int i = 0; i < EraNames.Length; i++) {
746 // Compare the era name in a case-insensitive way for the appropriate culture.
747 if (m_eraNames[i].Length > 0) {
748 if (String.Compare(eraName, m_eraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
753 for (int i = 0; i < AbbreviatedEraNames.Length; i++) {
754 // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
755 if (String.Compare(eraName, m_abbrevEraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
759 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
760 // this comparison should use the InvariantCulture. The English name could have linguistically
761 // interesting characters.
762 if (String.Compare(eraName, m_abbrevEnglishEraNames[i], StringComparison.InvariantCultureIgnoreCase)==0) {
769 internal String[] EraNames
773 if (this.m_eraNames == null)
775 this.m_eraNames = this.m_cultureData.EraNames(Calendar.ID);;
777 return (this.m_eraNames);
781 /*=================================GetEraName==========================
782 **Action: Get the name of the era for the specified era value.
783 **Returns: The name of the specified era.
785 ** era the era value.
787 ** ArguementException if the era valie is invalid.
788 ============================================================================*/
790 // Era names are 1 indexed
791 public String GetEraName(int era) {
792 if (era == Calendar.CurrentEra) {
793 era = Calendar.CurrentEraValue;
796 // The following is based on the assumption that the era value is starting from 1, and has a
798 // If that ever changes, the code has to be changed.
799 if ((--era) < EraNames.Length && (era >= 0)) {
800 return (m_eraNames[era]);
802 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
805 internal String[] AbbreviatedEraNames
809 if (this.m_abbrevEraNames == null)
811 this.m_abbrevEraNames = this.m_cultureData.AbbrevEraNames(Calendar.ID);
813 return (this.m_abbrevEraNames);
817 // Era names are 1 indexed
818 public String GetAbbreviatedEraName(int era) {
819 if (AbbreviatedEraNames.Length == 0) {
820 // If abbreviation era name is not used in this culture,
821 // return the full era name.
822 return (GetEraName(era));
824 if (era == Calendar.CurrentEra) {
825 era = Calendar.CurrentEraValue;
827 if ((--era) < m_abbrevEraNames.Length && (era >= 0)) {
828 return (m_abbrevEraNames[era]);
830 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
833 internal String[] AbbreviatedEnglishEraNames
837 if (this.m_abbrevEnglishEraNames == null)
839 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
840 this.m_abbrevEnglishEraNames = this.m_cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
842 return (this.m_abbrevEnglishEraNames);
847 // Note that cultureData derives this from the short date format (unless someone's set this previously)
848 // Note that this property is quite undesirable.
849 public String DateSeparator
854 if (this.dateSeparator == null)
856 this.dateSeparator = this.m_cultureData.DateSeparator(Calendar.ID);
859 Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
860 return (this.dateSeparator);
864 set { throw new NotImplementedException(); }
870 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
872 throw new ArgumentNullException("value",
873 Environment.GetResourceString("ArgumentNull_String"));
875 Contract.EndContractBlock();
876 ClearTokenHashTable();
877 this.dateSeparator = value;
883 public DayOfWeek FirstDayOfWeek
888 if (this.firstDayOfWeek == -1)
890 this.firstDayOfWeek = this.m_cultureData.IFIRSTDAYOFWEEK;
893 Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
895 return ((DayOfWeek)this.firstDayOfWeek);
900 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
901 if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) {
902 firstDayOfWeek = (int)value;
904 throw new ArgumentOutOfRangeException(
905 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
906 DayOfWeek.Sunday, DayOfWeek.Saturday));
912 public CalendarWeekRule CalendarWeekRule
917 if (this.calendarWeekRule == -1)
919 this.calendarWeekRule = this.m_cultureData.IFIRSTWEEKOFYEAR;
922 Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
923 return ((CalendarWeekRule)this.calendarWeekRule);
928 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
929 if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) {
930 calendarWeekRule = (int)value;
932 throw new ArgumentOutOfRangeException(
933 "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
934 CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
941 public String FullDateTimePattern
945 if (fullDateTimePattern == null)
947 fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
949 return (fullDateTimePattern);
954 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
956 throw new ArgumentNullException("value",
957 Environment.GetResourceString("ArgumentNull_String"));
959 Contract.EndContractBlock();
960 fullDateTimePattern = value;
965 // For our "patterns" arrays we have 2 variables, a string and a string[]
967 // The string[] contains the list of patterns, EXCEPT the default may not be included.
968 // The string contains the default pattern.
969 // When we initially construct our string[], we set the string to string[0]
970 public String LongDatePattern
974 // Initialize our long date pattern from the 1st array value if not set
975 if (this.longDatePattern == null)
977 // Initialize our data
978 this.longDatePattern = this.UnclonedLongDatePatterns[0];
981 return this.longDatePattern;
986 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
988 throw new ArgumentNullException("value",
989 Environment.GetResourceString("ArgumentNull_String"));
991 Contract.EndContractBlock();
993 // Remember the new string
994 this.longDatePattern = value;
996 // Clear the token hash table
997 ClearTokenHashTable();
999 // Clean up cached values that will be affected by this property.
1000 this.fullDateTimePattern = null;
1004 // For our "patterns" arrays we have 2 variables, a string and a string[]
1006 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1007 // The string contains the default pattern.
1008 // When we initially construct our string[], we set the string to string[0]
1009 public String LongTimePattern
1013 // Initialize our long time pattern from the 1st array value if not set
1014 if (this.longTimePattern == null)
1016 // Initialize our data
1017 this.longTimePattern = this.UnclonedLongTimePatterns[0];
1020 return this.longTimePattern;
1025 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1026 if (value == null) {
1027 throw new ArgumentNullException("value",
1028 Environment.GetResourceString("ArgumentNull_String"));
1030 Contract.EndContractBlock();
1032 // Remember the new string
1033 this.longTimePattern = value;
1035 // Clear the token hash table
1036 ClearTokenHashTable();
1038 // Clean up cached values that will be affected by this property.
1039 this.fullDateTimePattern = null; // Full date = long date + long Time
1040 this.generalLongTimePattern = null; // General long date = short date + long Time
1041 this.dateTimeOffsetPattern = null;
1046 // Note: just to be confusing there's only 1 month day pattern, not a whole list
1047 public String MonthDayPattern
1051 if (this.monthDayPattern == null)
1053 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
1054 this.monthDayPattern = this.m_cultureData.MonthDay(Calendar.ID);
1056 Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
1057 return (this.monthDayPattern);
1062 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1063 if (value == null) {
1064 throw new ArgumentNullException("value",
1065 Environment.GetResourceString("ArgumentNull_String"));
1067 Contract.EndContractBlock();
1069 this.monthDayPattern = value;
1074 public String PMDesignator
1077 [System.Security.SecuritySafeCritical] // auto-generated
1082 if (this.pmDesignator == null)
1084 this.pmDesignator = this.m_cultureData.SPM2359;
1087 Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
1088 return (this.pmDesignator);
1093 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1094 if (value == null) {
1095 throw new ArgumentNullException("value",
1096 Environment.GetResourceString("ArgumentNull_String"));
1098 Contract.EndContractBlock();
1099 ClearTokenHashTable();
1101 pmDesignator = value;
1107 public String RFC1123Pattern
1111 return (rfc1123Pattern);
1115 // For our "patterns" arrays we have 2 variables, a string and a string[]
1117 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1118 // The string contains the default pattern.
1119 // When we initially construct our string[], we set the string to string[0]
1120 public String ShortDatePattern
1124 // Initialize our short date pattern from the 1st array value if not set
1125 if (this.shortDatePattern == null)
1127 // Initialize our data
1128 this.shortDatePattern = this.UnclonedShortDatePatterns[0];
1131 return this.shortDatePattern;
1137 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1139 throw new ArgumentNullException("value",
1140 Environment.GetResourceString("ArgumentNull_String"));
1141 Contract.EndContractBlock();
1143 // Remember the new string
1144 this.shortDatePattern = value;
1146 // Clear the token hash table, note that even short dates could require this
1147 ClearTokenHashTable();
1149 // Clean up cached values that will be affected by this property.
1150 generalLongTimePattern = null; // General long time = short date + long time
1151 generalShortTimePattern = null; // General short time = short date + short Time
1152 dateTimeOffsetPattern = null;
1157 // For our "patterns" arrays we have 2 variables, a string and a string[]
1159 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1160 // The string contains the default pattern.
1161 // When we initially construct our string[], we set the string to string[0]
1162 public String ShortTimePattern
1166 // Initialize our short time pattern from the 1st array value if not set
1167 if (this.shortTimePattern == null)
1169 // Initialize our data
1170 this.shortTimePattern = this.UnclonedShortTimePatterns[0];
1172 return this.shortTimePattern;
1177 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1178 if (value == null) {
1179 throw new ArgumentNullException("value",
1180 Environment.GetResourceString("ArgumentNull_String"));
1182 Contract.EndContractBlock();
1184 // Remember the new string
1185 this.shortTimePattern= value;
1187 // Clear the token hash table, note that even short times could require this
1188 ClearTokenHashTable();
1190 // Clean up cached values that will be affected by this property.
1191 generalShortTimePattern = null; // General short date = short date + short time.
1196 public String SortableDateTimePattern {
1198 return (sortableDateTimePattern);
1202 /*=================================GeneralShortTimePattern=====================
1203 **Property: Return the pattern for 'g' general format: shortDate + short time
1204 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1205 ** We put this internal property here so that we can avoid doing the
1206 ** concatation every time somebody asks for the general format.
1207 ==============================================================================*/
1209 internal String GeneralShortTimePattern {
1211 if (generalShortTimePattern == null) {
1212 generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
1214 return (generalShortTimePattern);
1218 /*=================================GeneralLongTimePattern=====================
1219 **Property: Return the pattern for 'g' general format: shortDate + Long time
1220 **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
1221 ** We put this internal property here so that we can avoid doing the
1222 ** concatation every time somebody asks for the general format.
1223 ==============================================================================*/
1225 internal String GeneralLongTimePattern {
1227 if (generalLongTimePattern == null) {
1228 generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
1230 return (generalLongTimePattern);
1234 /*=================================DateTimeOffsetPattern==========================
1235 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1236 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1237 ** We put this internal property here so that we can avoid doing the
1238 ** concatation every time somebody uses this form
1239 ==============================================================================*/
1241 /*=================================DateTimeOffsetPattern==========================
1242 **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
1243 **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
1244 ** We put this internal property here so that we can avoid doing the
1245 ** concatation every time somebody uses this form
1246 ==============================================================================*/
1248 internal String DateTimeOffsetPattern {
1250 if (dateTimeOffsetPattern == null) {
1252 string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
1254 /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
1256 bool foundZ = false;
1257 bool inQuote = false;
1259 for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) {
1260 switch (LongTimePattern[i]) {
1262 /* if we aren't in a quote, we've found a z */
1264 /* we'll fall out of the loop now because the test includes !foundZ */
1268 if (inQuote && (quote == LongTimePattern[i])) {
1269 /* we were in a quote and found a matching exit quote, so we are outside a quote now */
1271 } else if (!inQuote) {
1272 quote = LongTimePattern[i];
1275 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
1280 i++; /* skip next character that is escaped by this backslash */
1288 dateTimePattern = dateTimePattern + " zzz";
1291 dateTimeOffsetPattern = dateTimePattern;
1293 return (dateTimeOffsetPattern);
1297 // Note that cultureData derives this from the long time format (unless someone's set this previously)
1298 // Note that this property is quite undesirable.
1299 public String TimeSeparator
1304 if (timeSeparator == null)
1306 timeSeparator = this.m_cultureData.TimeSeparator;
1309 Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
1310 return (timeSeparator);
1314 set { throw new NotImplementedException(); }
1317 #if !FEATURE_CORECLR
1320 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1321 if (value == null) {
1322 throw new ArgumentNullException("value",
1323 Environment.GetResourceString("ArgumentNull_String"));
1325 Contract.EndContractBlock();
1326 ClearTokenHashTable();
1328 timeSeparator = value;
1334 public String UniversalSortableDateTimePattern
1338 return (universalSortableDateTimePattern);
1342 // For our "patterns" arrays we have 2 variables, a string and a string[]
1344 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1345 // The string contains the default pattern.
1346 // When we initially construct our string[], we set the string to string[0]
1347 public String YearMonthPattern
1351 // Initialize our year/month pattern from the 1st array value if not set
1352 if (this.yearMonthPattern == null)
1354 // Initialize our data
1355 this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
1357 return this.yearMonthPattern;
1362 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1363 if (value == null) {
1364 throw new ArgumentNullException("value",
1365 Environment.GetResourceString("ArgumentNull_String"));
1367 Contract.EndContractBlock();
1369 // Remember the new string
1370 this.yearMonthPattern = value;
1372 // Clear the token hash table, note that even short times could require this
1373 ClearTokenHashTable();
1378 // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
1380 static private void CheckNullValue(String[] values, int length) {
1381 Contract.Requires(values != null, "value != null");
1382 Contract.Requires(values.Length >= length);
1383 for (int i = 0; i < length; i++) {
1384 if (values[i] == null) {
1385 throw new ArgumentNullException("value",
1386 Environment.GetResourceString("ArgumentNull_ArrayValue"));
1392 public String[] AbbreviatedDayNames
1396 return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
1401 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1402 if (value == null) {
1403 throw new ArgumentNullException("value",
1404 Environment.GetResourceString("ArgumentNull_Array"));
1406 if (value.Length != 7) {
1407 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1409 Contract.EndContractBlock();
1410 CheckNullValue(value, value.Length);
1411 ClearTokenHashTable();
1413 abbreviatedDayNames = value;
1418 // Returns the string array of the one-letter day of week names.
1419 [System.Runtime.InteropServices.ComVisible(false)]
1420 public String[] ShortestDayNames
1424 return ((String[])internalGetSuperShortDayNames().Clone());
1429 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1430 if (value == null) {
1431 throw new ArgumentNullException("value",
1432 Environment.GetResourceString("ArgumentNull_Array"));
1434 if (value.Length != 7)
1436 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1438 Contract.EndContractBlock();
1439 CheckNullValue(value, value.Length);
1440 this.m_superShortDayNames = value;
1445 public String[] DayNames
1449 return ((String[])internalGetDayOfWeekNames().Clone());
1454 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1455 if (value == null) {
1456 throw new ArgumentNullException("value",
1457 Environment.GetResourceString("ArgumentNull_Array"));
1459 if (value.Length != 7)
1461 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
1463 Contract.EndContractBlock();
1464 CheckNullValue(value, value.Length);
1465 ClearTokenHashTable();
1472 public String[] AbbreviatedMonthNames {
1474 return ((String[])internalGetAbbreviatedMonthNames().Clone());
1479 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1480 if (value == null) {
1481 throw new ArgumentNullException("value",
1482 Environment.GetResourceString("ArgumentNull_Array"));
1484 if (value.Length != 13)
1486 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1488 Contract.EndContractBlock();
1489 CheckNullValue(value, value.Length - 1);
1490 ClearTokenHashTable();
1491 abbreviatedMonthNames = value;
1496 public String[] MonthNames
1500 return ((String[])internalGetMonthNames().Clone());
1505 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
1506 if (value == null) {
1507 throw new ArgumentNullException("value",
1508 Environment.GetResourceString("ArgumentNull_Array"));
1510 if (value.Length != 13)
1512 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
1514 Contract.EndContractBlock();
1515 CheckNullValue(value, value.Length - 1);
1517 ClearTokenHashTable();
1521 // Whitespaces that we allow in the month names.
1522 // U+00a0 is non-breaking space.
1523 static char[] MonthSpaces = {' ', '\u00a0'};
1525 internal bool HasSpacesInMonthNames {
1527 return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
1531 internal bool HasSpacesInDayNames {
1533 return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
1539 // internalGetMonthName
1541 // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
1545 // style To indicate a form like regular/genitive/month name in a leap year.
1546 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1548 // ArgumentOutOfRangeException When month name is invalid.
1550 internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) {
1552 // Right now, style is mutual exclusive, but I make the style to be flag so that
1553 // maybe we can combine flag if there is such a need.
1555 String[] monthNamesArray = null;
1557 case MonthNameStyles.Genitive:
1558 monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
1560 case MonthNameStyles.LeapYear:
1561 monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
1564 monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames(): internalGetMonthNames());
1567 // The month range is from 1 ~ this.m_monthNames.Length
1568 // (actually is 13 right now for all cases)
1569 if ((month < 1) || (month > monthNamesArray.Length)) {
1570 throw new ArgumentOutOfRangeException(
1571 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1572 1, monthNamesArray.Length));
1574 return (monthNamesArray[month-1]);
1578 // internalGetGenitiveMonthNames
1580 // Action: Retrieve the array which contains the month names in genitive form.
1581 // If this culture does not use the gentive form, the normal month name is returned.
1583 // abbreviated When true, return abbreviated form. Otherwise, return a full form.
1585 private String[] internalGetGenitiveMonthNames(bool abbreviated) {
1587 if (this.m_genitiveAbbreviatedMonthNames == null)
1589 this.m_genitiveAbbreviatedMonthNames = this.m_cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
1590 Contract.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
1591 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
1593 return (this.m_genitiveAbbreviatedMonthNames);
1596 if (this.genitiveMonthNames == null)
1598 this.genitiveMonthNames = this.m_cultureData.GenitiveMonthNames(this.Calendar.ID);
1599 Contract.Assert(this.genitiveMonthNames.Length == 13,
1600 "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
1602 return (this.genitiveMonthNames);
1606 // internalGetLeapYearMonthNames
1608 // Actions: Retrieve the month names used in a leap year.
1609 // If this culture does not have different month names in a leap year, the normal month name is returned.
1610 // Agruments: None. (can use abbreviated later if needed)
1612 internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) {
1613 if (this.leapYearMonthNames == null)
1615 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
1616 this.leapYearMonthNames = this.m_cultureData.LeapYearMonthNames(Calendar.ID);
1617 Contract.Assert(this.leapYearMonthNames.Length == 13,
1618 "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
1620 return (leapYearMonthNames);
1624 public String GetAbbreviatedDayName(DayOfWeek dayofweek)
1627 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1628 throw new ArgumentOutOfRangeException(
1629 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1630 DayOfWeek.Sunday, DayOfWeek.Saturday));
1632 Contract.EndContractBlock();
1634 // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
1635 // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
1637 return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
1641 // Returns the super short day of week names for the specified day of week.
1642 [System.Runtime.InteropServices.ComVisible(false)]
1643 public String GetShortestDayName(DayOfWeek dayOfWeek)
1646 if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) {
1647 throw new ArgumentOutOfRangeException(
1648 "dayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1649 DayOfWeek.Sunday, DayOfWeek.Saturday));
1651 Contract.EndContractBlock();
1653 // Don't call the public property SuperShortDayNames here since a clone is needed in that
1654 // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
1656 return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
1659 // Get all possible combination of inputs
1660 static private String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
1662 Contract.Requires(patterns1 != null);
1663 Contract.Requires(patterns2 != null);
1666 String[] result = new String[patterns1.Length * patterns2.Length];
1668 // Counter of actual results
1670 for (int i = 0; i < patterns1.Length; i++)
1672 for (int j = 0; j < patterns2.Length; j++)
1674 // Can't combine if null or empty
1675 result[k++] = patterns1[i] + connectString + patterns2[j];
1679 // Return the combinations
1684 public String[] GetAllDateTimePatterns()
1686 List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
1688 for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
1690 String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
1691 for (int j = 0; j < strings.Length; j++)
1693 results.Add(strings[j]);
1696 return results.ToArray();
1700 public String[] GetAllDateTimePatterns(char format)
1702 Contract.Ensures(Contract.Result<String[]>() != null);
1703 String [] result = null;
1708 result = this.AllShortDatePatterns;
1711 result = this.AllLongDatePatterns;
1714 result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
1718 result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
1721 result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
1724 result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
1728 result = new String[] {MonthDayPattern};
1732 result = new String[] {DateTimeFormat.RoundtripFormat};
1736 result = new String[] {rfc1123Pattern};
1739 result = new String[] {sortableDateTimePattern};
1742 result = this.AllShortTimePatterns;
1745 result = this.AllLongTimePatterns;
1748 result = new String[] {UniversalSortableDateTimePattern};
1752 result = this.AllYearMonthPatterns;
1755 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
1761 public String GetDayName(DayOfWeek dayofweek)
1763 if ((int)dayofweek < 0 || (int)dayofweek > 6) {
1764 throw new ArgumentOutOfRangeException(
1765 "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
1766 DayOfWeek.Sunday, DayOfWeek.Saturday));
1768 Contract.EndContractBlock();
1770 // Use the internal one so that we don't clone the array unnecessarily
1771 return (internalGetDayOfWeekNames()[(int)dayofweek]);
1776 public String GetAbbreviatedMonthName(int month)
1778 if (month < 1 || month > 13) {
1779 throw new ArgumentOutOfRangeException(
1780 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1783 Contract.EndContractBlock();
1784 // Use the internal one so we don't clone the array unnecessarily
1785 return (internalGetAbbreviatedMonthNames()[month-1]);
1789 public String GetMonthName(int month)
1791 if (month < 1 || month > 13) {
1792 throw new ArgumentOutOfRangeException(
1793 "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
1796 Contract.EndContractBlock();
1797 // Use the internal one so we don't clone the array unnecessarily
1798 return (internalGetMonthNames()[month-1]);
1801 // For our "patterns" arrays we have 2 variables, a string and a string[]
1803 // The string[] contains the list of patterns, EXCEPT the default may not be included.
1804 // The string contains the default pattern.
1805 // When we initially construct our string[], we set the string to string[0]
1807 // The resulting [] can get returned to the calling app, so clone it.
1808 private static string[] GetMergedPatterns(string [] patterns, string defaultPattern)
1810 Contract.Assert(patterns != null && patterns.Length > 0,
1811 "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
1812 Contract.Assert(defaultPattern != null,
1813 "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
1815 // If the default happens to be the first in the list just return (a cloned) copy
1816 if (defaultPattern == patterns[0])
1818 return (string[])patterns.Clone();
1821 // We either need a bigger list, or the pattern from the list.
1823 for (i = 0; i < patterns.Length; i++)
1825 // Stop if we found it
1826 if (defaultPattern == patterns[i])
1830 // Either way we're going to need a new array
1831 string[] newPatterns;
1834 if (i < patterns.Length)
1836 // Found it, output will be same size
1837 newPatterns = (string[])patterns.Clone();
1839 // Have to move [0] item to [i] so we can re-write default at [0]
1840 // (remember defaultPattern == [i] so this is OK)
1841 newPatterns[i] = newPatterns[0];
1845 // Not found, make room for it
1846 newPatterns = new String[patterns.Length + 1];
1848 // Copy existing array
1849 Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
1852 // Remember the default
1853 newPatterns[0] = defaultPattern;
1855 // Return the reconstructed list
1859 // Default string isn't necessarily in our string array, so get the
1860 // merged patterns of both
1861 private String[] AllYearMonthPatterns
1865 return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
1869 private String[] AllShortDatePatterns
1873 return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
1877 private String[] AllShortTimePatterns
1881 return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
1885 private String[] AllLongDatePatterns
1889 return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
1893 private String[] AllLongTimePatterns
1897 return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
1901 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1902 // This won't include default, call AllYearMonthPatterns
1903 private String[] UnclonedYearMonthPatterns
1907 if (this.allYearMonthPatterns == null)
1909 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
1910 this.allYearMonthPatterns = this.m_cultureData.YearMonths(this.Calendar.ID);
1911 Contract.Assert(this.allYearMonthPatterns.Length > 0,
1912 "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
1915 return this.allYearMonthPatterns;
1920 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1921 // This won't include default, call AllShortDatePatterns
1922 private String [] UnclonedShortDatePatterns
1926 if (allShortDatePatterns == null)
1928 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
1929 this.allShortDatePatterns = this.m_cultureData.ShortDates(this.Calendar.ID);
1930 Contract.Assert(this.allShortDatePatterns.Length > 0,
1931 "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
1934 return this.allShortDatePatterns;
1938 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1939 // This won't include default, call AllLongDatePatterns
1940 private String[] UnclonedLongDatePatterns
1944 if (allLongDatePatterns == null)
1946 Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
1947 this.allLongDatePatterns = this.m_cultureData.LongDates(this.Calendar.ID);
1948 Contract.Assert(this.allLongDatePatterns.Length > 0,
1949 "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
1952 return this.allLongDatePatterns;
1956 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1957 // This won't include default, call AllShortTimePatterns
1958 private String[] UnclonedShortTimePatterns
1962 if (this.allShortTimePatterns == null)
1964 this.allShortTimePatterns = this.m_cultureData.ShortTimes;
1965 Contract.Assert(this.allShortTimePatterns.Length > 0,
1966 "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
1969 return this.allShortTimePatterns;
1973 // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
1974 // This won't include default, call AllLongTimePatterns
1975 private String[] UnclonedLongTimePatterns
1979 if (this.allLongTimePatterns == null)
1981 this.allLongTimePatterns = this.m_cultureData.LongTimes;
1982 Contract.Assert(this.allLongTimePatterns.Length > 0,
1983 "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
1986 return this.allLongTimePatterns;
1990 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) {
1992 throw new ArgumentNullException("dtfi",
1993 Environment.GetResourceString("ArgumentNull_Obj"));
1995 Contract.EndContractBlock();
1996 if (dtfi.IsReadOnly) {
1999 DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
2000 // We can use the data member calendar in the setter, instead of the property Calendar,
2001 // since the cloned copy should have the same state as the original copy.
2002 newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
2003 newInfo.m_isReadOnly = true;
2008 public bool IsReadOnly {
2010 return (m_isReadOnly);
2014 // Return the native name for the calendar in DTFI.Calendar. The native name is referred to
2015 // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese.
2016 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
2017 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
2018 // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
2019 // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
2020 [System.Runtime.InteropServices.ComVisible(false)]
2021 public String NativeCalendarName
2025 return m_cultureData.CalendarName(Calendar.ID);
2030 // Used by custom cultures and others to set the list of available formats. Note that none of them are
2031 // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
2034 // Most of the format characters that can be used in GetAllDateTimePatterns are
2035 // not really needed since they are one of the following:
2037 // r/R/s/u locale-independent constants -- cannot be changed!
2038 // m/M/y/Y fields with a single string in them -- that can be set through props directly
2039 // f/F/g/G/U derived fields based on combinations of various of the below formats
2041 // NOTE: No special validation is done here beyond what is done when the actual respective fields
2042 // are used (what would be the point of disallowing here what we allow in the appropriate property?)
2044 // WARNING: If more validation is ever done in one place, it should be done in the other.
2047 [System.Runtime.InteropServices.ComVisible(false)]
2048 public void SetAllDateTimePatterns(String[] patterns, char format)
2051 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2052 if (patterns == null) {
2053 throw new ArgumentNullException("patterns",
2054 Environment.GetResourceString("ArgumentNull_Array"));
2057 if (patterns.Length == 0)
2059 throw new ArgumentException(Environment.GetResourceString("Arg_ArrayZeroError"), "patterns");
2061 Contract.EndContractBlock();
2063 for (int i=0; i<patterns.Length; i++)
2065 if (patterns[i] == null)
2067 throw new ArgumentNullException("patterns[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayValue"));
2071 // Remember the patterns, and use the 1st as default
2075 this.allShortDatePatterns = patterns;
2076 this.shortDatePattern = this.allShortDatePatterns[0];
2080 this.allLongDatePatterns = patterns;
2081 this.longDatePattern = this.allLongDatePatterns[0];
2085 this.allShortTimePatterns = patterns;
2086 this.shortTimePattern = this.allShortTimePatterns[0];
2090 this.allLongTimePatterns = patterns;
2091 this.longTimePattern = this.allLongTimePatterns[0];
2096 this.allYearMonthPatterns = patterns;
2097 this.yearMonthPattern = this.allYearMonthPatterns[0];
2101 throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
2104 // Clear the token hash table, note that even short dates could require this
2105 ClearTokenHashTable();
2110 [System.Runtime.InteropServices.ComVisible(false)]
2111 public String[] AbbreviatedMonthGenitiveNames
2115 return ((String[])internalGetGenitiveMonthNames(true).Clone());
2121 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2124 throw new ArgumentNullException("value",
2125 Environment.GetResourceString("ArgumentNull_Array"));
2127 if (value.Length != 13)
2129 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2131 Contract.EndContractBlock();
2132 CheckNullValue(value, value.Length - 1);
2133 ClearTokenHashTable();
2134 this.m_genitiveAbbreviatedMonthNames= value;
2138 [System.Runtime.InteropServices.ComVisible(false)]
2139 public String[] MonthGenitiveNames
2143 return ((String[])internalGetGenitiveMonthNames(false).Clone());
2149 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
2152 throw new ArgumentNullException("value",
2153 Environment.GetResourceString("ArgumentNull_Array"));
2155 if (value.Length != 13)
2157 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
2159 Contract.EndContractBlock();
2160 CheckNullValue(value, value.Length - 1);
2161 genitiveMonthNames= value;
2162 ClearTokenHashTable();
2167 // Positive TimeSpan Pattern
2170 private string m_fullTimeSpanPositivePattern;
2171 internal String FullTimeSpanPositivePattern
2175 if (m_fullTimeSpanPositivePattern == null)
2177 CultureData cultureDataWithoutUserOverrides;
2178 if (m_cultureData.UseUserOverride)
2179 cultureDataWithoutUserOverrides = CultureData.GetCultureData(m_cultureData.CultureName, false);
2181 cultureDataWithoutUserOverrides = m_cultureData;
2182 String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
2184 m_fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
2186 return m_fullTimeSpanPositivePattern;
2191 // Negative TimeSpan Pattern
2194 private string m_fullTimeSpanNegativePattern;
2195 internal String FullTimeSpanNegativePattern
2199 if (m_fullTimeSpanNegativePattern == null)
2200 m_fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
2201 return m_fullTimeSpanNegativePattern;
2206 // Get suitable CompareInfo from current DTFI object.
2208 internal CompareInfo CompareInfo
2212 if (m_compareInfo == null)
2214 // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
2215 // CompareInfo cache. otherwise we would just create CompareInfo using m_cultureData.
2216 m_compareInfo = CompareInfo.GetCompareInfo(m_cultureData.SCOMPAREINFO);
2219 return m_compareInfo;
2224 internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
2225 | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
2226 | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
2227 | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
2229 internal static void ValidateStyles(DateTimeStyles style, String parameterName) {
2230 if ((style & InvalidDateTimeStyles) != 0) {
2231 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidDateTimeStyles"), parameterName);
2233 if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) {
2234 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeStyles"), parameterName);
2236 Contract.EndContractBlock();
2237 if (((style & DateTimeStyles.RoundtripKind) != 0)
2238 && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) {
2239 throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeRoundtripStyles"), parameterName);
2244 // Actions: Return the internal flag used in formatting and parsing.
2245 // 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.
2247 internal DateTimeFormatFlags FormatFlags
2251 if (formatFlags == DateTimeFormatFlags.NotInitialized)
2253 // Build the format flags from the data in this DTFI
2254 formatFlags = DateTimeFormatFlags.None;
2255 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
2256 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2257 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
2258 MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
2259 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames);
2260 formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
2262 return (formatFlags);
2266 internal Boolean HasForceTwoDigitYears {
2268 switch (calendar.ID)
2270 // If is y/yy, do not get (year % 100). "y" will print
2271 // year without leading zero. "yy" will print year with two-digit in leading zero.
2272 // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
2273 // So year 5 is "05", and year 125 is "125".
2274 // The reason for not doing (year % 100) is for Taiwan calendar.
2275 // If year 125, then output 125 and not 25.
2276 // Note: OS uses "yyyy" for Taiwan calendar by default.
2277 case (Calendar.CAL_JAPAN):
2278 case (Calendar.CAL_TAIWAN):
2285 // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
2286 internal Boolean HasYearMonthAdjustment {
2288 return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
2292 // This is a callback that the parser can make back into the DTFI to let it fiddle with special
2293 // cases associated with that culture or calendar. Currently this only has special cases for
2294 // the Hebrew calendar, but this could be extended to other cultures.
2296 // The return value is whether the year and month are actually valid for this calendar.
2297 internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) {
2298 if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
2300 // Special rules to fix up the Hebrew year/month
2302 // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
2303 // E.g. if the year is 5763, we only format as 763.
2308 // Because we need to calculate leap year, we should fall out now for an invalid year.
2309 if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) {
2313 // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
2314 // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
2315 // shuffle the other months down.
2316 if (parsedMonthName) {
2317 if (!Calendar.IsLeapYear(year)) {
2321 else if (month == 7) {
2331 // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
2334 TokenHashValue[] m_dtfiTokenHash;
2336 private const int TOKEN_HASH_SIZE = 199;
2337 private const int SECOND_PRIME = 197;
2338 private const String dateSeparatorOrTimeZoneOffset = "-";
2339 private const String invariantDateSeparator = "/";
2340 private const String invariantTimeSeparator = ":";
2343 // Common Ignorable Symbols
2345 internal const String IgnorablePeriod = ".";
2346 internal const String IgnorableComma = ",";
2349 // Year/Month/Day suffixes
2351 internal const String CJKYearSuff = "\u5e74";
2352 internal const String CJKMonthSuff = "\u6708";
2353 internal const String CJKDaySuff = "\u65e5";
2355 internal const String KoreanYearSuff = "\ub144";
2356 internal const String KoreanMonthSuff = "\uc6d4";
2357 internal const String KoreanDaySuff = "\uc77c";
2359 internal const String KoreanHourSuff = "\uc2dc";
2360 internal const String KoreanMinuteSuff = "\ubd84";
2361 internal const String KoreanSecondSuff = "\ucd08";
2363 internal const String CJKHourSuff = "\u6642";
2364 internal const String ChineseHourSuff = "\u65f6";
2366 internal const String CJKMinuteSuff = "\u5206";
2367 internal const String CJKSecondSuff = "\u79d2";
2369 internal const String LocalTimeMark = "T";
2371 internal const String KoreanLangName = "ko";
2372 internal const String JapaneseLangName = "ja";
2373 internal const String EnglishLangName = "en";
2375 private static volatile DateTimeFormatInfo s_jajpDTFI;
2376 private static volatile DateTimeFormatInfo s_zhtwDTFI;
2379 // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse
2380 // date string with Japanese era name correctly even when the supplied DTFI
2381 // does not use Japanese calendar.
2382 // The created instance is stored in global s_jajpDTFI.
2384 internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() {
2385 DateTimeFormatInfo temp = s_jajpDTFI;
2387 temp = new CultureInfo("ja-JP", false).DateTimeFormat;
2388 temp.Calendar = JapaneseCalendar.GetDefaultInstance();
2393 internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() {
2394 DateTimeFormatInfo temp = s_zhtwDTFI;
2396 temp = new CultureInfo("zh-TW", false).DateTimeFormat;
2397 temp.Calendar = TaiwanCalendar.GetDefaultInstance();
2404 // DTFI properties should call this when the setter are called.
2405 private void ClearTokenHashTable()
2407 m_dtfiTokenHash = null;
2408 formatFlags = DateTimeFormatFlags.NotInitialized;
2411 [System.Security.SecurityCritical] // auto-generated
2412 internal TokenHashValue[] CreateTokenHashTable() {
2413 TokenHashValue[] temp = m_dtfiTokenHash;
2415 temp = new TokenHashValue[TOKEN_HASH_SIZE];
2417 bool koreanLanguage = LanguageName.Equals(KoreanLangName);
2419 string sep = this.TimeSeparator.Trim();
2420 if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
2421 if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
2423 if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) {
2425 // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
2426 // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
2427 // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
2428 // instead of SEP_HourSuff.
2430 InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
2433 InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2434 InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2436 if (LanguageName.Equals("sq")) {
2437 // Albanian allows time formats like "12:00.PD"
2438 InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2439 InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2443 InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
2444 InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
2445 InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
2446 InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
2447 InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
2448 InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
2450 InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
2451 InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
2452 InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2453 InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
2455 if (koreanLanguage) {
2457 InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
2458 InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
2459 InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
2462 if ( LanguageName.Equals("ky")) {
2463 // For some cultures, the date separator works more like a comma, being allowed before or after any date part
2464 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
2467 InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
2470 String[] dateWords = null;
2471 DateTimeFormatInfoScanner scanner = null;
2473 // We need to rescan the date words since we're always synthetic
2474 scanner = new DateTimeFormatInfoScanner();
2475 // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
2476 // The only reason they're being assigned to m_dateWords is for Whidbey Deserialization
2477 m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this);
2478 // Ensure the formatflags is initialized.
2479 DateTimeFormatFlags flag = FormatFlags;
2481 // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
2482 // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
2483 // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
2484 bool useDateSepAsIgnorableSymbol = false;
2486 String monthPostfix = null;
2487 if (dateWords != null)
2489 // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
2490 // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
2491 for (int i = 0; i < dateWords.Length; i++)
2493 switch (dateWords[i][0])
2495 // This is a month postfix
2496 case DateTimeFormatInfoScanner.MonthPostfixChar:
2497 // Get the real month postfix.
2498 monthPostfix = dateWords[i].Substring(1);
2499 // Add the month name + postfix into the token.
2500 AddMonthNames(temp, monthPostfix);
2502 case DateTimeFormatInfoScanner.IgnorableSymbolChar:
2503 String symbol = dateWords[i].Substring(1);
2504 InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
2505 if (this.DateSeparator.Trim(null).Equals(symbol))
2507 // The date separator is the same as the ingorable symbol.
2508 useDateSepAsIgnorableSymbol = true;
2512 InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
2513 if (LanguageName.Equals("eu")) {
2514 // Basque has date words with leading dots
2515 InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
2522 if (!useDateSepAsIgnorableSymbol)
2524 // Use the normal date separator.
2525 InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
2527 // Add the regular month names.
2528 AddMonthNames(temp, null);
2530 // Add the abbreviated month names.
2531 for (int i = 1; i <= 13; i++) {
2532 InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
2536 if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) {
2537 for (int i = 1; i <= 13; i++) {
2539 str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
2540 InsertHash(temp, str, TokenType.MonthToken, i);
2544 if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
2545 for (int i = 1; i <= 13; i++) {
2547 str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
2548 InsertHash(temp, str, TokenType.MonthToken, i);
2552 for (int i = 0; i < 7; i++) {
2553 //String str = GetDayOfWeekNames()[i];
2554 // We have to call public methods here to work with inherited DTFI.
2555 String str = GetDayName((DayOfWeek)i);
2556 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2558 str = GetAbbreviatedDayName((DayOfWeek)i);
2559 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2563 int[] eras = calendar.Eras;
2564 for (int i = 1; i <= eras.Length; i++) {
2565 InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
2566 InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
2569 if (LanguageName.Equals(JapaneseLangName)) {
2570 // Japanese allows day of week forms like: "(Tue)"
2571 for (int i = 0; i < 7; i++) {
2572 String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
2573 InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
2575 if (this.Calendar.GetType() != typeof(JapaneseCalendar)) {
2576 // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar,
2577 // we will check Japanese Era name as well when the calendar is Gregorian.
2578 DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
2579 for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) {
2580 InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
2581 InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
2582 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2583 InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i-1], TokenType.JapaneseEraToken, i);
2587 else if (CultureName.Equals("zh-TW")) {
2588 DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
2589 for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) {
2590 if (twDtfi.GetEraName(i).Length > 0) {
2591 InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
2596 InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
2597 InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
2599 // Add invariant month names and day names.
2600 for (int i = 1; i <= 12; i++) {
2602 // We have to call public methods here to work with inherited DTFI.
2603 // Insert the month name first, so that they are at the front of abbrevaited
2605 str = InvariantInfo.GetMonthName(i);
2606 InsertHash(temp, str, TokenType.MonthToken, i);
2607 str = InvariantInfo.GetAbbreviatedMonthName(i);
2608 InsertHash(temp, str, TokenType.MonthToken, i);
2611 for (int i = 0; i < 7; i++) {
2612 // We have to call public methods here to work with inherited DTFI.
2613 String str = InvariantInfo.GetDayName((DayOfWeek)i);
2614 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2616 str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
2617 InsertHash(temp, str, TokenType.DayOfWeekToken, i);
2621 for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
2622 // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
2623 InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
2626 InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
2627 InsertHash(temp, DateTimeParse.GMTName, TokenType.TimeZoneToken, 0);
2628 InsertHash(temp, DateTimeParse.ZuluName, TokenType.TimeZoneToken, 0);
2630 InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
2631 InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
2633 m_dtfiTokenHash = temp;
2638 private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
2640 for (int i = 1; i <= 13; i++) {
2642 //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
2643 // We have to call public methods here to work with inherited DTFI.
2644 // Insert the month name first, so that they are at the front of abbrevaited
2646 str = GetMonthName(i);
2647 if (str.Length > 0) {
2648 if (monthPostfix != null) {
2649 // Insert the month name with the postfix first, so it can be matched first.
2650 InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
2653 InsertHash(temp, str, TokenType.MonthToken, i);
2656 str = GetAbbreviatedMonthName(i);
2657 InsertHash(temp, str, TokenType.MonthToken, i);
2662 ////////////////////////////////////////////////////////////////////////
2665 // Try to parse the current word to see if it is a Hebrew number.
2666 // Tokens will be updated accordingly.
2667 // This is called by the Lexer of DateTime.Parse().
2669 // Unlike most of the functions in this class, the return value indicates
2670 // whether or not it started to parse. The badFormat parameter indicates
2671 // if parsing began, but the format was bad.
2673 ////////////////////////////////////////////////////////////////////////
2675 private static bool TryParseHebrewNumber(
2677 out Boolean badFormat,
2684 if (!HebrewNumber.IsDigit(str.Value[i])) {
2685 // If the current character is not a Hebrew digit, just return false.
2686 // There is no chance that we can parse a valid Hebrew number from here.
2689 // The current character is a Hebrew digit. Try to parse this word as a Hebrew number.
2690 HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
2691 HebrewNumberParsingState state;
2694 state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
2696 case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number.
2697 case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character.
2698 // Break out so that we don't continue to try parse this as a Hebrew number.
2701 } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
2703 // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
2704 Contract.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
2705 "Invalid returned state from HebrewNumber.ParseByChar()");
2707 if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) {
2708 // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
2712 // We have found a valid Hebrew number. Update the index.
2713 str.Advance(i - str.Index);
2715 // Get the final Hebrew number value from the HebrewNumberParsingContext.
2716 number = context.result;
2721 private static bool IsHebrewChar(char ch) {
2722 return (ch >= '\x0590' && ch <= '\x05ff');
2725 [System.Security.SecurityCritical] // auto-generated
2726 internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, ref __DTString str) {
2727 tokenType = TokenType.UnknownToken;
2730 TokenHashValue value;
2731 Contract.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
2733 char ch = str.m_current;
2734 bool isLetter = Char.IsLetter(ch);
2736 ch = Char.ToLower(ch, this.Culture);
2737 if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) {
2739 if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) {
2741 tokenType = TokenType.UnknownToken;
2744 // This is a Hebrew number.
2745 // Do nothing here. TryParseHebrewNumber() will update token accordingly.
2746 tokenType = TokenType.HebrewNumber;
2753 int hashcode = ch % TOKEN_HASH_SIZE;
2754 int hashProbe = 1 + ch % SECOND_PRIME;
2755 int remaining = str.len - str.Index;
2758 TokenHashValue[] hashTable = m_dtfiTokenHash;
2759 if (hashTable == null) {
2760 hashTable = CreateTokenHashTable();
2763 value = hashTable[hashcode];
2764 if (value == null) {
2768 // Check this value has the right category (regular token or separator token) that we are looking for.
2769 if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) {
2770 if (String.Compare(str.Value, str.Index, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase)==0) {
2772 // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately.
2774 if ((nextCharIndex = str.Index + value.tokenString.Length) < str.len) {
2775 // Check word boundary. The next character should NOT be a letter.
2776 char nextCh = str.Value[nextCharIndex];
2777 if (Char.IsLetter(nextCh)) {
2782 tokenType = value.tokenType & TokenMask;
2783 tokenValue = value.tokenValue;
2784 str.Advance(value.tokenString.Length);
2786 } else if (value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) {
2787 // For month token, we will match the month names which have spaces.
2788 int matchStrLen = 0;
2789 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2790 tokenType = value.tokenType & TokenMask;
2791 tokenValue = value.tokenValue;
2792 str.Advance(matchStrLen);
2795 } else if (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames) {
2796 // For month token, we will match the month names which have spaces.
2797 int matchStrLen = 0;
2798 if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
2799 tokenType = value.tokenType & TokenMask;
2800 tokenValue = value.tokenValue;
2801 str.Advance(matchStrLen);
2807 hashcode += hashProbe;
2808 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2809 }while (i < TOKEN_HASH_SIZE);
2814 void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) {
2815 // Remember the current slot.
2816 TokenHashValue previousNode = hashTable[hashcode];
2818 //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert);
2819 // Insert the new node into the current slot.
2820 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);;
2822 while (++pos < TOKEN_HASH_SIZE) {
2823 hashcode += hashProbe;
2824 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2825 // Remember this slot
2826 TokenHashValue temp = hashTable[hashcode];
2828 if (temp != null && Char.ToLower(temp.tokenString[0], this.Culture) != ch) {
2831 // Put the previous slot into this slot.
2832 hashTable[hashcode] = previousNode;
2833 //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
2838 previousNode = temp;
2840 Contract.Assert(false, "The hashtable is full. This should not happen.");
2843 void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) {
2844 // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
2845 if (str == null || str.Length == 0) {
2848 TokenHashValue value;
2850 // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
2851 // DateTime.Parse().
2852 if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) {
2853 str = str.Trim(null); // Trim white space characters.
2854 // Could have space for separators
2855 if (str.Length == 0)
2858 char ch = Char.ToLower(str[0], this.Culture);
2859 int hashcode = ch % TOKEN_HASH_SIZE;
2860 int hashProbe = 1 + ch % SECOND_PRIME;
2862 value = hashTable[hashcode];
2863 if (value == null) {
2864 //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
2865 hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
2868 // Collision happens. Find another slot.
2869 if (str.Length >= value.tokenString.Length) {
2870 // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
2871 // the shorter ones.
2872 if (String.Compare(str, 0, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase) == 0) {
2873 if (str.Length > value.tokenString.Length) {
2874 // The str to be inserted has the same prefix as the current token, and str is longer.
2875 // Insert str into this node, and shift every node behind it.
2876 InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
2879 // Same token. If they have different types (regular token vs separator token). Add them.
2880 // If we have the same regular token or separator token in the hash already, do NOT update the hash.
2881 // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
2885 // 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.
2886 // 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.
2889 int nTokenType = (int)tokenType;
2890 int nCurrentTokenTypeInHash = (int)value.tokenType;
2892 // The idea behind this check is:
2893 // - if the app is targetting 4.5.1 or above OR the compat flag is set, use the correct behavior by default.
2894 // - if the app is targetting 4.5 or below AND the compat switch is set, use the correct behavior
2895 // - if the app is targetting 4.5 or below AND the compat switch is NOT set, use the incorrect behavior
2896 if (preferExistingTokens || BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
2898 if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
2899 ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
2901 value.tokenType |= tokenType;
2902 if (tokenValue != 0)
2904 value.tokenValue = tokenValue;
2910 // The following logic is incorrect and causes updates to happen depending on the bitwise relationship between the existing token type and the
2911 // the stored token type. It was this way in .NET 4 RTM. The behavior above is correct and will be adopted going forward.
2913 if ((((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.RegularTokenMask) == nTokenType) ||
2914 (((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.SeparatorTokenMask) == nTokenType))
2916 value.tokenType |= tokenType;
2917 if (tokenValue != 0)
2919 value.tokenValue = tokenValue;
2923 // The token to be inserted is already in the table. Skip it.
2928 //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
2930 hashcode += hashProbe;
2931 if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
2932 } while (i < TOKEN_HASH_SIZE);
2933 Contract.Assert(false, "The hashtable is full. This should not happen.");
2935 } // class DateTimeFormatInfo
2937 internal class TokenHashValue {
2938 internal String tokenString;
2939 internal TokenType tokenType;
2940 internal int tokenValue;
2942 internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) {
2943 this.tokenString = tokenString;
2944 this.tokenType = tokenType;
2945 this.tokenValue = tokenValue;