diff options
Diffstat (limited to 'src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs')
-rw-r--r-- | src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs | 3087 |
1 files changed, 0 insertions, 3087 deletions
diff --git a/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs deleted file mode 100644 index b79ce90424..0000000000 --- a/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs +++ /dev/null @@ -1,3087 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.Runtime.Serialization; - -namespace System.Globalization -{ - // - // Flags used to indicate different styles of month names. - // This is an internal flag used by internalGetMonthName(). - // Use flag here in case that we need to provide a combination of these styles - // (such as month name of a leap year in genitive form. Not likely for now, - // but would like to keep the option open). - // - - [Flags] - internal enum MonthNameStyles - { - Regular = 0x00000000, - Genitive = 0x00000001, - LeapYear = 0x00000002, - } - - // - // Flags used to indicate special rule used in parsing/formatting - // for a specific DateTimeFormatInfo instance. - // This is an internal flag. - // - // This flag is different from MonthNameStyles because this flag - // can be expanded to accomodate parsing behaviors like CJK month names - // or alternative month names, etc. - - [Flags] - internal enum DateTimeFormatFlags - { - None = 0x00000000, - UseGenitiveMonth = 0x00000001, - UseLeapYearMonth = 0x00000002, - UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names. - UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule. - UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names. - UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers. - - NotInitialized = -1, - } - - - [Serializable] - public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable - { - // cache for the invariant culture. - // invariantInfo is constant irrespective of your current culture. - private static volatile DateTimeFormatInfo s_invariantInfo; - - // an index which points to a record in Culture Data Table. - [NonSerialized] - private CultureData _cultureData; - - // The culture name used to create this DTFI. - - [OptionalField(VersionAdded = 2)] - private String _name = null; - - // The language name of the culture used to create this DTFI. - [NonSerialized] - private String _langName = null; - - // CompareInfo usually used by the parser. - [NonSerialized] - private CompareInfo _compareInfo = null; - - // Culture matches current DTFI. mainly used for string comparisons during parsing. - [NonSerialized] - private CultureInfo _cultureInfo = null; - - // - // Caches for various properties. - // - - private String amDesignator = null; - private String pmDesignator = null; - - private String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't) - - private String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't) - - private String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't) - - private String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't) - private String monthDayPattern = null; - // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM} - private String dateTimeOffsetPattern = null; - - // - // The following are constant values. - // - private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"; - - // The sortable pattern is based on ISO 8601. - private const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"; - private const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'"; - - // - // The following are affected by calendar settings. - // - private Calendar calendar = null; - - private int firstDayOfWeek = -1; - private int calendarWeekRule = -1; - - - private String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't) - - private String[] abbreviatedDayNames = null; - - - private String[] m_superShortDayNames = null; - - private String[] dayNames = null; - private String[] abbreviatedMonthNames = null; - private String[] monthNames = null; - // Cache the genitive month names that we retrieve from the data table. - - private String[] genitiveMonthNames = null; - - // Cache the abbreviated genitive month names that we retrieve from the data table. - - private String[] m_genitiveAbbreviatedMonthNames = null; - - // Cache the month names of a leap year that we retrieve from the data table. - - private String[] leapYearMonthNames = null; - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - - // The "default" Date/time patterns - private String longDatePattern = null; - private String shortDatePattern = null; - private String yearMonthPattern = null; - private String longTimePattern = null; - private String shortTimePattern = null; - - [OptionalField(VersionAdded = 3)] - private String[] allYearMonthPatterns = null; - - private String[] allShortDatePatterns = null; - private String[] allLongDatePatterns = null; - private String[] allShortTimePatterns = null; - private String[] allLongTimePatterns = null; - - // Cache the era names for this DateTimeFormatInfo instance. - private String[] m_eraNames = null; - private String[] m_abbrevEraNames = null; - private String[] m_abbrevEnglishEraNames = null; - - private CalendarId[] optionalCalendars = null; - - private const int DEFAULT_ALL_DATETIMES_SIZE = 132; - - // CultureInfo updates this - internal bool _isReadOnly = false; - - // This flag gives hints about if formatting/parsing should perform special code path for things like - // genitive form or leap year month names. - - private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized; - - private String CultureName - { - get - { - if (_name == null) - { - _name = _cultureData.CultureName; - } - return (_name); - } - } - - private CultureInfo Culture - { - get - { - if (_cultureInfo == null) - { - _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName); - } - return _cultureInfo; - } - } - - // TODO: This ignores other cultures that might want to do something similar - private String LanguageName - { - get - { - if (_langName == null) - { - _langName = _cultureData.SISO639LANGNAME; - } - return (_langName); - } - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the abbreviated day names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetAbbreviatedDayOfWeekNames() - { - if (this.abbreviatedDayNames == null) - { - // Get the abbreviated day names for our current calendar - this.abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID); - Debug.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week"); - } - return (this.abbreviatedDayNames); - } - - - //////////////////////////////////////////////////////////////////////// - // - // Action: Returns the string array of the one-letter day of week names. - // Returns: - // an array of one-letter day of week names - // Arguments: - // None - // Exceptions: - // None - // - //////////////////////////////////////////////////////////////////////// - - private String[] internalGetSuperShortDayNames() - { - if (this.m_superShortDayNames == null) - { - // Get the super short day names for our current calendar - this.m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID); - Debug.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week"); - } - return (this.m_superShortDayNames); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the day names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetDayOfWeekNames() - { - if (this.dayNames == null) - { - // Get the day names for our current calendar - this.dayNames = _cultureData.DayNames(Calendar.ID); - Debug.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week"); - } - return (this.dayNames); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the abbreviated month names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetAbbreviatedMonthNames() - { - if (this.abbreviatedMonthNames == null) - { - // Get the month names for our current calendar - this.abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID); - Debug.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13, - "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year"); - } - return (this.abbreviatedMonthNames); - } - - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the month names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetMonthNames() - { - if (this.monthNames == null) - { - // Get the month names for our current calendar - this.monthNames = _cultureData.MonthNames(Calendar.ID); - Debug.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13, - "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year"); - } - - return (this.monthNames); - } - - - // - // Invariant DateTimeFormatInfo doesn't have user-overriden values - // Default calendar is gregorian - public DateTimeFormatInfo() - : this(CultureInfo.InvariantCulture.m_cultureData, - GregorianCalendar.GetDefaultInstance()) - { - } - - internal DateTimeFormatInfo(CultureData cultureData, Calendar cal) - { - Debug.Assert(cultureData != null); - Debug.Assert(cal != null); - - // Remember our culture - _cultureData = cultureData; - - this.Calendar = cal; - } - - private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId) - { - Debug.Assert(cultureData != null); - Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId"); - - if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; } - if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; } - - if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; } - if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; } - if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; } - if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarId); } - - this.allLongTimePatterns = _cultureData.LongTimes; - Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns"); - - this.allShortTimePatterns = _cultureData.ShortTimes; - Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns"); - - this.allLongDatePatterns = cultureData.LongDates(calendarId); - Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns"); - - this.allShortDatePatterns = cultureData.ShortDates(calendarId); - Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns"); - - this.allYearMonthPatterns = cultureData.YearMonths(calendarId); - Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns"); - } - - [OptionalField(VersionAdded = 1)] - private bool _useUserOverride; - - // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string - // Now we always synthesize so its not helpful - - internal String[] m_dateWords = null; - - [OnSerializing] - private void OnSerializing(StreamingContext ctx) - { - _name = this.CultureName; // make sure the _name is initialized. - _useUserOverride = _cultureData.UseUserOverride; - - // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey - // because Whidbey try to initialize some of these fields using calendar data which could be null values - // and then we get exceptions. So we call the accessors to force the caches to get loaded. - Object o; - o = this.LongTimePattern; - o = this.LongDatePattern; - o = this.ShortTimePattern; - o = this.ShortDatePattern; - o = this.YearMonthPattern; - o = this.AllLongTimePatterns; - o = this.AllLongDatePatterns; - o = this.AllShortTimePatterns; - o = this.AllShortDatePatterns; - o = this.AllYearMonthPatterns; - } - - [OnDeserialized] - private void OnDeserialized(StreamingContext ctx) - { - if (_name != null) - { - _cultureData = CultureData.GetCultureData(_name, _useUserOverride); - if (_cultureData == null) - { - throw new CultureNotFoundException("_name", _name, SR.Argument_CultureNotSupported); - } - } - - if (calendar == null) - { - calendar = (Calendar)GregorianCalendar.GetDefaultInstance().Clone(); - calendar.SetReadOnlyState(_isReadOnly); - } - - InitializeOverridableProperties(_cultureData, calendar.ID); - - // - // turn off read only state till we finish initializing all fields and then store read only state after we are done. - // - bool isReadOnly = _isReadOnly; - _isReadOnly = false; - - // If we deserialized defaults ala Whidbey, make sure they're still defaults - // Whidbey's arrays could get a bit mixed up. - if (longDatePattern != null) this.LongDatePattern = longDatePattern; - if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern; - if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern; - if (longTimePattern != null) this.LongTimePattern = longTimePattern; - if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern; - - _isReadOnly = isReadOnly; - } - - // Returns a default DateTimeFormatInfo that will be universally - // supported and constant irrespective of the current culture. - // Used by FromString methods. - // - - public static DateTimeFormatInfo InvariantInfo - { - get - { - Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null); - if (s_invariantInfo == null) - { - DateTimeFormatInfo info = new DateTimeFormatInfo(); - info.Calendar.SetReadOnlyState(true); - info._isReadOnly = true; - s_invariantInfo = info; - } - return (s_invariantInfo); - } - } - - // Returns the current culture's DateTimeFormatInfo. Used by Parse methods. - // - - public static DateTimeFormatInfo CurrentInfo - { - get - { - Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null); - System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture; - if (!culture.m_isInherited) - { - DateTimeFormatInfo info = culture.dateTimeInfo; - if (info != null) - { - return info; - } - } - return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo)); - } - } - - - public static DateTimeFormatInfo GetInstance(IFormatProvider provider) - { - // Fast case for a regular CultureInfo - DateTimeFormatInfo info; - CultureInfo cultureProvider = provider as CultureInfo; - if (cultureProvider != null && !cultureProvider.m_isInherited) - { - return cultureProvider.DateTimeFormat; - } - // Fast case for a DTFI; - info = provider as DateTimeFormatInfo; - if (info != null) - { - return info; - } - // Wasn't cultureInfo or DTFI, do it the slower way - if (provider != null) - { - info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo; - if (info != null) - { - return info; - } - } - // Couldn't get anything, just use currentInfo as fallback - return CurrentInfo; - } - - - public Object GetFormat(Type formatType) - { - return (formatType == typeof(DateTimeFormatInfo) ? this : null); - } - - - public Object Clone() - { - DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone(); - // We can use the data member calendar in the setter, instead of the property Calendar, - // since the cloned copy should have the same state as the original copy. - n.calendar = (Calendar)this.Calendar.Clone(); - n._isReadOnly = false; - return n; - } - - - public String AMDesignator - { - get - { - if (this.amDesignator == null) - { - this.amDesignator = _cultureData.SAM1159; - } - Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null"); - return (this.amDesignator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - amDesignator = value; - } - } - - - public Calendar Calendar - { - get - { - Contract.Ensures(Contract.Result<Calendar>() != null); - - Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null"); - return (this.calendar); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); - } - Contract.EndContractBlock(); - if (value == calendar) - { - return; - } - - for (int i = 0; i < this.OptionalCalendars.Length; i++) - { - if (this.OptionalCalendars[i] == value.ID) - { - // We can use this one, so do so. - - // Clean related properties if we already had a calendar set - if (calendar != null) - { - // clean related properties which are affected by the calendar setting, - // so that they will be refreshed when they are accessed next time. - // - - // These properites are in the order as appearing in calendar.xml. - m_eraNames = null; - m_abbrevEraNames = null; - m_abbrevEnglishEraNames = null; - - monthDayPattern = null; - - dayNames = null; - abbreviatedDayNames = null; - m_superShortDayNames = null; - monthNames = null; - abbreviatedMonthNames = null; - genitiveMonthNames = null; - m_genitiveAbbreviatedMonthNames = null; - leapYearMonthNames = null; - formatFlags = DateTimeFormatFlags.NotInitialized; - - allShortDatePatterns = null; - allLongDatePatterns = null; - allYearMonthPatterns = null; - dateTimeOffsetPattern = null; - - // The defaults need reset as well: - longDatePattern = null; - shortDatePattern = null; - yearMonthPattern = null; - - // These properies are not in the OS data, but they are dependent on the values like shortDatePattern. - fullDateTimePattern = null; // Long date + long time - generalShortTimePattern = null; // short date + short time - generalLongTimePattern = null; // short date + long time - - // Derived item that changes - dateSeparator = null; - - // We don't need to do these because they are not changed by changing calendar - // amDesignator - // pmDesignator - // timeSeparator - // longTimePattern - // firstDayOfWeek - // calendarWeekRule - - // remember to reload tokens - ClearTokenHashTable(); - } - - // Remember the new calendar - calendar = value; - InitializeOverridableProperties(_cultureData, calendar.ID); - - // We succeeded, return - return; - } - } - - // The assigned calendar is not a valid calendar for this culture, throw - throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar); - } - } - - private CalendarId[] OptionalCalendars - { - get - { - if (this.optionalCalendars == null) - { - this.optionalCalendars = _cultureData.CalendarIds; - } - return (this.optionalCalendars); - } - } - - /*=================================GetEra========================== - **Action: Get the era value by parsing the name of the era. - **Returns: The era value for the specified era name. - ** -1 if the name of the era is not valid or not supported. - **Arguments: eraName the name of the era. - **Exceptions: None. - ============================================================================*/ - - - public int GetEra(String eraName) - { - if (eraName == null) - { - throw new ArgumentNullException(nameof(eraName), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // The Era Name and Abbreviated Era Name - // for Taiwan Calendar on non-Taiwan SKU returns empty string (which - // would be matched below) but we don't want the empty string to give - // us an Era number - // confer 85900 DTFI.GetEra("") should fail on all cultures - if (eraName.Length == 0) - { - return (-1); - } - - // The following is based on the assumption that the era value is starting from 1, and has a - // serial values. - // If that ever changes, the code has to be changed. - - // The calls to String.Compare should use the current culture for the string comparisons, but the - // invariant culture when comparing against the english names. - for (int i = 0; i < EraNames.Length; i++) - { - // Compare the era name in a case-insensitive way for the appropriate culture. - if (m_eraNames[i].Length > 0) - { - if (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - } - for (int i = 0; i < AbbreviatedEraNames.Length; i++) - { - // Compare the abbreviated era name in a case-insensitive way for the appropriate culture. - if (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) - { - // this comparison should use the InvariantCulture. The English name could have linguistically - // interesting characters. - if (CultureInfo.InvariantCulture.CompareInfo.Compare(eraName, m_abbrevEnglishEraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - return (-1); - } - - - internal String[] EraNames - { - get - { - if (this.m_eraNames == null) - { - this.m_eraNames = _cultureData.EraNames(Calendar.ID); ; - } - return (this.m_eraNames); - } - } - - /*=================================GetEraName========================== - **Action: Get the name of the era for the specified era value. - **Returns: The name of the specified era. - **Arguments: - ** era the era value. - **Exceptions: - ** ArguementException if the era valie is invalid. - ============================================================================*/ - - // Era names are 1 indexed - public String GetEraName(int era) - { - if (era == Calendar.CurrentEra) - { - era = Calendar.CurrentEraValue; - } - - // The following is based on the assumption that the era value is starting from 1, and has a - // serial values. - // If that ever changes, the code has to be changed. - if ((--era) < EraNames.Length && (era >= 0)) - { - return (m_eraNames[era]); - } - throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); - } - - internal String[] AbbreviatedEraNames - { - get - { - if (this.m_abbrevEraNames == null) - { - this.m_abbrevEraNames = _cultureData.AbbrevEraNames(Calendar.ID); - } - return (this.m_abbrevEraNames); - } - } - - // Era names are 1 indexed - public String GetAbbreviatedEraName(int era) - { - if (AbbreviatedEraNames.Length == 0) - { - // If abbreviation era name is not used in this culture, - // return the full era name. - return (GetEraName(era)); - } - if (era == Calendar.CurrentEra) - { - era = Calendar.CurrentEraValue; - } - if ((--era) < m_abbrevEraNames.Length && (era >= 0)) - { - return (m_abbrevEraNames[era]); - } - throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); - } - - internal String[] AbbreviatedEnglishEraNames - { - get - { - if (this.m_abbrevEnglishEraNames == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0"); - this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID); - } - return (this.m_abbrevEnglishEraNames); - } - } - - // Note that cultureData derives this from the short date format (unless someone's set this previously) - // Note that this property is quite undesirable. - public string DateSeparator - { - get - { - if (dateSeparator == null) - { - dateSeparator = _cultureData.DateSeparator(Calendar.ID); - } - Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null"); - return dateSeparator; - } - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - dateSeparator = value; - } - } - - public DayOfWeek FirstDayOfWeek - { - get - { - if (this.firstDayOfWeek == -1) - { - this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK; - } - Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1"); - - return ((DayOfWeek)this.firstDayOfWeek); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) - { - firstDayOfWeek = (int)value; - } - else - { - throw new ArgumentOutOfRangeException( - nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - } - } - - public CalendarWeekRule CalendarWeekRule - { - get - { - if (this.calendarWeekRule == -1) - { - this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR; - } - Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1"); - return ((CalendarWeekRule)this.calendarWeekRule); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) - { - calendarWeekRule = (int)value; - } - else - { - throw new ArgumentOutOfRangeException( - nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, - CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek)); - } - } - } - - public String FullDateTimePattern - { - get - { - if (fullDateTimePattern == null) - { - fullDateTimePattern = LongDatePattern + " " + LongTimePattern; - } - return (fullDateTimePattern); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - fullDateTimePattern = value; - } - } - - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String LongDatePattern - { - get - { - // Initialize our long date pattern from the 1st array value if not set - if (this.longDatePattern == null) - { - // Initialize our data - this.longDatePattern = this.UnclonedLongDatePatterns[0]; - } - - return this.longDatePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.longDatePattern = value; - - // Clear the token hash table - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - this.fullDateTimePattern = null; - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String LongTimePattern - { - get - { - // Initialize our long time pattern from the 1st array value if not set - if (this.longTimePattern == null) - { - // Initialize our data - this.longTimePattern = this.UnclonedLongTimePatterns[0]; - } - - return this.longTimePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.longTimePattern = value; - - // Clear the token hash table - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - this.fullDateTimePattern = null; // Full date = long date + long Time - this.generalLongTimePattern = null; // General long date = short date + long Time - this.dateTimeOffsetPattern = null; - } - } - - - // Note: just to be confusing there's only 1 month day pattern, not a whole list - public String MonthDayPattern - { - get - { - if (this.monthDayPattern == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0"); - this.monthDayPattern = _cultureData.MonthDay(Calendar.ID); - } - Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null"); - return (this.monthDayPattern); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - this.monthDayPattern = value; - } - } - - - public String PMDesignator - { - get - { - if (this.pmDesignator == null) - { - this.pmDesignator = _cultureData.SPM2359; - } - Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null"); - return (this.pmDesignator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - - pmDesignator = value; - } - } - - - public String RFC1123Pattern - { - get - { - return (rfc1123Pattern); - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String ShortDatePattern - { - get - { - // Initialize our short date pattern from the 1st array value if not set - if (this.shortDatePattern == null) - { - // Initialize our data - this.shortDatePattern = this.UnclonedShortDatePatterns[0]; - } - - return this.shortDatePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - Contract.EndContractBlock(); - - // Remember the new string - this.shortDatePattern = value; - - // Clear the token hash table, note that even short dates could require this - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - generalLongTimePattern = null; // General long time = short date + long time - generalShortTimePattern = null; // General short time = short date + short Time - dateTimeOffsetPattern = null; - } - } - - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String ShortTimePattern - { - get - { - // Initialize our short time pattern from the 1st array value if not set - if (this.shortTimePattern == null) - { - // Initialize our data - this.shortTimePattern = this.UnclonedShortTimePatterns[0]; - } - return this.shortTimePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.shortTimePattern = value; - - // Clear the token hash table, note that even short times could require this - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - generalShortTimePattern = null; // General short date = short date + short time. - } - } - - - public String SortableDateTimePattern - { - get - { - return (sortableDateTimePattern); - } - } - - /*=================================GeneralShortTimePattern===================== - **Property: Return the pattern for 'g' general format: shortDate + short time - **Note: This is used by DateTimeFormat.cs to get the pattern for 'g' - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody asks for the general format. - ==============================================================================*/ - - internal String GeneralShortTimePattern - { - get - { - if (generalShortTimePattern == null) - { - generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern; - } - return (generalShortTimePattern); - } - } - - /*=================================GeneralLongTimePattern===================== - **Property: Return the pattern for 'g' general format: shortDate + Long time - **Note: This is used by DateTimeFormat.cs to get the pattern for 'g' - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody asks for the general format. - ==============================================================================*/ - - internal String GeneralLongTimePattern - { - get - { - if (generalLongTimePattern == null) - { - generalLongTimePattern = ShortDatePattern + " " + LongTimePattern; - } - return (generalLongTimePattern); - } - } - - /*=================================DateTimeOffsetPattern========================== - **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset - **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody uses this form - ==============================================================================*/ - - /*=================================DateTimeOffsetPattern========================== - **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset - **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody uses this form - ==============================================================================*/ - - internal String DateTimeOffsetPattern - { - get - { - if (dateTimeOffsetPattern == null) - { - string dateTimePattern = ShortDatePattern + " " + LongTimePattern; - - /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */ - - bool foundZ = false; - bool inQuote = false; - char quote = '\''; - for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) - { - switch (LongTimePattern[i]) - { - case 'z': - /* if we aren't in a quote, we've found a z */ - foundZ = !inQuote; - /* we'll fall out of the loop now because the test includes !foundZ */ - break; - case '\'': - case '\"': - if (inQuote && (quote == LongTimePattern[i])) - { - /* we were in a quote and found a matching exit quote, so we are outside a quote now */ - inQuote = false; - } - else if (!inQuote) - { - quote = LongTimePattern[i]; - inQuote = true; - } - else - { - /* we were in a quote and saw the other type of quote character, so we are still in a quote */ - } - break; - case '%': - case '\\': - i++; /* skip next character that is escaped by this backslash */ - break; - default: - break; - } - } - - if (!foundZ) - { - dateTimePattern = dateTimePattern + " zzz"; - } - - dateTimeOffsetPattern = dateTimePattern; - } - return (dateTimeOffsetPattern); - } - } - - // Note that cultureData derives this from the long time format (unless someone's set this previously) - // Note that this property is quite undesirable. - public string TimeSeparator - { - get - { - if (timeSeparator == null) - { - timeSeparator = _cultureData.TimeSeparator; - } - Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null"); - return (timeSeparator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); - } - - Contract.EndContractBlock(); - ClearTokenHashTable(); - - timeSeparator = value; - } - } - - public String UniversalSortableDateTimePattern - { - get - { - return (universalSortableDateTimePattern); - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String YearMonthPattern - { - get - { - // Initialize our year/month pattern from the 1st array value if not set - if (this.yearMonthPattern == null) - { - // Initialize our data - this.yearMonthPattern = this.UnclonedYearMonthPatterns[0]; - } - return this.yearMonthPattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.yearMonthPattern = value; - - // Clear the token hash table, note that even short times could require this - ClearTokenHashTable(); - } - } - - // - // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value" - // - private static void CheckNullValue(String[] values, int length) - { - Debug.Assert(values != null, "value != null"); - Debug.Assert(values.Length >= length); - for (int i = 0; i < length; i++) - { - if (values[i] == null) - { - throw new ArgumentNullException("value", - SR.ArgumentNull_ArrayValue); - } - } - } - - - public String[] AbbreviatedDayNames - { - get - { - return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - ClearTokenHashTable(); - - abbreviatedDayNames = value; - } - } - - // Returns the string array of the one-letter day of week names. - public String[] ShortestDayNames - { - get - { - return ((String[])internalGetSuperShortDayNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - this.m_superShortDayNames = value; - } - } - - - public String[] DayNames - { - get - { - return ((String[])internalGetDayOfWeekNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - ClearTokenHashTable(); - - dayNames = value; - } - } - - - public String[] AbbreviatedMonthNames - { - get - { - return ((String[])internalGetAbbreviatedMonthNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - ClearTokenHashTable(); - abbreviatedMonthNames = value; - } - } - - - public String[] MonthNames - { - get - { - return ((String[])internalGetMonthNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - monthNames = value; - ClearTokenHashTable(); - } - } - - // Whitespaces that we allow in the month names. - // U+00a0 is non-breaking space. - private static readonly char[] s_monthSpaces = { ' ', '\u00a0' }; - - internal bool HasSpacesInMonthNames - { - get - { - return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0; - } - } - - internal bool HasSpacesInDayNames - { - get - { - return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0; - } - } - - - // - // internalGetMonthName - // - // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form - // or full form. - // Arguments: - // month - // style To indicate a form like regular/genitive/month name in a leap year. - // abbreviated When true, return abbreviated form. Otherwise, return a full form. - // Exceptions: - // ArgumentOutOfRangeException When month name is invalid. - // - internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) - { - // - // Right now, style is mutual exclusive, but I make the style to be flag so that - // maybe we can combine flag if there is such a need. - // - String[] monthNamesArray = null; - switch (style) - { - case MonthNameStyles.Genitive: - monthNamesArray = internalGetGenitiveMonthNames(abbreviated); - break; - case MonthNameStyles.LeapYear: - monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/); - break; - default: - monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames() : internalGetMonthNames()); - break; - } - // The month range is from 1 ~ this.m_monthNames.Length - // (actually is 13 right now for all cases) - if ((month < 1) || (month > monthNamesArray.Length)) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, monthNamesArray.Length)); - } - return (monthNamesArray[month - 1]); - } - - // - // internalGetGenitiveMonthNames - // - // Action: Retrieve the array which contains the month names in genitive form. - // If this culture does not use the gentive form, the normal month name is returned. - // Arguments: - // abbreviated When true, return abbreviated form. Otherwise, return a full form. - // - private String[] internalGetGenitiveMonthNames(bool abbreviated) - { - if (abbreviated) - { - if (this.m_genitiveAbbreviatedMonthNames == null) - { - this.m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID); - Debug.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13, - "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year"); - } - return (this.m_genitiveAbbreviatedMonthNames); - } - - if (this.genitiveMonthNames == null) - { - this.genitiveMonthNames = _cultureData.GenitiveMonthNames(this.Calendar.ID); - Debug.Assert(this.genitiveMonthNames.Length == 13, - "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year"); - } - return (this.genitiveMonthNames); - } - - // - // internalGetLeapYearMonthNames - // - // Actions: Retrieve the month names used in a leap year. - // If this culture does not have different month names in a leap year, the normal month name is returned. - // Agruments: None. (can use abbreviated later if needed) - // - internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) - { - if (this.leapYearMonthNames == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0"); - this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID); - Debug.Assert(this.leapYearMonthNames.Length == 13, - "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names"); - } - return (leapYearMonthNames); - } - - - public String GetAbbreviatedDayName(DayOfWeek dayofweek) - { - if ((int)dayofweek < 0 || (int)dayofweek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - // - // Don't call the public property AbbreviatedDayNames here since a clone is needed in that - // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly. - // - return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]); - } - - // Returns the super short day of week names for the specified day of week. - public string GetShortestDayName(DayOfWeek dayOfWeek) - { - if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - // - // Don't call the public property SuperShortDayNames here since a clone is needed in that - // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly. - // - return (internalGetSuperShortDayNames()[(int)dayOfWeek]); - } - - // Get all possible combination of inputs - private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString) - { - Debug.Assert(patterns1 != null); - Debug.Assert(patterns2 != null); - - // Get array size - String[] result = new String[patterns1.Length * patterns2.Length]; - - // Counter of actual results - int k = 0; - for (int i = 0; i < patterns1.Length; i++) - { - for (int j = 0; j < patterns2.Length; j++) - { - // Can't combine if null or empty - result[k++] = patterns1[i] + connectString + patterns2[j]; - } - } - - // Return the combinations - return (result); - } - - public string[] GetAllDateTimePatterns() - { - List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE); - - for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++) - { - String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]); - for (int j = 0; j < strings.Length; j++) - { - results.Add(strings[j]); - } - } - return results.ToArray(); - } - - public string[] GetAllDateTimePatterns(char format) - { - Contract.Ensures(Contract.Result<String[]>() != null); - String[] result = null; - - switch (format) - { - case 'd': - result = this.AllShortDatePatterns; - break; - case 'D': - result = this.AllLongDatePatterns; - break; - case 'f': - result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " "); - break; - case 'F': - case 'U': - result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " "); - break; - case 'g': - result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " "); - break; - case 'G': - result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " "); - break; - case 'm': - case 'M': - result = new String[] { MonthDayPattern }; - break; - case 'o': - case 'O': - result = new String[] { RoundtripFormat }; - break; - case 'r': - case 'R': - result = new String[] { rfc1123Pattern }; - break; - case 's': - result = new String[] { sortableDateTimePattern }; - break; - case 't': - result = this.AllShortTimePatterns; - break; - case 'T': - result = this.AllLongTimePatterns; - break; - case 'u': - result = new String[] { UniversalSortableDateTimePattern }; - break; - case 'y': - case 'Y': - result = this.AllYearMonthPatterns; - break; - default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); - } - return (result); - } - - - public String GetDayName(DayOfWeek dayofweek) - { - if ((int)dayofweek < 0 || (int)dayofweek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - - // Use the internal one so that we don't clone the array unnecessarily - return (internalGetDayOfWeekNames()[(int)dayofweek]); - } - - - - public String GetAbbreviatedMonthName(int month) - { - if (month < 1 || month > 13) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, 13)); - } - Contract.EndContractBlock(); - // Use the internal one so we don't clone the array unnecessarily - return (internalGetAbbreviatedMonthNames()[month - 1]); - } - - - public String GetMonthName(int month) - { - if (month < 1 || month > 13) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, 13)); - } - Contract.EndContractBlock(); - // Use the internal one so we don't clone the array unnecessarily - return (internalGetMonthNames()[month - 1]); - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - // - // The resulting [] can get returned to the calling app, so clone it. - private static string[] GetMergedPatterns(string[] patterns, string defaultPattern) - { - Debug.Assert(patterns != null && patterns.Length > 0, - "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern"); - Debug.Assert(defaultPattern != null, - "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string"); - - // If the default happens to be the first in the list just return (a cloned) copy - if (defaultPattern == patterns[0]) - { - return (string[])patterns.Clone(); - } - - // We either need a bigger list, or the pattern from the list. - int i; - for (i = 0; i < patterns.Length; i++) - { - // Stop if we found it - if (defaultPattern == patterns[i]) - break; - } - - // Either way we're going to need a new array - string[] newPatterns; - - // Did we find it - if (i < patterns.Length) - { - // Found it, output will be same size - newPatterns = (string[])patterns.Clone(); - - // Have to move [0] item to [i] so we can re-write default at [0] - // (remember defaultPattern == [i] so this is OK) - newPatterns[i] = newPatterns[0]; - } - else - { - // Not found, make room for it - newPatterns = new String[patterns.Length + 1]; - - // Copy existing array - Array.Copy(patterns, 0, newPatterns, 1, patterns.Length); - } - - // Remember the default - newPatterns[0] = defaultPattern; - - // Return the reconstructed list - return newPatterns; - } - - // Needed by DateTimeFormatInfo and DateTimeFormat - internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; - internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; - - // Default string isn't necessarily in our string array, so get the - // merged patterns of both - private String[] AllYearMonthPatterns - { - get - { - return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern); - } - } - - private String[] AllShortDatePatterns - { - get - { - return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern); - } - } - - private String[] AllShortTimePatterns - { - get - { - return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern); - } - } - - private String[] AllLongDatePatterns - { - get - { - return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern); - } - } - - private String[] AllLongTimePatterns - { - get - { - return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern); - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllYearMonthPatterns - private String[] UnclonedYearMonthPatterns - { - get - { - if (this.allYearMonthPatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0"); - this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID); - Debug.Assert(this.allYearMonthPatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns"); - } - - return this.allYearMonthPatterns; - } - } - - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllShortDatePatterns - private String[] UnclonedShortDatePatterns - { - get - { - if (allShortDatePatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0"); - this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID); - Debug.Assert(this.allShortDatePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns"); - } - - return this.allShortDatePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllLongDatePatterns - private String[] UnclonedLongDatePatterns - { - get - { - if (allLongDatePatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0"); - this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID); - Debug.Assert(this.allLongDatePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns"); - } - - return this.allLongDatePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllShortTimePatterns - private String[] UnclonedShortTimePatterns - { - get - { - if (this.allShortTimePatterns == null) - { - this.allShortTimePatterns = _cultureData.ShortTimes; - Debug.Assert(this.allShortTimePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns"); - } - - return this.allShortTimePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllLongTimePatterns - private String[] UnclonedLongTimePatterns - { - get - { - if (this.allLongTimePatterns == null) - { - this.allLongTimePatterns = _cultureData.LongTimes; - Debug.Assert(this.allLongTimePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns"); - } - - return this.allLongTimePatterns; - } - } - - public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) - { - if (dtfi == null) - { - throw new ArgumentNullException(nameof(dtfi), - SR.ArgumentNull_Obj); - } - Contract.EndContractBlock(); - if (dtfi.IsReadOnly) - { - return (dtfi); - } - DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone()); - // We can use the data member calendar in the setter, instead of the property Calendar, - // since the cloned copy should have the same state as the original copy. - newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar); - newInfo._isReadOnly = true; - return (newInfo); - } - - public bool IsReadOnly - { - get - { - return (_isReadOnly); - } - } - - // Return the native name for the calendar in DTFI.Calendar. The native name is referred to - // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese. - // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar(); - // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar. - // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized); - // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar. - public string NativeCalendarName - { - get - { - return _cultureData.CalendarName(Calendar.ID); - } - } - - // - // Used by custom cultures and others to set the list of available formats. Note that none of them are - // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items - // from the list. - // - // Most of the format characters that can be used in GetAllDateTimePatterns are - // not really needed since they are one of the following: - // - // r/R/s/u locale-independent constants -- cannot be changed! - // m/M/y/Y fields with a single string in them -- that can be set through props directly - // f/F/g/G/U derived fields based on combinations of various of the below formats - // - // NOTE: No special validation is done here beyond what is done when the actual respective fields - // are used (what would be the point of disallowing here what we allow in the appropriate property?) - // - // WARNING: If more validation is ever done in one place, it should be done in the other. - // - public void SetAllDateTimePatterns(String[] patterns, char format) - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (patterns == null) - { - throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array); - } - - if (patterns.Length == 0) - { - throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns)); - } - - Contract.EndContractBlock(); - - for (int i=0; i<patterns.Length; i++) - { - if (patterns[i] == null) - { - throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue); - } - } - - // Remember the patterns, and use the 1st as default - switch (format) - { - case 'd': - allShortDatePatterns = patterns; - shortDatePattern = allShortDatePatterns[0]; - break; - - case 'D': - allLongDatePatterns = patterns; - longDatePattern = allLongDatePatterns[0]; - break; - - case 't': - allShortTimePatterns = patterns; - shortTimePattern = allShortTimePatterns[0]; - break; - - case 'T': - allLongTimePatterns = patterns; - longTimePattern = allLongTimePatterns[0]; - break; - - case 'y': - case 'Y': - allYearMonthPatterns = patterns; - yearMonthPattern = allYearMonthPatterns[0]; - break; - - default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); - } - - // Clear the token hash table, note that even short dates could require this - ClearTokenHashTable(); - } - - public String[] AbbreviatedMonthGenitiveNames - { - get - { - return ((String[])internalGetGenitiveMonthNames(true).Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - ClearTokenHashTable(); - this.m_genitiveAbbreviatedMonthNames = value; - } - } - - public String[] MonthGenitiveNames - { - get - { - return ((String[])internalGetGenitiveMonthNames(false).Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - genitiveMonthNames = value; - ClearTokenHashTable(); - } - } - - // - // Positive TimeSpan Pattern - // - [NonSerialized] - private string _fullTimeSpanPositivePattern; - internal String FullTimeSpanPositivePattern - { - get - { - if (_fullTimeSpanPositivePattern == null) - { - CultureData cultureDataWithoutUserOverrides; - if (_cultureData.UseUserOverride) - cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false); - else - cultureDataWithoutUserOverrides = _cultureData; - String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator; - - _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF"; - } - return _fullTimeSpanPositivePattern; - } - } - - // - // Negative TimeSpan Pattern - // - [NonSerialized] - private string _fullTimeSpanNegativePattern; - internal String FullTimeSpanNegativePattern - { - get - { - if (_fullTimeSpanNegativePattern == null) - _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern; - return _fullTimeSpanNegativePattern; - } - } - - // - // Get suitable CompareInfo from current DTFI object. - // - internal CompareInfo CompareInfo - { - get - { - if (_compareInfo == null) - { - // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the - // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData. - _compareInfo = CompareInfo.GetCompareInfo(_cultureData.SCOMPAREINFO); - } - - return _compareInfo; - } - } - - - internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite - | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault - | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal - | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind); - - internal static void ValidateStyles(DateTimeStyles style, String parameterName) - { - if ((style & InvalidDateTimeStyles) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName); - } - if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) - { - throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName); - } - Contract.EndContractBlock(); - if (((style & DateTimeStyles.RoundtripKind) != 0) - && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) - { - throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName); - } - } - - // - // Actions: Return the internal flag used in formatting and parsing. - // 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. - // - internal DateTimeFormatFlags FormatFlags - { - get - { - if (formatFlags == DateTimeFormatFlags.NotInitialized) - { - // Build the format flags from the data in this DTFI - formatFlags = DateTimeFormatFlags.None; - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth( - MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames( - MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID); - } - return (formatFlags); - } - } - - internal Boolean HasForceTwoDigitYears - { - get - { - switch (calendar.ID) - { - // Handle Japanese and Taiwan cases. - // If is y/yy, do not get (year % 100). "y" will print - // year without leading zero. "yy" will print year with two-digit in leading zero. - // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero. - // So year 5 is "05", and year 125 is "125". - // The reason for not doing (year % 100) is for Taiwan calendar. - // If year 125, then output 125 and not 25. - // Note: OS uses "yyyy" for Taiwan calendar by default. - case (CalendarId.JAPAN): - case (CalendarId.TAIWAN): - return true; - } - return false; - } - } - - // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar. - internal Boolean HasYearMonthAdjustment - { - get - { - return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0); - } - } - - // This is a callback that the parser can make back into the DTFI to let it fiddle with special - // cases associated with that culture or calendar. Currently this only has special cases for - // the Hebrew calendar, but this could be extended to other cultures. - // - // The return value is whether the year and month are actually valid for this calendar. - internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) - { - if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) - { - // Special rules to fix up the Hebrew year/month - - // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000. - // E.g. if the year is 5763, we only format as 763. - if (year < 1000) - { - year += 5000; - } - - // Because we need to calculate leap year, we should fall out now for an invalid year. - if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) - { - return false; - } - - // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers. - // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and - // shuffle the other months down. - if (parsedMonthName) - { - if (!Calendar.IsLeapYear(year)) - { - if (month >= 8) - { - month--; - } - else if (month == 7) - { - return false; - } - } - } - } - return true; - } - - // - // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens. - // - [NonSerialized] - private TokenHashValue[] _dtfiTokenHash; - - private const int TOKEN_HASH_SIZE = 199; - private const int SECOND_PRIME = 197; - private const String dateSeparatorOrTimeZoneOffset = "-"; - private const String invariantDateSeparator = "/"; - private const String invariantTimeSeparator = ":"; - - // - // Common Ignorable Symbols - // - internal const String IgnorablePeriod = "."; - internal const String IgnorableComma = ","; - - // - // Year/Month/Day suffixes - // - internal const String CJKYearSuff = "\u5e74"; - internal const String CJKMonthSuff = "\u6708"; - internal const String CJKDaySuff = "\u65e5"; - - internal const String KoreanYearSuff = "\ub144"; - internal const String KoreanMonthSuff = "\uc6d4"; - internal const String KoreanDaySuff = "\uc77c"; - - internal const String KoreanHourSuff = "\uc2dc"; - internal const String KoreanMinuteSuff = "\ubd84"; - internal const String KoreanSecondSuff = "\ucd08"; - - internal const String CJKHourSuff = "\u6642"; - internal const String ChineseHourSuff = "\u65f6"; - - internal const String CJKMinuteSuff = "\u5206"; - internal const String CJKSecondSuff = "\u79d2"; - - internal const String LocalTimeMark = "T"; - - internal const String GMTName = "GMT"; - internal const String ZuluName = "Z"; - - internal const String KoreanLangName = "ko"; - internal const String JapaneseLangName = "ja"; - internal const String EnglishLangName = "en"; - - private static volatile DateTimeFormatInfo s_jajpDTFI; - private static volatile DateTimeFormatInfo s_zhtwDTFI; - - // - // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse - // date string with Japanese era name correctly even when the supplied DTFI - // does not use Japanese calendar. - // The created instance is stored in global s_jajpDTFI. - // - internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() - { - DateTimeFormatInfo temp = s_jajpDTFI; - if (temp == null) - { - temp = new CultureInfo("ja-JP", false).DateTimeFormat; - temp.Calendar = JapaneseCalendar.GetDefaultInstance(); - s_jajpDTFI = temp; - } - return (temp); - } - - // Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse - // date string with era name correctly even when the supplied DTFI - // does not use Taiwan calendar. - // The created instance is stored in global s_zhtwDTFI. - internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() - { - DateTimeFormatInfo temp = s_zhtwDTFI; - if (temp == null) - { - temp = new CultureInfo("zh-TW", false).DateTimeFormat; - temp.Calendar = TaiwanCalendar.GetDefaultInstance(); - s_zhtwDTFI = temp; - } - return (temp); - } - - - // DTFI properties should call this when the setter are called. - private void ClearTokenHashTable() - { - _dtfiTokenHash = null; - formatFlags = DateTimeFormatFlags.NotInitialized; - } - - internal TokenHashValue[] CreateTokenHashTable() - { - TokenHashValue[] temp = _dtfiTokenHash; - if (temp == null) - { - temp = new TokenHashValue[TOKEN_HASH_SIZE]; - - bool koreanLanguage = LanguageName.Equals(KoreanLangName); - - string sep = this.TimeSeparator.Trim(); - if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0); - if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0); - - if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) - { - // - // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like - // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable - // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff - // instead of SEP_HourSuff. - // - InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0); - } - - InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - - // TODO: This ignores similar custom cultures - if (LanguageName.Equals("sq")) - { - // Albanian allows time formats like "12:00.PD" - InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - } - - // CJK suffix - InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0); - InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0); - InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0); - InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0); - InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0); - InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0); - - InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0); - InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0); - - // TODO: This ignores other custom cultures that might want to do something similar - if (koreanLanguage) - { - // Korean suffix - InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0); - InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0); - } - - if (LanguageName.Equals("ky")) - { - // For some cultures, the date separator works more like a comma, being allowed before or after any date part - InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0); - } - else - { - InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0); - } - - String[] dateWords = null; - DateTimeFormatInfoScanner scanner = null; - - // We need to rescan the date words since we're always synthetic - scanner = new DateTimeFormatInfoScanner(); - m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this); - // Ensure the formatflags is initialized. - DateTimeFormatFlags flag = FormatFlags; - - // For some cultures, the date separator works more like a comma, being allowed before or after any date part. - // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state. - // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol. - bool useDateSepAsIgnorableSymbol = false; - - String monthPostfix = null; - if (dateWords != null) - { - // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix. - // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix. - for (int i = 0; i < dateWords.Length; i++) - { - switch (dateWords[i][0]) - { - // This is a month postfix - case DateTimeFormatInfoScanner.MonthPostfixChar: - // Get the real month postfix. - monthPostfix = dateWords[i].Substring(1); - // Add the month name + postfix into the token. - AddMonthNames(temp, monthPostfix); - break; - case DateTimeFormatInfoScanner.IgnorableSymbolChar: - String symbol = dateWords[i].Substring(1); - InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0); - if (this.DateSeparator.Trim(null).Equals(symbol)) - { - // The date separator is the same as the ignorable symbol. - useDateSepAsIgnorableSymbol = true; - } - break; - default: - InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0); - // TODO: This ignores similar custom cultures - if (LanguageName.Equals("eu")) - { - // Basque has date words with leading dots - InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0); - } - break; - } - } - } - - if (!useDateSepAsIgnorableSymbol) - { - // Use the normal date separator. - InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0); - } - // Add the regular month names. - AddMonthNames(temp, null); - - // Add the abbreviated month names. - for (int i = 1; i <= 13; i++) - { - InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i); - } - - - if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) - { - for (int i = 1; i <= 13; i++) - { - String str; - str = internalGetMonthName(i, MonthNameStyles.Genitive, false); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) - { - for (int i = 1; i <= 13; i++) - { - String str; - str = internalGetMonthName(i, MonthNameStyles.LeapYear, false); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - for (int i = 0; i < 7; i++) - { - //String str = GetDayOfWeekNames()[i]; - // We have to call public methods here to work with inherited DTFI. - String str = GetDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - - str = GetAbbreviatedDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - } - - int[] eras = calendar.Eras; - for (int i = 1; i <= eras.Length; i++) - { - InsertHash(temp, GetEraName(i), TokenType.EraToken, i); - InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i); - } - - // TODO: This ignores other cultures that might want to do something similar - if (LanguageName.Equals(JapaneseLangName)) - { - // Japanese allows day of week forms like: "(Tue)" - for (int i = 0; i < 7; i++) - { - String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")"; - InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i); - } - if (this.Calendar.GetType() != typeof(JapaneseCalendar)) - { - // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar, - // we will check Japanese Era name as well when the calendar is Gregorian. - DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI(); - for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) - { - InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i); - InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i); - // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. - InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i); - } - } - } - // TODO: This prohibits similar custom cultures, but we hard coded the name - else if (CultureName.Equals("zh-TW")) - { - DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI(); - for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) - { - if (twDtfi.GetEraName(i).Length > 0) - { - InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i); - } - } - } - - InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - - // Add invariant month names and day names. - for (int i = 1; i <= 12; i++) - { - String str; - // We have to call public methods here to work with inherited DTFI. - // Insert the month name first, so that they are at the front of abbrevaited - // month names. - str = InvariantInfo.GetMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - str = InvariantInfo.GetAbbreviatedMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - } - - for (int i = 0; i < 7; i++) - { - // We have to call public methods here to work with inherited DTFI. - String str = InvariantInfo.GetDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - - str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - } - - for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) - { - // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. - InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1); - } - - InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0); - InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0); - InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0); - - InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0); - InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0); - - _dtfiTokenHash = temp; - } - return (temp); - } - - private void AddMonthNames(TokenHashValue[] temp, String monthPostfix) - { - for (int i = 1; i <= 13; i++) - { - String str; - //str = internalGetMonthName(i, MonthNameStyles.Regular, false); - // We have to call public methods here to work with inherited DTFI. - // Insert the month name first, so that they are at the front of abbrevaited - // month names. - str = GetMonthName(i); - if (str.Length > 0) - { - if (monthPostfix != null) - { - // Insert the month name with the postfix first, so it can be matched first. - InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i); - } - else - { - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - str = GetAbbreviatedMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - //////////////////////////////////////////////////////////////////////// - // - // Actions: - // Try to parse the current word to see if it is a Hebrew number. - // Tokens will be updated accordingly. - // This is called by the Lexer of DateTime.Parse(). - // - // Unlike most of the functions in this class, the return value indicates - // whether or not it started to parse. The badFormat parameter indicates - // if parsing began, but the format was bad. - // - //////////////////////////////////////////////////////////////////////// - - private static bool TryParseHebrewNumber( -#if INSIDE_CLR - ref __DTString str, -#else - ref FormatProvider.__DTString str, -#endif - out Boolean badFormat, - out int number) - { - number = -1; - badFormat = false; - - int i = str.Index; - if (!HebrewNumber.IsDigit(str.Value[i])) - { - // If the current character is not a Hebrew digit, just return false. - // There is no chance that we can parse a valid Hebrew number from here. - return (false); - } - // The current character is a Hebrew digit. Try to parse this word as a Hebrew number. - HebrewNumberParsingContext context = new HebrewNumberParsingContext(0); - HebrewNumberParsingState state; - - do - { - state = HebrewNumber.ParseByChar(str.Value[i++], ref context); - switch (state) - { - case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number. - case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character. - // Break out so that we don't continue to try parse this as a Hebrew number. - return (false); - } - } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)); - - // When we are here, we are either at the end of the string, or we find a valid Hebrew number. - Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber, - "Invalid returned state from HebrewNumber.ParseByChar()"); - - if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) - { - // We reach end of the string but we can't find a terminal state in parsing Hebrew number. - return (false); - } - - // We have found a valid Hebrew number. Update the index. - str.Advance(i - str.Index); - - // Get the final Hebrew number value from the HebrewNumberParsingContext. - number = context.result; - - return (true); - } - - private static bool IsHebrewChar(char ch) - { - return (ch >= '\x0590' && ch <= '\x05ff'); - } - - internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, -#if INSIDE_CLR - ref __DTString str) -#else - ref FormatProvider.__DTString str) -#endif - - { - tokenType = TokenType.UnknownToken; - tokenValue = 0; - - TokenHashValue value; - Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length"); - - char ch = str.m_current; - bool isLetter = Char.IsLetter(ch); - if (isLetter) - { - ch = this.Culture.TextInfo.ToLower(ch); - if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) - { - bool badFormat; - if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) - { - if (badFormat) - { - tokenType = TokenType.UnknownToken; - return (false); - } - // This is a Hebrew number. - // Do nothing here. TryParseHebrewNumber() will update token accordingly. - tokenType = TokenType.HebrewNumber; - return (true); - } - } - } - - - int hashcode = ch % TOKEN_HASH_SIZE; - int hashProbe = 1 + ch % SECOND_PRIME; - int remaining = str.len - str.Index; - int i = 0; - - TokenHashValue[] hashTable = _dtfiTokenHash; - if (hashTable == null) - { - hashTable = CreateTokenHashTable(); - } - do - { - value = hashTable[hashcode]; - if (value == null) - { - // Not found. - break; - } - // Check this value has the right category (regular token or separator token) that we are looking for. - if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) - { - bool compareStrings = true; - if (isLetter) - { - // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately. - // Also an optimization to avoid string comparison - int nextCharIndex = str.Index + value.tokenString.Length; - if (nextCharIndex > str.len) - { - compareStrings = false; - } - else if (nextCharIndex < str.len) - { - // Check word boundary. The next character should NOT be a letter. - char nextCh = str.Value[nextCharIndex]; - compareStrings = !(Char.IsLetter(nextCh)); - } - } - if (compareStrings && CompareStringIgnoreCaseOptimized(str.Value, str.Index, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length)) - { - tokenType = value.tokenType & TokenMask; - tokenValue = value.tokenValue; - str.Advance(value.tokenString.Length); - return (true); - } - else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) || - (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames)) - { - // For month or day token, we will match the names which have spaces. - int matchStrLen = 0; - if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) - { - tokenType = value.tokenType & TokenMask; - tokenValue = value.tokenValue; - str.Advance(matchStrLen); - return (true); - } - } - } - i++; - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - } while (i < TOKEN_HASH_SIZE); - - return (false); - } - - private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) - { - // Remember the current slot. - TokenHashValue previousNode = hashTable[hashcode]; - - //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert); - // Insert the new node into the current slot. - hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); ; - - while (++pos < TOKEN_HASH_SIZE) - { - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - // Remember this slot - TokenHashValue temp = hashTable[hashcode]; - - if (temp != null && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != ch) - { - continue; - } - // Put the previous slot into this slot. - hashTable[hashcode] = previousNode; - //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode); - if (temp == null) - { - // Done - return; - } - previousNode = temp; - }; - Debug.Assert(false, "The hashtable is full. This should not happen."); - } - - private void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) - { - // The month of the 13th month is allowed to be null, so make sure that we ignore null value here. - if (str == null || str.Length == 0) - { - return; - } - TokenHashValue value; - int i = 0; - // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by - // DateTime.Parse(). - if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) - { - str = str.Trim(null); // Trim white space characters. - // Could have space for separators - if (str.Length == 0) - return; - } - char ch = this.Culture.TextInfo.ToLower(str[0]); - int hashcode = ch % TOKEN_HASH_SIZE; - int hashProbe = 1 + ch % SECOND_PRIME; - do - { - value = hashTable[hashcode]; - if (value == null) - { - //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode); - hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); - return; - } - else - { - // Collision happens. Find another slot. - if (str.Length >= value.tokenString.Length) - { - // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of - // the shorter ones. - if (this.CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length)) - { - if (str.Length > value.tokenString.Length) - { - // The str to be inserted has the same prefix as the current token, and str is longer. - // Insert str into this node, and shift every node behind it. - InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe); - return; - } - else - { - // Same token. If they have different types (regular token vs separator token). Add them. - // If we have the same regular token or separator token in the hash already, do NOT update the hash. - // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash. - - - // - // 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. - // 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. - // - - int nTokenType = (int)tokenType; - int nCurrentTokenTypeInHash = (int)value.tokenType; - - // - // The folowing is the fix for the issue of throwing FormatException when "mar" is passed in string of the short date format dd/MMM/yyyy for es-MX - // - - if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) || - ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0)) - { - value.tokenType |= tokenType; - if (tokenValue != 0) - { - value.tokenValue = tokenValue; - } - } - // The token to be inserted is already in the table. Skip it. - return; - } - } - } - } - //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str); - i++; - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - } while (i < TOKEN_HASH_SIZE); - Debug.Assert(false, "The hashtable is full. This should not happen."); - } - - private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2) - { - // Optimize for one character cases which are common due to date and time separators (/ and :) - if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2]) - { - return true; - } - - return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0); - } - - // class DateTimeFormatInfo - - internal class TokenHashValue - { - internal String tokenString; - internal TokenType tokenType; - internal int tokenValue; - - internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) - { - this.tokenString = tokenString; - this.tokenType = tokenType; - this.tokenValue = tokenValue; - } - } - } - -#if !INSIDE_CLR - // - // The type of token that will be returned by DateTimeFormatInfo.Tokenize(). - // - internal enum TokenType - { - // The valid token should start from 1. - - // Regular tokens. The range is from 0x00 ~ 0xff. - NumberToken = 1, // The number. E.g. "12" - YearNumberToken = 2, // The number which is considered as year number, which has 3 or more digits. E.g. "2003" - Am = 3, // AM timemark. E.g. "AM" - Pm = 4, // PM timemark. E.g. "PM" - MonthToken = 5, // A word (or words) that represents a month name. E.g. "March" - EndOfString = 6, // End of string - DayOfWeekToken = 7, // A word (or words) that represents a day of week name. E.g. "Monday" or "Mon" - TimeZoneToken = 8, // A word that represents a timezone name. E.g. "GMT" - EraToken = 9, // A word that represents a era name. E.g. "A.D." - DateWordToken = 10, // A word that can appear in a DateTime string, but serves no parsing semantics. E.g. "de" in Spanish culture. - UnknownToken = 11, // An unknown word, which signals an error in parsing. - HebrewNumber = 12, // A number that is composed of Hebrew text. Hebrew calendar uses Hebrew digits for year values, month values, and day values. - JapaneseEraToken = 13, // Era name for JapaneseCalendar - TEraToken = 14, // Era name for TaiwanCalendar - IgnorableSymbol = 15, // A separator like "," that is equivalent to whitespace - - - // Separator tokens. - SEP_Unk = 0x100, // Unknown separator. - SEP_End = 0x200, // The end of the parsing string. - SEP_Space = 0x300, // Whitespace (including comma). - SEP_Am = 0x400, // AM timemark. E.g. "AM" - SEP_Pm = 0x500, // PM timemark. E.g. "PM" - SEP_Date = 0x600, // date separator. E.g. "/" - SEP_Time = 0x700, // time separator. E.g. ":" - SEP_YearSuff = 0x800, // Chinese/Japanese/Korean year suffix. - SEP_MonthSuff = 0x900, // Chinese/Japanese/Korean month suffix. - SEP_DaySuff = 0xa00, // Chinese/Japanese/Korean day suffix. - SEP_HourSuff = 0xb00, // Chinese/Japanese/Korean hour suffix. - SEP_MinuteSuff = 0xc00, // Chinese/Japanese/Korean minute suffix. - SEP_SecondSuff = 0xd00, // Chinese/Japanese/Korean second suffix. - SEP_LocalTimeMark = 0xe00, // 'T', used in ISO 8601 format. - SEP_DateOrOffset = 0xf00, // '-' which could be a date separator or start of a time zone offset - - RegularTokenMask = 0x00ff, - SeparatorTokenMask = 0xff00, - } -#endif - -} |