summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs')
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs2936
1 files changed, 2936 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs
new file mode 100644
index 0000000000..00c2d1f439
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs
@@ -0,0 +1,2936 @@
+// 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.
+
+
+namespace System.Globalization {
+ using System;
+ using System.Security;
+ using System.Threading;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+ using System.Security.Permissions;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Text;
+ using System.Diagnostics.Contracts;
+
+ //
+ // 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]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public sealed class DateTimeFormatInfo : ICloneable, IFormatProvider
+ {
+ //
+ // Note, some fields are derived so don't really need to be serialized, but we can't mark as
+ // optional because Whidbey was expecting them. Post-Arrowhead we could fix that
+ // once Whidbey serialization is no longer necessary.
+ //
+
+ // cache for the invariant culture.
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile DateTimeFormatInfo invariantInfo;
+
+ // an index which points to a record in Culture Data Table.
+ [NonSerialized]private CultureData m_cultureData;
+
+ // The culture name used to create this DTFI.
+ [OptionalField(VersionAdded = 2)]
+ internal String m_name = null;
+
+ // The language name of the culture used to create this DTFI.
+ [NonSerialized]private String m_langName = null;
+
+ // CompareInfo usually used by the parser.
+ [NonSerialized]private CompareInfo m_compareInfo = null;
+
+ // Culture matches current DTFI. mainly used for string comparisons during parsing.
+ [NonSerialized]private CultureInfo m_cultureInfo = null;
+
+ //
+ // Caches for various properties.
+ //
+
+ internal String amDesignator = null;
+ internal String pmDesignator = null;
+ [OptionalField(VersionAdded = 1)]
+ internal String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
+ internal String monthDayPattern = null;
+ [OptionalField(VersionAdded = 2)] // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
+ internal String dateTimeOffsetPattern = null;
+
+ //
+ // The following are constant values.
+ //
+ internal const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
+
+ // The sortable pattern is based on ISO 8601.
+ internal const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
+ internal const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
+
+ //
+ // The following are affected by calendar settings.
+ //
+ internal Calendar calendar = null;
+
+ internal int firstDayOfWeek = -1;
+ internal int calendarWeekRule = -1;
+
+ [OptionalField(VersionAdded = 1)]
+ internal String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
+
+ internal String[] abbreviatedDayNames = null;
+
+ [OptionalField(VersionAdded = 2)]
+ internal String[] m_superShortDayNames = null;
+
+ internal String[] dayNames = null;
+ internal String[] abbreviatedMonthNames = null;
+ internal String[] monthNames = null;
+ // Cache the genitive month names that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal String[] genitiveMonthNames = null;
+
+ // Cache the abbreviated genitive month names that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal String[] m_genitiveAbbreviatedMonthNames = null;
+
+ // Cache the month names of a leap year that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal 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
+ internal String longDatePattern = null;
+ internal String shortDatePattern = null;
+ internal String yearMonthPattern = null;
+ internal String longTimePattern = null;
+ internal String shortTimePattern = null;
+
+ // These are Whidbey-serialization compatable arrays (eg: default not included)
+ // "all" is a bit of a misnomer since the "default" pattern stored above isn't
+ // necessarily a member of the list
+ [OptionalField(VersionAdded = 3)]
+ private String[] allYearMonthPatterns = null; // This was wasn't serialized in Whidbey
+ internal String[] allShortDatePatterns = null;
+ internal String[] allLongDatePatterns = null;
+ internal String[] allShortTimePatterns = null;
+ internal String[] allLongTimePatterns = null;
+
+ // Cache the era names for this DateTimeFormatInfo instance.
+ internal String[] m_eraNames = null;
+ internal String[] m_abbrevEraNames = null;
+ internal String[] m_abbrevEnglishEraNames = null;
+
+ internal int[] optionalCalendars = null;
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ // CultureInfo updates this
+ internal bool m_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.
+ [OptionalField(VersionAdded = 2)]
+ internal DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
+ internal static bool preferExistingTokens = InitPreferExistingTokens();
+
+
+ [System.Security.SecuritySafeCritical]
+ static bool InitPreferExistingTokens()
+ {
+ bool ret = false;
+#if !FEATURE_CORECLR
+ ret = DateTime.LegacyParseMode();
+#endif
+ return ret;
+ }
+
+ private String CultureName
+ {
+ get
+ {
+ if (m_name == null)
+ {
+ m_name = this.m_cultureData.CultureName;
+ }
+ return (m_name);
+ }
+ }
+
+ private CultureInfo Culture
+ {
+ get
+ {
+ if (m_cultureInfo == null)
+ {
+ m_cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
+ }
+ return m_cultureInfo;
+ }
+ }
+
+ private String LanguageName
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (m_langName == null)
+ {
+ m_langName = this.m_cultureData.SISO639LANGNAME;
+ }
+ return (m_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 = this.m_cultureData.AbbreviatedDayNames(Calendar.ID);
+ Contract.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 = this.m_cultureData.SuperShortDayNames(Calendar.ID);
+ Contract.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 = this.m_cultureData.DayNames(Calendar.ID);
+ Contract.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 = this.m_cultureData.AbbreviatedMonthNames(Calendar.ID);
+ Contract.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 = this.m_cultureData.MonthNames(Calendar.ID);
+ Contract.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)
+ {
+ Contract.Requires(cultureData != null);
+ Contract.Requires(cal != null);
+
+ // Remember our culture
+ this.m_cultureData = cultureData;
+
+ // m_isDefaultCalendar is set in the setter of Calendar below.
+ this.Calendar = cal;
+ }
+
+#if !FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical]
+#endif
+ private void InitializeOverridableProperties(CultureData cultureData, int calendarID)
+ {
+ // Silverlight 2.0 never took a snapshot of the user's overridable properties
+ // This has a substantial performance impact so skip when CoreCLR
+ Contract.Requires(cultureData != null);
+ Contract.Assert(calendarID > 0, "[DateTimeFormatInfo.Populate] Expected Calendar.ID > 0");
+
+ 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 = this.m_cultureData.LongTimes;
+ Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
+
+ this.allShortTimePatterns = this.m_cultureData.ShortTimes;
+ Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
+
+ this.allLongDatePatterns = cultureData.LongDates(calendarID);
+ Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
+
+ this.allShortDatePatterns = cultureData.ShortDates(calendarID);
+ Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
+
+ this.allYearMonthPatterns = cultureData.YearMonths(calendarID);
+ Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
+ }
+
+#region Serialization
+ // The following fields are defined to keep the serialization compatibility with .NET V1.0/V1.1.
+ [OptionalField(VersionAdded = 1)]
+ private int CultureID;
+ [OptionalField(VersionAdded = 1)]
+ private bool m_useUserOverride;
+ [OptionalField(VersionAdded = 1)]
+ private bool bUseCalendarInfo;
+ [OptionalField(VersionAdded = 1)]
+ private int nDataItem;
+ [OptionalField(VersionAdded = 2)]
+ internal bool m_isDefaultCalendar; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
+ [OptionalField(VersionAdded = 2)]
+ private static volatile Hashtable s_calendarNativeNames; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
+
+ // 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
+ [OptionalField(VersionAdded = 1)]
+ internal String[] m_dateWords = null; // calculated, no need to serialze (whidbey expects, arrowhead doesn't)
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ if (this.m_name != null)
+ {
+ m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
+
+ if (this.m_cultureData == null)
+ throw new CultureNotFoundException(
+ "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
+ }
+ // Note: This is for Everett compatibility
+
+#if FEATURE_USE_LCID
+ else
+ m_cultureData = CultureData.GetCultureData(CultureID, m_useUserOverride);
+#endif
+ if (calendar == null)
+ {
+ calendar = (Calendar) GregorianCalendar.GetDefaultInstance().Clone();
+ calendar.SetReadOnlyState(m_isReadOnly);
+ }
+ else
+ {
+ CultureInfo.CheckDomainSafetyObject(calendar, this);
+ }
+ InitializeOverridableProperties(m_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 = m_isReadOnly;
+ m_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;
+
+ m_isReadOnly = isReadOnly;
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+#if FEATURE_USE_LCID
+ CultureID = this.m_cultureData.ILANGUAGE; // Used for serialization compatibility with Whidbey which didn't always serialize the name
+#endif
+ m_useUserOverride = this.m_cultureData.UseUserOverride;
+
+ // make sure the m_name is initialized.
+ m_name = this.CultureName;
+
+#if !FEATURE_CORECLR
+ if (s_calendarNativeNames == null)
+ s_calendarNativeNames = new Hashtable();
+#endif // FEATURE_CORECLR
+
+ // 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;
+ }
+#endregion Serialization
+
+ // 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 (invariantInfo == null)
+ {
+ DateTimeFormatInfo info = new DateTimeFormatInfo();
+ info.Calendar.SetReadOnlyState(true);
+ info.m_isReadOnly = true;
+ invariantInfo = info;
+ }
+ return (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.Threading.Thread.CurrentThread.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.m_isReadOnly = false;
+ return n;
+ }
+
+
+ public String AMDesignator
+ {
+#if FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical] // auto-generated
+#endif
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.amDesignator == null)
+ {
+ this.amDesignator = this.m_cultureData.SAM1159;
+ }
+#endif
+ Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
+ return (this.amDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ amDesignator = value;
+ }
+ }
+
+
+ public Calendar Calendar {
+ get {
+ Contract.Ensures(Contract.Result<Calendar>() != null);
+
+ Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
+ return (this.calendar);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ if (value == calendar) {
+ return;
+ }
+
+ //
+ // Because the culture is agile object which can be attached to a thread and then thread can travel
+ // to another app domain then we prevent attaching any customized object to culture that we cannot contol.
+ //
+ CultureInfo.CheckDomainSafetyObject(value, this);
+
+ 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
+
+ // We don't need to clear these because they're only used for whidbey compat serialization
+ // the only values we use are the all...Patterns[0]
+ // longDatePattern
+ // shortDatePattern
+ // yearMonthPattern
+
+ // remember to reload tokens
+ ClearTokenHashTable();
+ }
+
+ // Remember the new calendar
+ calendar = value;
+ InitializeOverridableProperties(m_cultureData, calendar.ID);
+
+ // We succeeded, return
+ return;
+ }
+ }
+
+ // The assigned calendar is not a valid calendar for this culture, throw
+ throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("Argument_InvalidCalendar"));
+ }
+ }
+
+ private int[] OptionalCalendars {
+ get {
+ if (this.optionalCalendars == null) {
+ this.optionalCalendars = this.m_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("eraName",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // For Geo-Political reasons, 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 (String.Compare(eraName, m_eraNames[i], this.Culture, 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 (String.Compare(eraName, m_abbrevEraNames[i], this.Culture, 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 (String.Compare(eraName, m_abbrevEnglishEraNames[i], StringComparison.InvariantCultureIgnoreCase)==0) {
+ return (i+1);
+ }
+ }
+ return (-1);
+ }
+
+ internal String[] EraNames
+ {
+ get
+ {
+ if (this.m_eraNames == null)
+ {
+ this.m_eraNames = this.m_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("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal String[] AbbreviatedEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEraNames == null)
+ {
+ this.m_abbrevEraNames = this.m_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("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal String[] AbbreviatedEnglishEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEnglishEraNames == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
+ this.m_abbrevEnglishEraNames = this.m_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 FEATURE_CORECLR
+ if (this.dateSeparator == null)
+ {
+ this.dateSeparator = this.m_cultureData.DateSeparator(Calendar.ID);
+ }
+#endif
+ Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
+ return (this.dateSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ this.dateSeparator = value;
+ }
+ }
+
+
+ public DayOfWeek FirstDayOfWeek
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.firstDayOfWeek == -1)
+ {
+ this.firstDayOfWeek = this.m_cultureData.IFIRSTDAYOFWEEK;
+ }
+#endif
+ Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
+
+ return ((DayOfWeek)this.firstDayOfWeek);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) {
+ firstDayOfWeek = (int)value;
+ } else {
+ throw new ArgumentOutOfRangeException(
+ "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ }
+ }
+
+
+ public CalendarWeekRule CalendarWeekRule
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.calendarWeekRule == -1)
+ {
+ this.calendarWeekRule = this.m_cultureData.IFIRSTWEEKOFYEAR;
+ }
+#endif
+ Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
+ return ((CalendarWeekRule)this.calendarWeekRule);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) {
+ calendarWeekRule = (int)value;
+ } else {
+ throw new ArgumentOutOfRangeException(
+ "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+ }
+ }
+ }
+
+
+
+ public String FullDateTimePattern
+ {
+ get
+ {
+ if (fullDateTimePattern == null)
+ {
+ fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
+ }
+ return (fullDateTimePattern);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
+ this.monthDayPattern = this.m_cultureData.MonthDay(Calendar.ID);
+ }
+ Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
+ return (this.monthDayPattern);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ this.monthDayPattern = value;
+ }
+ }
+
+
+ public String PMDesignator
+ {
+#if FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical] // auto-generated
+#endif
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.pmDesignator == null)
+ {
+ this.pmDesignator = this.m_cultureData.SPM2359;
+ }
+#endif
+ Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
+ return (this.pmDesignator);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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 FEATURE_CORECLR
+ if (timeSeparator == null)
+ {
+ timeSeparator = this.m_cultureData.TimeSeparator;
+ }
+#endif
+ Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
+ return (timeSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("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"
+ //
+ static private void CheckNullValue(String[] values, int length) {
+ Contract.Requires(values != null, "value != null");
+ Contract.Requires(values.Length >= length);
+ for (int i = 0; i < length; i++) {
+ if (values[i] == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_ArrayValue"));
+ }
+ }
+ }
+
+
+ public String[] AbbreviatedDayNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ abbreviatedDayNames = value;
+ }
+ }
+
+
+ // Returns the string array of the one-letter day of week names.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] ShortestDayNames
+ {
+ get
+ {
+ return ((String[])internalGetSuperShortDayNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ dayNames = value;
+ }
+ }
+
+
+ public String[] AbbreviatedMonthNames {
+ get {
+ return ((String[])internalGetAbbreviatedMonthNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "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(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "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.
+ static char[] 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(
+ "month", Environment.GetResourceString("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 = this.m_cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
+ Contract.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 = this.m_cultureData.GenitiveMonthNames(this.Calendar.ID);
+ Contract.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)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
+ this.leapYearMonthNames = this.m_cultureData.LeapYearMonthNames(Calendar.ID);
+ Contract.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(
+ "dayofweek", Environment.GetResourceString("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.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String GetShortestDayName(DayOfWeek dayOfWeek)
+ {
+
+ if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "dayOfWeek", Environment.GetResourceString("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
+ static private String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
+ {
+ Contract.Requires(patterns1 != null);
+ Contract.Requires(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[] {DateTimeFormat.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(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
+ }
+ return (result);
+ }
+
+
+ public String GetDayName(DayOfWeek dayofweek)
+ {
+ if ((int)dayofweek < 0 || (int)dayofweek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "dayofweek", Environment.GetResourceString("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(
+ "month", Environment.GetResourceString("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(
+ "month", Environment.GetResourceString("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)
+ {
+ Contract.Assert(patterns != null && patterns.Length > 0,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
+ Contract.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;
+ }
+
+ // 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)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
+ this.allYearMonthPatterns = this.m_cultureData.YearMonths(this.Calendar.ID);
+ Contract.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)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
+ this.allShortDatePatterns = this.m_cultureData.ShortDates(this.Calendar.ID);
+ Contract.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)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
+ this.allLongDatePatterns = this.m_cultureData.LongDates(this.Calendar.ID);
+ Contract.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 = this.m_cultureData.ShortTimes;
+ Contract.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 = this.m_cultureData.LongTimes;
+ Contract.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("dtfi",
+ Environment.GetResourceString("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.m_isReadOnly = true;
+ return (newInfo);
+ }
+
+
+ public bool IsReadOnly {
+ get {
+ return (m_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.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String NativeCalendarName
+ {
+ get
+ {
+ return m_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.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public void SetAllDateTimePatterns(String[] patterns, char format)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (patterns == null) {
+ throw new ArgumentNullException("patterns",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+
+ if (patterns.Length == 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Arg_ArrayZeroError"), "patterns");
+ }
+ Contract.EndContractBlock();
+
+ for (int i=0; i<patterns.Length; i++)
+ {
+ if (patterns[i] == null)
+ {
+ throw new ArgumentNullException("patterns[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayValue"));
+ }
+ }
+
+ // Remember the patterns, and use the 1st as default
+ switch (format)
+ {
+ case 'd':
+ this.allShortDatePatterns = patterns;
+ this.shortDatePattern = this.allShortDatePatterns[0];
+ break;
+
+ case 'D':
+ this.allLongDatePatterns = patterns;
+ this.longDatePattern = this.allLongDatePatterns[0];
+ break;
+
+ case 't':
+ this.allShortTimePatterns = patterns;
+ this.shortTimePattern = this.allShortTimePatterns[0];
+ break;
+
+ case 'T':
+ this.allLongTimePatterns = patterns;
+ this.longTimePattern = this.allLongTimePatterns[0];
+ break;
+
+ case 'y':
+ case 'Y':
+ this.allYearMonthPatterns = patterns;
+ this.yearMonthPattern = this.allYearMonthPatterns[0];
+ break;
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
+ }
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+
+ return;
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] AbbreviatedMonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(true).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ this.m_genitiveAbbreviatedMonthNames= value;
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] MonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(false).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ genitiveMonthNames= value;
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Positive TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string m_fullTimeSpanPositivePattern;
+ internal String FullTimeSpanPositivePattern
+ {
+ get
+ {
+ if (m_fullTimeSpanPositivePattern == null)
+ {
+ CultureData cultureDataWithoutUserOverrides;
+ if (m_cultureData.UseUserOverride)
+ cultureDataWithoutUserOverrides = CultureData.GetCultureData(m_cultureData.CultureName, false);
+ else
+ cultureDataWithoutUserOverrides = m_cultureData;
+ String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
+
+ m_fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
+ }
+ return m_fullTimeSpanPositivePattern;
+ }
+ }
+
+ //
+ // Negative TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string m_fullTimeSpanNegativePattern;
+ internal String FullTimeSpanNegativePattern
+ {
+ get
+ {
+ if (m_fullTimeSpanNegativePattern == null)
+ m_fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
+ return m_fullTimeSpanNegativePattern;
+ }
+ }
+
+ //
+ // Get suitable CompareInfo from current DTFI object.
+ //
+ internal CompareInfo CompareInfo
+ {
+ get
+ {
+ if (m_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 m_cultureData.
+ m_compareInfo = CompareInfo.GetCompareInfo(m_cultureData.SCOMPAREINFO);
+ }
+
+ return m_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(Environment.GetResourceString("Argument_InvalidDateTimeStyles"), parameterName);
+ }
+ if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeStyles"), parameterName);
+ }
+ Contract.EndContractBlock();
+ if (((style & DateTimeStyles.RoundtripKind) != 0)
+ && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) {
+ throw new ArgumentException(Environment.GetResourceString("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)
+ {
+ // 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 (Calendar.CAL_JAPAN):
+ case (Calendar.CAL_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]
+ TokenHashValue[] m_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 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);
+ }
+ 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()
+ {
+ m_dtfiTokenHash = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal TokenHashValue[] CreateTokenHashTable() {
+ TokenHashValue[] temp = m_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);
+
+ 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);
+
+ 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();
+ // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
+ // The only reason they're being assigned to m_dateWords is for Whidbey Deserialization
+ 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 ingorable symbol.
+ useDateSepAsIgnorableSymbol = true;
+ }
+ break;
+ default:
+ InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
+ 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);
+ }
+
+ 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);
+ }
+ }
+ }
+ 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, DateTimeParse.GMTName, TokenType.TimeZoneToken, 0);
+ InsertHash(temp, DateTimeParse.ZuluName, TokenType.TimeZoneToken, 0);
+
+ InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
+ InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
+
+ m_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(
+ ref __DTString str,
+ 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.
+ Contract.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');
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, ref __DTString str) {
+ tokenType = TokenType.UnknownToken;
+ tokenValue = 0;
+
+ TokenHashValue value;
+ Contract.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
+
+ char ch = str.m_current;
+ bool isLetter = Char.IsLetter(ch);
+ if (isLetter) {
+ ch = Char.ToLower(ch, this.Culture);
+ 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 = m_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) {
+ if (String.Compare(str.Value, str.Index, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase)==0) {
+ 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.
+ int nextCharIndex;
+ if ((nextCharIndex = str.Index + value.tokenString.Length) < str.len) {
+ // Check word boundary. The next character should NOT be a letter.
+ char nextCh = str.Value[nextCharIndex];
+ if (Char.IsLetter(nextCh)) {
+ return (false);
+ }
+ }
+ }
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(value.tokenString.Length);
+ return (true);
+ } else if (value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) {
+ // For month token, we will match the month 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);
+ }
+ } else if (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames) {
+ // For month token, we will match the month 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);
+ }
+
+ 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 && Char.ToLower(temp.tokenString[0], this.Culture) != 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;
+ } ;
+ Contract.Assert(false, "The hashtable is full. This should not happen.");
+ }
+
+ 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 = Char.ToLower(str[0], this.Culture);
+ 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 (String.Compare(str, 0, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase) == 0) {
+ 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 idea behind this check is:
+ // - if the app is targetting 4.5.1 or above OR the compat flag is set, use the correct behavior by default.
+ // - if the app is targetting 4.5 or below AND the compat switch is set, use the correct behavior
+ // - if the app is targetting 4.5 or below AND the compat switch is NOT set, use the incorrect behavior
+ if (preferExistingTokens || BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
+ {
+ 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;
+ }
+ }
+ }
+ else
+ {
+ // The following logic is incorrect and causes updates to happen depending on the bitwise relationship between the existing token type and the
+ // the stored token type. It was this way in .NET 4 RTM. The behavior above is correct and will be adopted going forward.
+
+ if ((((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.RegularTokenMask) == nTokenType) ||
+ (((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.SeparatorTokenMask) == nTokenType))
+ {
+ value.tokenType |= tokenType;
+ if (tokenValue != 0)
+ {
+ value.tokenValue = tokenValue;
+ }
+ }
+ }
+ // The token to be inserted is already in the table. Skip it.
+ }
+ }
+ }
+ }
+ //// 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);
+ Contract.Assert(false, "The hashtable is full. This should not happen.");
+ }
+ } // 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;
+ }
+ }
+}