From 4aa3ab866ad65ae493e298d31237f583349d8637 Mon Sep 17 00:00:00 2001 From: Alex Perovich Date: Mon, 27 Mar 2017 15:44:34 -0700 Subject: [PATCH] Move deleted files and remove m_ prefix in shared file Commit migrated from https://github.com/dotnet/coreclr/commit/b15749642d928dad5652487a759bf0db4d494502 --- .../src/mscorlib/System.Private.CoreLib.csproj | 4 - .../System/Globalization/DateTimeFormatInfo.cs | 6 +- .../src/System/Globalization/DateTimeFormat.cs | 1211 ----- .../src/System/Globalization/DateTimeFormatInfo.cs | 3085 ----------- .../Globalization/DateTimeFormatInfoScanner.cs | 742 --- .../src/System/Globalization/DateTimeParse.cs | 5694 -------------------- 6 files changed, 3 insertions(+), 10739 deletions(-) delete mode 100644 src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormat.cs delete mode 100644 src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs delete mode 100644 src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs delete mode 100644 src/coreclr/src/mscorlib/src/System/Globalization/DateTimeParse.cs diff --git a/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj b/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj index dba0fbd..bba1ff4 100644 --- a/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/mscorlib/System.Private.CoreLib.csproj @@ -525,10 +525,6 @@ - - - - diff --git a/src/coreclr/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/coreclr/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs index 4e24381..d3f1ea9 100644 --- a/src/coreclr/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs +++ b/src/coreclr/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs @@ -311,7 +311,7 @@ namespace System.Globalization // Invariant DateTimeFormatInfo doesn't have user-overriden values // Default calendar is gregorian public DateTimeFormatInfo() - : this(CultureInfo.InvariantCulture.m_cultureData, GregorianCalendar.GetDefaultInstance()) + : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance()) { } @@ -452,7 +452,7 @@ namespace System.Globalization { Contract.Ensures(Contract.Result() != null); System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture; - if (!culture.m_isInherited) + if (!culture._isInherited) { DateTimeFormatInfo info = culture.dateTimeInfo; if (info != null) @@ -470,7 +470,7 @@ namespace System.Globalization // Fast case for a regular CultureInfo DateTimeFormatInfo info; CultureInfo cultureProvider = provider as CultureInfo; - if (cultureProvider != null && !cultureProvider.m_isInherited) + if (cultureProvider != null && !cultureProvider._isInherited) { return cultureProvider.DateTimeFormat; } diff --git a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormat.cs b/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormat.cs deleted file mode 100644 index e34ca35..0000000 --- a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormat.cs +++ /dev/null @@ -1,1211 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text; -using System.Threading; -using System.Globalization; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security; -using System.Diagnostics; -using System.Diagnostics.Contracts; - -namespace System -{ - /* - Customized format patterns: - P.S. Format in the table below is the internal number format used to display the pattern. - - Patterns Format Description Example - ========= ========== ===================================== ======== - "h" "0" hour (12-hour clock)w/o leading zero 3 - "hh" "00" hour (12-hour clock)with leading zero 03 - "hh*" "00" hour (12-hour clock)with leading zero 03 - - "H" "0" hour (24-hour clock)w/o leading zero 8 - "HH" "00" hour (24-hour clock)with leading zero 08 - "HH*" "00" hour (24-hour clock) 08 - - "m" "0" minute w/o leading zero - "mm" "00" minute with leading zero - "mm*" "00" minute with leading zero - - "s" "0" second w/o leading zero - "ss" "00" second with leading zero - "ss*" "00" second with leading zero - - "f" "0" second fraction (1 digit) - "ff" "00" second fraction (2 digit) - "fff" "000" second fraction (3 digit) - "ffff" "0000" second fraction (4 digit) - "fffff" "00000" second fraction (5 digit) - "ffffff" "000000" second fraction (6 digit) - "fffffff" "0000000" second fraction (7 digit) - - "F" "0" second fraction (up to 1 digit) - "FF" "00" second fraction (up to 2 digit) - "FFF" "000" second fraction (up to 3 digit) - "FFFF" "0000" second fraction (up to 4 digit) - "FFFFF" "00000" second fraction (up to 5 digit) - "FFFFFF" "000000" second fraction (up to 6 digit) - "FFFFFFF" "0000000" second fraction (up to 7 digit) - - "t" first character of AM/PM designator A - "tt" AM/PM designator AM - "tt*" AM/PM designator PM - - "d" "0" day w/o leading zero 1 - "dd" "00" day with leading zero 01 - "ddd" short weekday name (abbreviation) Mon - "dddd" full weekday name Monday - "dddd*" full weekday name Monday - - - "M" "0" month w/o leading zero 2 - "MM" "00" month with leading zero 02 - "MMM" short month name (abbreviation) Feb - "MMMM" full month name Febuary - "MMMM*" full month name Febuary - - "y" "0" two digit year (year % 100) w/o leading zero 0 - "yy" "00" two digit year (year % 100) with leading zero 00 - "yyy" "D3" year 2000 - "yyyy" "D4" year 2000 - "yyyyy" "D5" year 2000 - ... - - "z" "+0;-0" timezone offset w/o leading zero -8 - "zz" "+00;-00" timezone offset with leading zero -08 - "zzz" "+00;-00" for hour offset, "00" for minute offset full timezone offset -07:30 - "zzz*" "+00;-00" for hour offset, "00" for minute offset full timezone offset -08:00 - - "K" -Local "zzz", e.g. -08:00 - -Utc "'Z'", representing UTC - -Unspecified "" - -DateTimeOffset "zzzzz" e.g -07:30:15 - - "g*" the current era name A.D. - - ":" time separator : -- DEPRECATED - Insert separator directly into pattern (eg: "H.mm.ss") - "/" date separator /-- DEPRECATED - Insert separator directly into pattern (eg: "M-dd-yyyy") - "'" quoted string 'ABC' will insert ABC into the formatted string. - '"' quoted string "ABC" will insert ABC into the formatted string. - "%" used to quote a single pattern characters E.g.The format character "%y" is to print two digit year. - "\" escaped character E.g. '\d' insert the character 'd' into the format string. - other characters insert the character into the format string. - - Pre-defined format characters: - (U) to indicate Universal time is used. - (G) to indicate Gregorian calendar is used. - - Format Description Real format Example - ========= ================================= ====================== ======================= - "d" short date culture-specific 10/31/1999 - "D" long data culture-specific Sunday, October 31, 1999 - "f" full date (long date + short time) culture-specific Sunday, October 31, 1999 2:00 AM - "F" full date (long date + long time) culture-specific Sunday, October 31, 1999 2:00:00 AM - "g" general date (short date + short time) culture-specific 10/31/1999 2:00 AM - "G" general date (short date + long time) culture-specific 10/31/1999 2:00:00 AM - "m"/"M" Month/Day date culture-specific October 31 -(G) "o"/"O" Round Trip XML "yyyy-MM-ddTHH:mm:ss.fffffffK" 1999-10-31 02:00:00.0000000Z -(G) "r"/"R" RFC 1123 date, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'" Sun, 31 Oct 1999 10:00:00 GMT -(G) "s" Sortable format, based on ISO 8601. "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00 - ('T' for local time) - "t" short time culture-specific 2:00 AM - "T" long time culture-specific 2:00:00 AM -(G) "u" Universal time with sortable format, "yyyy'-'MM'-'dd HH':'mm':'ss'Z'" 1999-10-31 10:00:00Z - based on ISO 8601. -(U) "U" Universal time with full culture-specific Sunday, October 31, 1999 10:00:00 AM - (long date + long time) format - "y"/"Y" Year/Month day culture-specific October, 1999 - - */ - - //This class contains only static members and does not require the serializable attribute. - internal static - class DateTimeFormat - { - internal const int MaxSecondsFractionDigits = 7; - internal static readonly TimeSpan NullOffset = TimeSpan.MinValue; - - internal static char[] allStandardFormats = - { - 'd', 'D', 'f', 'F', 'g', 'G', - 'm', 'M', 'o', 'O', 'r', 'R', - 's', 't', 'T', 'u', 'U', 'y', 'Y', - }; - - internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; - internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; - - private const int DEFAULT_ALL_DATETIMES_SIZE = 132; - - internal static readonly DateTimeFormatInfo InvariantFormatInfo = CultureInfo.InvariantCulture.DateTimeFormat; - internal static readonly string[] InvariantAbbreviatedMonthNames = InvariantFormatInfo.AbbreviatedMonthNames; - internal static readonly string[] InvariantAbbreviatedDayNames = InvariantFormatInfo.AbbreviatedDayNames; - internal const string Gmt = "GMT"; - - internal static String[] fixedNumberFormats = new String[] { - "0", - "00", - "000", - "0000", - "00000", - "000000", - "0000000", - }; - - //////////////////////////////////////////////////////////////////////////// - // - // Format the positive integer value to a string and perfix with assigned - // length of leading zero. - // - // Parameters: - // value: The value to format - // len: The maximum length for leading zero. - // If the digits of the value is greater than len, no leading zero is added. - // - // Notes: - // The function can format to Int32.MaxValue. - // - //////////////////////////////////////////////////////////////////////////// - internal static void FormatDigits(StringBuilder outputBuffer, int value, int len) - { - Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0"); - FormatDigits(outputBuffer, value, len, false); - } - - internal unsafe static void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit) - { - Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0"); - - // Limit the use of this function to be two-digits, so that we have the same behavior - // as RTM bits. - if (!overrideLengthLimit && len > 2) - { - len = 2; - } - - char* buffer = stackalloc char[16]; - char* p = buffer + 16; - int n = value; - do - { - *--p = (char)(n % 10 + '0'); - n /= 10; - } while ((n != 0) && (p > buffer)); - - int digits = (int)(buffer + 16 - p); - - //If the repeat count is greater than 0, we're trying - //to emulate the "00" format, so we have to prepend - //a zero if the string only has one character. - while ((digits < len) && (p > buffer)) - { - *--p = '0'; - digits++; - } - outputBuffer.Append(p, digits); - } - - private static void HebrewFormatDigits(StringBuilder outputBuffer, int digits) - { - outputBuffer.Append(HebrewNumber.ToString(digits)); - } - - internal static int ParseRepeatPattern(String format, int pos, char patternChar) - { - int len = format.Length; - int index = pos + 1; - while ((index < len) && (format[index] == patternChar)) - { - index++; - } - return (index - pos); - } - - private static String FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi) - { - Debug.Assert(dayOfWeek >= 0 && dayOfWeek <= 6, "dayOfWeek >= 0 && dayOfWeek <= 6"); - if (repeat == 3) - { - return (dtfi.GetAbbreviatedDayName((DayOfWeek)dayOfWeek)); - } - // Call dtfi.GetDayName() here, instead of accessing DayNames property, because we don't - // want a clone of DayNames, which will hurt perf. - return (dtfi.GetDayName((DayOfWeek)dayOfWeek)); - } - - private static String FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi) - { - Debug.Assert(month >= 1 && month <= 12, "month >=1 && month <= 12"); - if (repeatCount == 3) - { - return (dtfi.GetAbbreviatedMonthName(month)); - } - // Call GetMonthName() here, instead of accessing MonthNames property, because we don't - // want a clone of MonthNames, which will hurt perf. - return (dtfi.GetMonthName(month)); - } - - // - // FormatHebrewMonthName - // - // Action: Return the Hebrew month name for the specified DateTime. - // Returns: The month name string for the specified DateTime. - // Arguments: - // time the time to format - // month The month is the value of HebrewCalendar.GetMonth(time). - // repeat Return abbreviated month name if repeat=3, or full month name if repeat=4 - // dtfi The DateTimeFormatInfo which uses the Hebrew calendars as its calendar. - // Exceptions: None. - // - - /* Note: - If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this: - 1 Hebrew 1st Month - 2 Hebrew 2nd Month - .. ... - 6 Hebrew 6th Month - 7 Hebrew 6th Month II (used only in a leap year) - 8 Hebrew 7th Month - 9 Hebrew 8th Month - 10 Hebrew 9th Month - 11 Hebrew 10th Month - 12 Hebrew 11th Month - 13 Hebrew 12th Month - - Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7. - */ - private static String FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi) - { - Debug.Assert(repeatCount != 3 || repeatCount != 4, "repeateCount should be 3 or 4"); - if (dtfi.Calendar.IsLeapYear(dtfi.Calendar.GetYear(time))) - { - // This month is in a leap year - return (dtfi.internalGetMonthName(month, MonthNameStyles.LeapYear, (repeatCount == 3))); - } - // This is in a regular year. - if (month >= 7) - { - month++; - } - if (repeatCount == 3) - { - return (dtfi.GetAbbreviatedMonthName(month)); - } - return (dtfi.GetMonthName(month)); - } - - // - // The pos should point to a quote character. This method will - // append to the result StringBuilder the string encloed by the quote character. - // - internal static int ParseQuoteString(String format, int pos, StringBuilder result) - { - // - // NOTE : pos will be the index of the quote character in the 'format' string. - // - int formatLen = format.Length; - int beginPos = pos; - char quoteChar = format[pos++]; // Get the character used to quote the following string. - - bool foundQuote = false; - while (pos < formatLen) - { - char ch = format[pos++]; - if (ch == quoteChar) - { - foundQuote = true; - break; - } - else if (ch == '\\') - { - // The following are used to support escaped character. - // Escaped character is also supported in the quoted string. - // Therefore, someone can use a format like "'minute:' mm\"" to display: - // minute: 45" - // because the second double quote is escaped. - if (pos < formatLen) - { - result.Append(format[pos++]); - } - else - { - // - // This means that '\' is at the end of the formatting string. - // - throw new FormatException(SR.Format_InvalidString); - } - } - else - { - result.Append(ch); - } - } - - if (!foundQuote) - { - // Here we can't find the matching quote. - throw new FormatException( - String.Format( - CultureInfo.CurrentCulture, - SR.Format_BadQuote, quoteChar)); - } - - // - // Return the character count including the begin/end quote characters and enclosed string. - // - return (pos - beginPos); - } - - // - // Get the next character at the index of 'pos' in the 'format' string. - // Return value of -1 means 'pos' is already at the end of the 'format' string. - // Otherwise, return value is the int value of the next character. - // - internal static int ParseNextChar(String format, int pos) - { - if (pos >= format.Length - 1) - { - return (-1); - } - return ((int)format[pos + 1]); - } - - // - // IsUseGenitiveForm - // - // Actions: Check the format to see if we should use genitive month in the formatting. - // Starting at the position (index) in the (format) string, look back and look ahead to - // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use - // genitive form. Genitive form is not used if there is more than two "d". - // Arguments: - // format The format string to be scanned. - // index Where we should start the scanning. This is generally where "M" starts. - // tokenLen The len of the current pattern character. This indicates how many "M" that we have. - // patternToMatch The pattern that we want to search. This generally uses "d" - // - private static bool IsUseGenitiveForm(String format, int index, int tokenLen, char patternToMatch) - { - int i; - int repeat = 0; - // - // Look back to see if we can find "d" or "ddd" - // - - // Find first "d". - for (i = index - 1; i >= 0 && format[i] != patternToMatch; i--) { /*Do nothing here */ }; - - if (i >= 0) - { - // Find a "d", so look back to see how many "d" that we can find. - while (--i >= 0 && format[i] == patternToMatch) - { - repeat++; - } - // - // repeat == 0 means that we have one (patternToMatch) - // repeat == 1 means that we have two (patternToMatch) - // - if (repeat <= 1) - { - return (true); - } - // Note that we can't just stop here. We may find "ddd" while looking back, and we have to look - // ahead to see if there is "d" or "dd". - } - - // - // If we can't find "d" or "dd" by looking back, try look ahead. - // - - // Find first "d" - for (i = index + tokenLen; i < format.Length && format[i] != patternToMatch; i++) { /* Do nothing here */ }; - - if (i < format.Length) - { - repeat = 0; - // Find a "d", so contine the walk to see how may "d" that we can find. - while (++i < format.Length && format[i] == patternToMatch) - { - repeat++; - } - // - // repeat == 0 means that we have one (patternToMatch) - // repeat == 1 means that we have two (patternToMatch) - // - if (repeat <= 1) - { - return (true); - } - } - return (false); - } - - - // - // FormatCustomized - // - // Actions: Format the DateTime instance using the specified format. - // - private static String FormatCustomized(DateTime dateTime, String format, DateTimeFormatInfo dtfi, TimeSpan offset) - { - Calendar cal = dtfi.Calendar; - StringBuilder result = StringBuilderCache.Acquire(); - // This is a flag to indicate if we are format the dates using Hebrew calendar. - - bool isHebrewCalendar = (cal.ID == Calendar.CAL_HEBREW); - // This is a flag to indicate if we are formating hour/minute/second only. - bool bTimeOnly = true; - - int i = 0; - int tokenLen, hour12; - - while (i < format.Length) - { - char ch = format[i]; - int nextChar; - switch (ch) - { - case 'g': - tokenLen = ParseRepeatPattern(format, i, ch); - result.Append(dtfi.GetEraName(cal.GetEra(dateTime))); - break; - case 'h': - tokenLen = ParseRepeatPattern(format, i, ch); - hour12 = dateTime.Hour % 12; - if (hour12 == 0) - { - hour12 = 12; - } - FormatDigits(result, hour12, tokenLen); - break; - case 'H': - tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(result, dateTime.Hour, tokenLen); - break; - case 'm': - tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(result, dateTime.Minute, tokenLen); - break; - case 's': - tokenLen = ParseRepeatPattern(format, i, ch); - FormatDigits(result, dateTime.Second, tokenLen); - break; - case 'f': - case 'F': - tokenLen = ParseRepeatPattern(format, i, ch); - if (tokenLen <= MaxSecondsFractionDigits) - { - long fraction = (dateTime.Ticks % Calendar.TicksPerSecond); - fraction = fraction / (long)Math.Pow(10, 7 - tokenLen); - if (ch == 'f') - { - result.Append(((int)fraction).ToString(fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture)); - } - else - { - int effectiveDigits = tokenLen; - while (effectiveDigits > 0) - { - if (fraction % 10 == 0) - { - fraction = fraction / 10; - effectiveDigits--; - } - else - { - break; - } - } - if (effectiveDigits > 0) - { - result.Append(((int)fraction).ToString(fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture)); - } - else - { - // No fraction to emit, so see if we should remove decimal also. - if (result.Length > 0 && result[result.Length - 1] == '.') - { - result.Remove(result.Length - 1, 1); - } - } - } - } - else - { - throw new FormatException(SR.Format_InvalidString); - } - break; - case 't': - tokenLen = ParseRepeatPattern(format, i, ch); - if (tokenLen == 1) - { - if (dateTime.Hour < 12) - { - if (dtfi.AMDesignator.Length >= 1) - { - result.Append(dtfi.AMDesignator[0]); - } - } - else - { - if (dtfi.PMDesignator.Length >= 1) - { - result.Append(dtfi.PMDesignator[0]); - } - } - } - else - { - result.Append((dateTime.Hour < 12 ? dtfi.AMDesignator : dtfi.PMDesignator)); - } - break; - case 'd': - // - // tokenLen == 1 : Day of month as digits with no leading zero. - // tokenLen == 2 : Day of month as digits with leading zero for single-digit months. - // tokenLen == 3 : Day of week as a three-leter abbreviation. - // tokenLen >= 4 : Day of week as its full name. - // - tokenLen = ParseRepeatPattern(format, i, ch); - if (tokenLen <= 2) - { - int day = cal.GetDayOfMonth(dateTime); - if (isHebrewCalendar) - { - // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values. - HebrewFormatDigits(result, day); - } - else - { - FormatDigits(result, day, tokenLen); - } - } - else - { - int dayOfWeek = (int)cal.GetDayOfWeek(dateTime); - result.Append(FormatDayOfWeek(dayOfWeek, tokenLen, dtfi)); - } - bTimeOnly = false; - break; - case 'M': - // - // tokenLen == 1 : Month as digits with no leading zero. - // tokenLen == 2 : Month as digits with leading zero for single-digit months. - // tokenLen == 3 : Month as a three-letter abbreviation. - // tokenLen >= 4 : Month as its full name. - // - tokenLen = ParseRepeatPattern(format, i, ch); - int month = cal.GetMonth(dateTime); - if (tokenLen <= 2) - { - if (isHebrewCalendar) - { - // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values. - HebrewFormatDigits(result, month); - } - else - { - FormatDigits(result, month, tokenLen); - } - } - else - { - if (isHebrewCalendar) - { - result.Append(FormatHebrewMonthName(dateTime, month, tokenLen, dtfi)); - } - else - { - if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0 && tokenLen >= 4) - { - result.Append( - dtfi.internalGetMonthName( - month, - IsUseGenitiveForm(format, i, tokenLen, 'd') ? MonthNameStyles.Genitive : MonthNameStyles.Regular, - false)); - } - else - { - result.Append(FormatMonth(month, tokenLen, dtfi)); - } - } - } - bTimeOnly = false; - break; - case 'y': - // Notes about OS behavior: - // y: Always print (year % 100). No leading zero. - // yy: Always print (year % 100) with leading zero. - // yyy/yyyy/yyyyy/... : Print year value. No leading zero. - - int year = cal.GetYear(dateTime); - tokenLen = ParseRepeatPattern(format, i, ch); - if (dtfi.HasForceTwoDigitYears) - { - FormatDigits(result, year, tokenLen <= 2 ? tokenLen : 2); - } - else if (cal.ID == Calendar.CAL_HEBREW) - { - HebrewFormatDigits(result, year); - } - else - { - if (tokenLen <= 2) - { - FormatDigits(result, year % 100, tokenLen); - } - else - { - String fmtPattern = "D" + tokenLen.ToString(); - result.Append(year.ToString(fmtPattern, CultureInfo.InvariantCulture)); - } - } - bTimeOnly = false; - break; - case 'z': - tokenLen = ParseRepeatPattern(format, i, ch); - FormatCustomizedTimeZone(dateTime, offset, format, tokenLen, bTimeOnly, result); - break; - case 'K': - tokenLen = 1; - FormatCustomizedRoundripTimeZone(dateTime, offset, result); - break; - case ':': - result.Append(dtfi.TimeSeparator); - tokenLen = 1; - break; - case '/': - result.Append(dtfi.DateSeparator); - tokenLen = 1; - break; - case '\'': - case '\"': - tokenLen = ParseQuoteString(format, i, result); - break; - case '%': - // Optional format character. - // For example, format string "%d" will print day of month - // without leading zero. Most of the cases, "%" can be ignored. - nextChar = ParseNextChar(format, i); - // nextChar will be -1 if we already reach the end of the format string. - // Besides, we will not allow "%%" appear in the pattern. - if (nextChar >= 0 && nextChar != (int)'%') - { - result.Append(FormatCustomized(dateTime, ((char)nextChar).ToString(), dtfi, offset)); - tokenLen = 2; - } - else - { - // - // This means that '%' is at the end of the format string or - // "%%" appears in the format string. - // - throw new FormatException(SR.Format_InvalidString); - } - break; - case '\\': - // Escaped character. Can be used to insert character into the format string. - // For exmple, "\d" will insert the character 'd' into the string. - // - // NOTENOTE : we can remove this format character if we enforce the enforced quote - // character rule. - // That is, we ask everyone to use single quote or double quote to insert characters, - // then we can remove this character. - // - nextChar = ParseNextChar(format, i); - if (nextChar >= 0) - { - result.Append(((char)nextChar)); - tokenLen = 2; - } - else - { - // - // This means that '\' is at the end of the formatting string. - // - throw new FormatException(SR.Format_InvalidString); - } - break; - default: - // NOTENOTE : we can remove this rule if we enforce the enforced quote - // character rule. - // That is, if we ask everyone to use single quote or double quote to insert characters, - // then we can remove this default block. - result.Append(ch); - tokenLen = 1; - break; - } - i += tokenLen; - } - return StringBuilderCache.GetStringAndRelease(result); - } - - - // output the 'z' famliy of formats, which output a the offset from UTC, e.g. "-07:30" - private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, String format, Int32 tokenLen, Boolean timeOnly, StringBuilder result) - { - // See if the instance already has an offset - Boolean dateTimeFormat = (offset == NullOffset); - if (dateTimeFormat) - { - // No offset. The instance is a DateTime and the output should be the local time zone - - if (timeOnly && dateTime.Ticks < Calendar.TicksPerDay) - { - // For time only format and a time only input, the time offset on 0001/01/01 is less - // accurate than the system's current offset because of daylight saving time. - offset = TimeZoneInfo.GetLocalUtcOffset(DateTime.Now, TimeZoneInfoOptions.NoThrowOnInvalidTime); - } - else if (dateTime.Kind == DateTimeKind.Utc) - { - offset = TimeSpan.Zero; - } - else - { - offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); - } - } - if (offset >= TimeSpan.Zero) - { - result.Append('+'); - } - else - { - result.Append('-'); - // get a positive offset, so that you don't need a separate code path for the negative numbers. - offset = offset.Negate(); - } - - if (tokenLen <= 1) - { - // 'z' format e.g "-7" - result.AppendFormat(CultureInfo.InvariantCulture, "{0:0}", offset.Hours); - } - else - { - // 'zz' or longer format e.g "-07" - result.AppendFormat(CultureInfo.InvariantCulture, "{0:00}", offset.Hours); - if (tokenLen >= 3) - { - // 'zzz*' or longer format e.g "-07:30" - result.AppendFormat(CultureInfo.InvariantCulture, ":{0:00}", offset.Minutes); - } - } - } - - // output the 'K' format, which is for round-tripping the data - private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, StringBuilder result) - { - // The objective of this format is to round trip the data in the type - // For DateTime it should round-trip the Kind value and preserve the time zone. - // DateTimeOffset instance, it should do so by using the internal time zone. - - if (offset == NullOffset) - { - // source is a date time, so behavior depends on the kind. - switch (dateTime.Kind) - { - case DateTimeKind.Local: - // This should output the local offset, e.g. "-07:30" - offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); - // fall through to shared time zone output code - break; - case DateTimeKind.Utc: - // The 'Z' constant is a marker for a UTC date - result.Append("Z"); - return; - default: - // If the kind is unspecified, we output nothing here - return; - } - } - if (offset >= TimeSpan.Zero) - { - result.Append('+'); - } - else - { - result.Append('-'); - // get a positive offset, so that you don't need a separate code path for the negative numbers. - offset = offset.Negate(); - } - - AppendNumber(result, offset.Hours, 2); - result.Append(':'); - AppendNumber(result, offset.Minutes, 2); - } - - - internal static String GetRealFormat(String format, DateTimeFormatInfo dtfi) - { - String realFormat = null; - - switch (format[0]) - { - case 'd': // Short Date - realFormat = dtfi.ShortDatePattern; - break; - case 'D': // Long Date - realFormat = dtfi.LongDatePattern; - break; - case 'f': // Full (long date + short time) - realFormat = dtfi.LongDatePattern + " " + dtfi.ShortTimePattern; - break; - case 'F': // Full (long date + long time) - realFormat = dtfi.FullDateTimePattern; - break; - case 'g': // General (short date + short time) - realFormat = dtfi.GeneralShortTimePattern; - break; - case 'G': // General (short date + long time) - realFormat = dtfi.GeneralLongTimePattern; - break; - case 'm': - case 'M': // Month/Day Date - realFormat = dtfi.MonthDayPattern; - break; - case 'o': - case 'O': - realFormat = RoundtripFormat; - break; - case 'r': - case 'R': // RFC 1123 Standard - realFormat = dtfi.RFC1123Pattern; - break; - case 's': // Sortable without Time Zone Info - realFormat = dtfi.SortableDateTimePattern; - break; - case 't': // Short Time - realFormat = dtfi.ShortTimePattern; - break; - case 'T': // Long Time - realFormat = dtfi.LongTimePattern; - break; - case 'u': // Universal with Sortable format - realFormat = dtfi.UniversalSortableDateTimePattern; - break; - case 'U': // Universal with Full (long date + long time) format - realFormat = dtfi.FullDateTimePattern; - break; - case 'y': - case 'Y': // Year/Month Date - realFormat = dtfi.YearMonthPattern; - break; - default: - throw new FormatException(SR.Format_InvalidString); - } - return (realFormat); - } - - - // Expand a pre-defined format string (like "D" for long date) to the real format that - // we are going to use in the date time parsing. - // This method also convert the dateTime if necessary (e.g. when the format is in Universal time), - // and change dtfi if necessary (e.g. when the format should use invariant culture). - // - private static String ExpandPredefinedFormat(String format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, ref TimeSpan offset) - { - switch (format[0]) - { - case 'o': - case 'O': // Round trip format - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - case 'r': - case 'R': // RFC 1123 Standard - if (offset != NullOffset) - { - // Convert to UTC invariants mean this will be in range - dateTime = dateTime - offset; - } - else if (dateTime.Kind == DateTimeKind.Local) - { - InvalidFormatForLocal(format, dateTime); - } - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - case 's': // Sortable without Time Zone Info - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - case 'u': // Universal time in sortable format. - if (offset != NullOffset) - { - // Convert to UTC invariants mean this will be in range - dateTime = dateTime - offset; - } - else if (dateTime.Kind == DateTimeKind.Local) - { - InvalidFormatForLocal(format, dateTime); - } - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - case 'U': // Universal time in culture dependent format. - if (offset != NullOffset) - { - // This format is not supported by DateTimeOffset - throw new FormatException(SR.Format_InvalidString); - } - // Universal time is always in Greogrian calendar. - // - // Change the Calendar to be Gregorian Calendar. - // - dtfi = (DateTimeFormatInfo)dtfi.Clone(); - if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) - { - dtfi.Calendar = GregorianCalendar.GetDefaultInstance(); - } - dateTime = dateTime.ToUniversalTime(); - break; - } - format = GetRealFormat(format, dtfi); - return (format); - } - - internal static String Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi) - { - return Format(dateTime, format, dtfi, NullOffset); - } - - - internal static String Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi, TimeSpan offset) - { - Contract.Requires(dtfi != null); - if (format == null || format.Length == 0) - { - Boolean timeOnlySpecialCase = false; - if (dateTime.Ticks < Calendar.TicksPerDay) - { - // If the time is less than 1 day, consider it as time of day. - // Just print out the short time format. - // - // This is a workaround for VB, since they use ticks less then one day to be - // time of day. In cultures which use calendar other than Gregorian calendar, these - // alternative calendar may not support ticks less than a day. - // For example, Japanese calendar only supports date after 1868/9/8. - // This will pose a problem when people in VB get the time of day, and use it - // to call ToString(), which will use the general format (short date + long time). - // Since Japanese calendar does not support Gregorian year 0001, an exception will be - // thrown when we try to get the Japanese year for Gregorian year 0001. - // Therefore, the workaround allows them to call ToString() for time of day from a DateTime by - // formatting as ISO 8601 format. - switch (dtfi.Calendar.ID) - { - case Calendar.CAL_JAPAN: - case Calendar.CAL_TAIWAN: - case Calendar.CAL_HIJRI: - case Calendar.CAL_HEBREW: - case Calendar.CAL_JULIAN: - case Calendar.CAL_UMALQURA: - case Calendar.CAL_PERSIAN: - timeOnlySpecialCase = true; - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - } - } - if (offset == NullOffset) - { - // Default DateTime.ToString case. - if (timeOnlySpecialCase) - { - format = "s"; - } - else - { - format = "G"; - } - } - else - { - // Default DateTimeOffset.ToString case. - if (timeOnlySpecialCase) - { - format = RoundtripDateTimeUnfixed; - } - else - { - format = dtfi.DateTimeOffsetPattern; - } - } - } - - if (format.Length == 1) - { - switch (format[0]) - { - case 'O': - case 'o': - return FastFormatRoundtrip(dateTime, offset); - case 'R': - case 'r': - return FastFormatRfc1123(dateTime, offset, dtfi); - } - - format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset); - } - - return (FormatCustomized(dateTime, format, dtfi, offset)); - } - - internal static string FastFormatRfc1123(DateTime dateTime, TimeSpan offset, DateTimeFormatInfo dtfi) - { - // ddd, dd MMM yyyy HH:mm:ss GMT - const int Rfc1123FormatLength = 29; - StringBuilder result = StringBuilderCache.Acquire(Rfc1123FormatLength); - - if (offset != NullOffset) - { - // Convert to UTC invariants - dateTime = dateTime - offset; - } - - result.Append(InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek]); - result.Append(','); - result.Append(' '); - AppendNumber(result, dateTime.Day, 2); - result.Append(' '); - result.Append(InvariantAbbreviatedMonthNames[dateTime.Month - 1]); - result.Append(' '); - AppendNumber(result, dateTime.Year, 4); - result.Append(' '); - AppendHHmmssTimeOfDay(result, dateTime); - result.Append(' '); - result.Append(Gmt); - - return StringBuilderCache.GetStringAndRelease(result); - } - - internal static string FastFormatRoundtrip(DateTime dateTime, TimeSpan offset) - { - // yyyy-MM-ddTHH:mm:ss.fffffffK - const int roundTripFormatLength = 28; - StringBuilder result = StringBuilderCache.Acquire(roundTripFormatLength); - - AppendNumber(result, dateTime.Year, 4); - result.Append('-'); - AppendNumber(result, dateTime.Month, 2); - result.Append('-'); - AppendNumber(result, dateTime.Day, 2); - result.Append('T'); - AppendHHmmssTimeOfDay(result, dateTime); - result.Append('.'); - - long fraction = dateTime.Ticks % TimeSpan.TicksPerSecond; - AppendNumber(result, fraction, 7); - - FormatCustomizedRoundripTimeZone(dateTime, offset, result); - - return StringBuilderCache.GetStringAndRelease(result); - } - - private static void AppendHHmmssTimeOfDay(StringBuilder result, DateTime dateTime) - { - // HH:mm:ss - AppendNumber(result, dateTime.Hour, 2); - result.Append(':'); - AppendNumber(result, dateTime.Minute, 2); - result.Append(':'); - AppendNumber(result, dateTime.Second, 2); - } - - internal static void AppendNumber(StringBuilder builder, long val, int digits) - { - for (int i = 0; i < digits; i++) - { - builder.Append('0'); - } - - int index = 1; - while (val > 0 && index <= digits) - { - builder[builder.Length - index] = (char)('0' + (val % 10)); - val = val / 10; - index++; - } - - Debug.Assert(val == 0, "DateTimeFormat.AppendNumber(): digits less than size of val"); - } - - internal static String[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi) - { - Contract.Requires(dtfi != null); - String[] allFormats = null; - String[] results = null; - - switch (format) - { - case 'd': - case 'D': - case 'f': - case 'F': - case 'g': - case 'G': - case 'm': - case 'M': - case 't': - case 'T': - case 'y': - case 'Y': - allFormats = dtfi.GetAllDateTimePatterns(format); - results = new String[allFormats.Length]; - for (int i = 0; i < allFormats.Length; i++) - { - results[i] = Format(dateTime, allFormats[i], dtfi); - } - break; - case 'U': - DateTime universalTime = dateTime.ToUniversalTime(); - allFormats = dtfi.GetAllDateTimePatterns(format); - results = new String[allFormats.Length]; - for (int i = 0; i < allFormats.Length; i++) - { - results[i] = Format(universalTime, allFormats[i], dtfi); - } - break; - // - // The following ones are special cases because these patterns are read-only in - // DateTimeFormatInfo. - // - case 'r': - case 'R': - case 'o': - case 'O': - case 's': - case 'u': - results = new String[] { Format(dateTime, new String(format, 1), dtfi) }; - break; - default: - throw new FormatException(SR.Format_InvalidString); - } - return (results); - } - - internal static String[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi) - { - List results = new List(DEFAULT_ALL_DATETIMES_SIZE); - - for (int i = 0; i < allStandardFormats.Length; i++) - { - String[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi); - for (int j = 0; j < strings.Length; j++) - { - results.Add(strings[j]); - } - } - String[] value = new String[results.Count]; - results.CopyTo(0, value, 0, results.Count); - return (value); - } - - // This is a placeholder for an MDA to detect when the user is using a - // local DateTime with a format that will be interpreted as UTC. - internal static void InvalidFormatForLocal(String format, DateTime dateTime) - { - } - } -} diff --git a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs b/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs deleted file mode 100644 index 90e243b..0000000 --- a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs +++ /dev/null @@ -1,3085 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.Runtime.Serialization; - -namespace System.Globalization -{ - // - // Flags used to indicate different styles of month names. - // This is an internal flag used by internalGetMonthName(). - // Use flag here in case that we need to provide a combination of these styles - // (such as month name of a leap year in genitive form. Not likely for now, - // but would like to keep the option open). - // - - [Flags] - internal enum MonthNameStyles - { - Regular = 0x00000000, - Genitive = 0x00000001, - LeapYear = 0x00000002, - } - - // - // Flags used to indicate special rule used in parsing/formatting - // for a specific DateTimeFormatInfo instance. - // This is an internal flag. - // - // This flag is different from MonthNameStyles because this flag - // can be expanded to accomodate parsing behaviors like CJK month names - // or alternative month names, etc. - - [Flags] - internal enum DateTimeFormatFlags - { - None = 0x00000000, - UseGenitiveMonth = 0x00000001, - UseLeapYearMonth = 0x00000002, - UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names. - UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule. - UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names. - UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers. - - NotInitialized = -1, - } - - - [Serializable] - public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable - { - // cache for the invariant culture. - // invariantInfo is constant irrespective of your current culture. - private static volatile DateTimeFormatInfo s_invariantInfo; - - // an index which points to a record in Culture Data Table. - [NonSerialized] - private CultureData _cultureData; - - // The culture name used to create this DTFI. - - [OptionalField(VersionAdded = 2)] - private String _name = null; - - // The language name of the culture used to create this DTFI. - [NonSerialized] - private String _langName = null; - - // CompareInfo usually used by the parser. - [NonSerialized] - private CompareInfo _compareInfo = null; - - // Culture matches current DTFI. mainly used for string comparisons during parsing. - [NonSerialized] - private CultureInfo _cultureInfo = null; - - // - // Caches for various properties. - // - - private String amDesignator = null; - private String pmDesignator = null; - - private String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't) - - private String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't) - - private String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't) - - private String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't) - private String monthDayPattern = null; - // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM} - private String dateTimeOffsetPattern = null; - - // - // The following are constant values. - // - private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"; - - // The sortable pattern is based on ISO 8601. - private const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"; - private const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'"; - - // - // The following are affected by calendar settings. - // - private Calendar calendar = null; - - private int firstDayOfWeek = -1; - private int calendarWeekRule = -1; - - - private String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't) - - private String[] abbreviatedDayNames = null; - - - private String[] m_superShortDayNames = null; - - private String[] dayNames = null; - private String[] abbreviatedMonthNames = null; - private String[] monthNames = null; - // Cache the genitive month names that we retrieve from the data table. - - private String[] genitiveMonthNames = null; - - // Cache the abbreviated genitive month names that we retrieve from the data table. - - private String[] m_genitiveAbbreviatedMonthNames = null; - - // Cache the month names of a leap year that we retrieve from the data table. - - private String[] leapYearMonthNames = null; - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - - // The "default" Date/time patterns - private String longDatePattern = null; - private String shortDatePattern = null; - private String yearMonthPattern = null; - private String longTimePattern = null; - private String shortTimePattern = null; - - [OptionalField(VersionAdded = 3)] - private String[] allYearMonthPatterns = null; - - private String[] allShortDatePatterns = null; - private String[] allLongDatePatterns = null; - private String[] allShortTimePatterns = null; - private String[] allLongTimePatterns = null; - - // Cache the era names for this DateTimeFormatInfo instance. - private String[] m_eraNames = null; - private String[] m_abbrevEraNames = null; - private String[] m_abbrevEnglishEraNames = null; - - private CalendarId[] optionalCalendars = null; - - private const int DEFAULT_ALL_DATETIMES_SIZE = 132; - - // CultureInfo updates this - internal bool _isReadOnly = false; - - // This flag gives hints about if formatting/parsing should perform special code path for things like - // genitive form or leap year month names. - - private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized; - - private String CultureName - { - get - { - if (_name == null) - { - _name = _cultureData.CultureName; - } - return (_name); - } - } - - private CultureInfo Culture - { - get - { - if (_cultureInfo == null) - { - _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName); - } - return _cultureInfo; - } - } - - // TODO: This ignores other cultures that might want to do something similar - private String LanguageName - { - get - { - if (_langName == null) - { - _langName = _cultureData.SISO639LANGNAME; - } - return (_langName); - } - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the abbreviated day names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetAbbreviatedDayOfWeekNames() - { - if (this.abbreviatedDayNames == null) - { - // Get the abbreviated day names for our current calendar - this.abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID); - Debug.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week"); - } - return (this.abbreviatedDayNames); - } - - - //////////////////////////////////////////////////////////////////////// - // - // Action: Returns the string array of the one-letter day of week names. - // Returns: - // an array of one-letter day of week names - // Arguments: - // None - // Exceptions: - // None - // - //////////////////////////////////////////////////////////////////////// - - private String[] internalGetSuperShortDayNames() - { - if (this.m_superShortDayNames == null) - { - // Get the super short day names for our current calendar - this.m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID); - Debug.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week"); - } - return (this.m_superShortDayNames); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the day names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetDayOfWeekNames() - { - if (this.dayNames == null) - { - // Get the day names for our current calendar - this.dayNames = _cultureData.DayNames(Calendar.ID); - Debug.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week"); - } - return (this.dayNames); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the abbreviated month names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetAbbreviatedMonthNames() - { - if (this.abbreviatedMonthNames == null) - { - // Get the month names for our current calendar - this.abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID); - Debug.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13, - "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year"); - } - return (this.abbreviatedMonthNames); - } - - - //////////////////////////////////////////////////////////////////////////// - // - // Create an array of string which contains the month names. - // - //////////////////////////////////////////////////////////////////////////// - - private String[] internalGetMonthNames() - { - if (this.monthNames == null) - { - // Get the month names for our current calendar - this.monthNames = _cultureData.MonthNames(Calendar.ID); - Debug.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13, - "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year"); - } - - return (this.monthNames); - } - - - // - // Invariant DateTimeFormatInfo doesn't have user-overriden values - // Default calendar is gregorian - public DateTimeFormatInfo() - : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance()) - { - } - - internal DateTimeFormatInfo(CultureData cultureData, Calendar cal) - { - Debug.Assert(cultureData != null); - Debug.Assert(cal != null); - - // Remember our culture - _cultureData = cultureData; - - this.Calendar = cal; - } - - private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId) - { - Debug.Assert(cultureData != null); - Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId"); - - if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; } - if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; } - - if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; } - if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; } - if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; } - if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarId); } - - this.allLongTimePatterns = _cultureData.LongTimes; - Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns"); - - this.allShortTimePatterns = _cultureData.ShortTimes; - Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns"); - - this.allLongDatePatterns = cultureData.LongDates(calendarId); - Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns"); - - this.allShortDatePatterns = cultureData.ShortDates(calendarId); - Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns"); - - this.allYearMonthPatterns = cultureData.YearMonths(calendarId); - Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns"); - } - - [OptionalField(VersionAdded = 1)] - private bool _useUserOverride; - - // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string - // Now we always synthesize so its not helpful - - internal String[] m_dateWords = null; - - [OnSerializing] - private void OnSerializing(StreamingContext ctx) - { - _name = this.CultureName; // make sure the _name is initialized. - _useUserOverride = _cultureData.UseUserOverride; - - // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey - // because Whidbey try to initialize some of these fields using calendar data which could be null values - // and then we get exceptions. So we call the accessors to force the caches to get loaded. - Object o; - o = this.LongTimePattern; - o = this.LongDatePattern; - o = this.ShortTimePattern; - o = this.ShortDatePattern; - o = this.YearMonthPattern; - o = this.AllLongTimePatterns; - o = this.AllLongDatePatterns; - o = this.AllShortTimePatterns; - o = this.AllShortDatePatterns; - o = this.AllYearMonthPatterns; - } - - [OnDeserialized] - private void OnDeserialized(StreamingContext ctx) - { - if (_name != null) - { - _cultureData = CultureData.GetCultureData(_name, _useUserOverride); - if (_cultureData == null) - { - throw new CultureNotFoundException("_name", _name, SR.Argument_CultureNotSupported); - } - } - - if (calendar == null) - { - calendar = (Calendar)GregorianCalendar.GetDefaultInstance().Clone(); - calendar.SetReadOnlyState(_isReadOnly); - } - - InitializeOverridableProperties(_cultureData, calendar.ID); - - // - // turn off read only state till we finish initializing all fields and then store read only state after we are done. - // - bool isReadOnly = _isReadOnly; - _isReadOnly = false; - - // If we deserialized defaults ala Whidbey, make sure they're still defaults - // Whidbey's arrays could get a bit mixed up. - if (longDatePattern != null) this.LongDatePattern = longDatePattern; - if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern; - if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern; - if (longTimePattern != null) this.LongTimePattern = longTimePattern; - if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern; - - _isReadOnly = isReadOnly; - } - - // Returns a default DateTimeFormatInfo that will be universally - // supported and constant irrespective of the current culture. - // Used by FromString methods. - // - - public static DateTimeFormatInfo InvariantInfo - { - get - { - Contract.Ensures(Contract.Result() != null); - if (s_invariantInfo == null) - { - DateTimeFormatInfo info = new DateTimeFormatInfo(); - info.Calendar.SetReadOnlyState(true); - info._isReadOnly = true; - s_invariantInfo = info; - } - return (s_invariantInfo); - } - } - - // Returns the current culture's DateTimeFormatInfo. Used by Parse methods. - // - - public static DateTimeFormatInfo CurrentInfo - { - get - { - Contract.Ensures(Contract.Result() != null); - System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture; - if (!culture._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._isInherited) - { - return cultureProvider.DateTimeFormat; - } - // Fast case for a DTFI; - info = provider as DateTimeFormatInfo; - if (info != null) - { - return info; - } - // Wasn't cultureInfo or DTFI, do it the slower way - if (provider != null) - { - info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo; - if (info != null) - { - return info; - } - } - // Couldn't get anything, just use currentInfo as fallback - return CurrentInfo; - } - - - public Object GetFormat(Type formatType) - { - return (formatType == typeof(DateTimeFormatInfo) ? this : null); - } - - - public Object Clone() - { - DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone(); - // We can use the data member calendar in the setter, instead of the property Calendar, - // since the cloned copy should have the same state as the original copy. - n.calendar = (Calendar)this.Calendar.Clone(); - n._isReadOnly = false; - return n; - } - - - public String AMDesignator - { - get - { - if (this.amDesignator == null) - { - this.amDesignator = _cultureData.SAM1159; - } - Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null"); - return (this.amDesignator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - amDesignator = value; - } - } - - - public Calendar Calendar - { - get - { - Contract.Ensures(Contract.Result() != null); - - Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null"); - return (this.calendar); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); - } - Contract.EndContractBlock(); - if (value == calendar) - { - return; - } - - for (int i = 0; i < this.OptionalCalendars.Length; i++) - { - if (this.OptionalCalendars[i] == value.ID) - { - // We can use this one, so do so. - - // Clean related properties if we already had a calendar set - if (calendar != null) - { - // clean related properties which are affected by the calendar setting, - // so that they will be refreshed when they are accessed next time. - // - - // These properites are in the order as appearing in calendar.xml. - m_eraNames = null; - m_abbrevEraNames = null; - m_abbrevEnglishEraNames = null; - - monthDayPattern = null; - - dayNames = null; - abbreviatedDayNames = null; - m_superShortDayNames = null; - monthNames = null; - abbreviatedMonthNames = null; - genitiveMonthNames = null; - m_genitiveAbbreviatedMonthNames = null; - leapYearMonthNames = null; - formatFlags = DateTimeFormatFlags.NotInitialized; - - allShortDatePatterns = null; - allLongDatePatterns = null; - allYearMonthPatterns = null; - dateTimeOffsetPattern = null; - - // The defaults need reset as well: - longDatePattern = null; - shortDatePattern = null; - yearMonthPattern = null; - - // These properies are not in the OS data, but they are dependent on the values like shortDatePattern. - fullDateTimePattern = null; // Long date + long time - generalShortTimePattern = null; // short date + short time - generalLongTimePattern = null; // short date + long time - - // Derived item that changes - dateSeparator = null; - - // We don't need to do these because they are not changed by changing calendar - // amDesignator - // pmDesignator - // timeSeparator - // longTimePattern - // firstDayOfWeek - // calendarWeekRule - - // remember to reload tokens - ClearTokenHashTable(); - } - - // Remember the new calendar - calendar = value; - InitializeOverridableProperties(_cultureData, calendar.ID); - - // We succeeded, return - return; - } - } - - // The assigned calendar is not a valid calendar for this culture, throw - throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar); - } - } - - private CalendarId[] OptionalCalendars - { - get - { - if (this.optionalCalendars == null) - { - this.optionalCalendars = _cultureData.CalendarIds; - } - return (this.optionalCalendars); - } - } - - /*=================================GetEra========================== - **Action: Get the era value by parsing the name of the era. - **Returns: The era value for the specified era name. - ** -1 if the name of the era is not valid or not supported. - **Arguments: eraName the name of the era. - **Exceptions: None. - ============================================================================*/ - - - public int GetEra(String eraName) - { - if (eraName == null) - { - throw new ArgumentNullException(nameof(eraName), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // The Era Name and Abbreviated Era Name - // for Taiwan Calendar on non-Taiwan SKU returns empty string (which - // would be matched below) but we don't want the empty string to give - // us an Era number - // confer 85900 DTFI.GetEra("") should fail on all cultures - if (eraName.Length == 0) - { - return (-1); - } - - // The following is based on the assumption that the era value is starting from 1, and has a - // serial values. - // If that ever changes, the code has to be changed. - - // The calls to String.Compare should use the current culture for the string comparisons, but the - // invariant culture when comparing against the english names. - for (int i = 0; i < EraNames.Length; i++) - { - // Compare the era name in a case-insensitive way for the appropriate culture. - if (m_eraNames[i].Length > 0) - { - if (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - } - for (int i = 0; i < AbbreviatedEraNames.Length; i++) - { - // Compare the abbreviated era name in a case-insensitive way for the appropriate culture. - if (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) - { - // this comparison should use the InvariantCulture. The English name could have linguistically - // interesting characters. - if (CultureInfo.InvariantCulture.CompareInfo.Compare(eraName, m_abbrevEnglishEraNames[i], CompareOptions.IgnoreCase) == 0) - { - return (i + 1); - } - } - return (-1); - } - - - internal String[] EraNames - { - get - { - if (this.m_eraNames == null) - { - this.m_eraNames = _cultureData.EraNames(Calendar.ID); ; - } - return (this.m_eraNames); - } - } - - /*=================================GetEraName========================== - **Action: Get the name of the era for the specified era value. - **Returns: The name of the specified era. - **Arguments: - ** era the era value. - **Exceptions: - ** ArguementException if the era valie is invalid. - ============================================================================*/ - - // Era names are 1 indexed - public String GetEraName(int era) - { - if (era == Calendar.CurrentEra) - { - era = Calendar.CurrentEraValue; - } - - // The following is based on the assumption that the era value is starting from 1, and has a - // serial values. - // If that ever changes, the code has to be changed. - if ((--era) < EraNames.Length && (era >= 0)) - { - return (m_eraNames[era]); - } - throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); - } - - internal String[] AbbreviatedEraNames - { - get - { - if (this.m_abbrevEraNames == null) - { - this.m_abbrevEraNames = _cultureData.AbbrevEraNames(Calendar.ID); - } - return (this.m_abbrevEraNames); - } - } - - // Era names are 1 indexed - public String GetAbbreviatedEraName(int era) - { - if (AbbreviatedEraNames.Length == 0) - { - // If abbreviation era name is not used in this culture, - // return the full era name. - return (GetEraName(era)); - } - if (era == Calendar.CurrentEra) - { - era = Calendar.CurrentEraValue; - } - if ((--era) < m_abbrevEraNames.Length && (era >= 0)) - { - return (m_abbrevEraNames[era]); - } - throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); - } - - internal String[] AbbreviatedEnglishEraNames - { - get - { - if (this.m_abbrevEnglishEraNames == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0"); - this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID); - } - return (this.m_abbrevEnglishEraNames); - } - } - - // Note that cultureData derives this from the short date format (unless someone's set this previously) - // Note that this property is quite undesirable. - public string DateSeparator - { - get - { - if (dateSeparator == null) - { - dateSeparator = _cultureData.DateSeparator(Calendar.ID); - } - Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null"); - return dateSeparator; - } - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - dateSeparator = value; - } - } - - public DayOfWeek FirstDayOfWeek - { - get - { - if (this.firstDayOfWeek == -1) - { - this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK; - } - Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1"); - - return ((DayOfWeek)this.firstDayOfWeek); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) - { - firstDayOfWeek = (int)value; - } - else - { - throw new ArgumentOutOfRangeException( - nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - } - } - - public CalendarWeekRule CalendarWeekRule - { - get - { - if (this.calendarWeekRule == -1) - { - this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR; - } - Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1"); - return ((CalendarWeekRule)this.calendarWeekRule); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) - { - calendarWeekRule = (int)value; - } - else - { - throw new ArgumentOutOfRangeException( - nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, - CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek)); - } - } - } - - public String FullDateTimePattern - { - get - { - if (fullDateTimePattern == null) - { - fullDateTimePattern = LongDatePattern + " " + LongTimePattern; - } - return (fullDateTimePattern); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - fullDateTimePattern = value; - } - } - - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String LongDatePattern - { - get - { - // Initialize our long date pattern from the 1st array value if not set - if (this.longDatePattern == null) - { - // Initialize our data - this.longDatePattern = this.UnclonedLongDatePatterns[0]; - } - - return this.longDatePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.longDatePattern = value; - - // Clear the token hash table - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - this.fullDateTimePattern = null; - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String LongTimePattern - { - get - { - // Initialize our long time pattern from the 1st array value if not set - if (this.longTimePattern == null) - { - // Initialize our data - this.longTimePattern = this.UnclonedLongTimePatterns[0]; - } - - return this.longTimePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.longTimePattern = value; - - // Clear the token hash table - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - this.fullDateTimePattern = null; // Full date = long date + long Time - this.generalLongTimePattern = null; // General long date = short date + long Time - this.dateTimeOffsetPattern = null; - } - } - - - // Note: just to be confusing there's only 1 month day pattern, not a whole list - public String MonthDayPattern - { - get - { - if (this.monthDayPattern == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0"); - this.monthDayPattern = _cultureData.MonthDay(Calendar.ID); - } - Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null"); - return (this.monthDayPattern); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - this.monthDayPattern = value; - } - } - - - public String PMDesignator - { - get - { - if (this.pmDesignator == null) - { - this.pmDesignator = _cultureData.SPM2359; - } - Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null"); - return (this.pmDesignator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - ClearTokenHashTable(); - - pmDesignator = value; - } - } - - - public String RFC1123Pattern - { - get - { - return (rfc1123Pattern); - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String ShortDatePattern - { - get - { - // Initialize our short date pattern from the 1st array value if not set - if (this.shortDatePattern == null) - { - // Initialize our data - this.shortDatePattern = this.UnclonedShortDatePatterns[0]; - } - - return this.shortDatePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - Contract.EndContractBlock(); - - // Remember the new string - this.shortDatePattern = value; - - // Clear the token hash table, note that even short dates could require this - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - generalLongTimePattern = null; // General long time = short date + long time - generalShortTimePattern = null; // General short time = short date + short Time - dateTimeOffsetPattern = null; - } - } - - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String ShortTimePattern - { - get - { - // Initialize our short time pattern from the 1st array value if not set - if (this.shortTimePattern == null) - { - // Initialize our data - this.shortTimePattern = this.UnclonedShortTimePatterns[0]; - } - return this.shortTimePattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.shortTimePattern = value; - - // Clear the token hash table, note that even short times could require this - ClearTokenHashTable(); - - // Clean up cached values that will be affected by this property. - generalShortTimePattern = null; // General short date = short date + short time. - } - } - - - public String SortableDateTimePattern - { - get - { - return (sortableDateTimePattern); - } - } - - /*=================================GeneralShortTimePattern===================== - **Property: Return the pattern for 'g' general format: shortDate + short time - **Note: This is used by DateTimeFormat.cs to get the pattern for 'g' - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody asks for the general format. - ==============================================================================*/ - - internal String GeneralShortTimePattern - { - get - { - if (generalShortTimePattern == null) - { - generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern; - } - return (generalShortTimePattern); - } - } - - /*=================================GeneralLongTimePattern===================== - **Property: Return the pattern for 'g' general format: shortDate + Long time - **Note: This is used by DateTimeFormat.cs to get the pattern for 'g' - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody asks for the general format. - ==============================================================================*/ - - internal String GeneralLongTimePattern - { - get - { - if (generalLongTimePattern == null) - { - generalLongTimePattern = ShortDatePattern + " " + LongTimePattern; - } - return (generalLongTimePattern); - } - } - - /*=================================DateTimeOffsetPattern========================== - **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset - **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody uses this form - ==============================================================================*/ - - /*=================================DateTimeOffsetPattern========================== - **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset - **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset - ** We put this internal property here so that we can avoid doing the - ** concatation every time somebody uses this form - ==============================================================================*/ - - internal String DateTimeOffsetPattern - { - get - { - if (dateTimeOffsetPattern == null) - { - string dateTimePattern = ShortDatePattern + " " + LongTimePattern; - - /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */ - - bool foundZ = false; - bool inQuote = false; - char quote = '\''; - for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) - { - switch (LongTimePattern[i]) - { - case 'z': - /* if we aren't in a quote, we've found a z */ - foundZ = !inQuote; - /* we'll fall out of the loop now because the test includes !foundZ */ - break; - case '\'': - case '\"': - if (inQuote && (quote == LongTimePattern[i])) - { - /* we were in a quote and found a matching exit quote, so we are outside a quote now */ - inQuote = false; - } - else if (!inQuote) - { - quote = LongTimePattern[i]; - inQuote = true; - } - else - { - /* we were in a quote and saw the other type of quote character, so we are still in a quote */ - } - break; - case '%': - case '\\': - i++; /* skip next character that is escaped by this backslash */ - break; - default: - break; - } - } - - if (!foundZ) - { - dateTimePattern = dateTimePattern + " zzz"; - } - - dateTimeOffsetPattern = dateTimePattern; - } - return (dateTimeOffsetPattern); - } - } - - // Note that cultureData derives this from the long time format (unless someone's set this previously) - // Note that this property is quite undesirable. - public string TimeSeparator - { - get - { - if (timeSeparator == null) - { - timeSeparator = _cultureData.TimeSeparator; - } - Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null"); - return (timeSeparator); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (value == null) - { - throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); - } - - Contract.EndContractBlock(); - ClearTokenHashTable(); - - timeSeparator = value; - } - } - - public String UniversalSortableDateTimePattern - { - get - { - return (universalSortableDateTimePattern); - } - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - public String YearMonthPattern - { - get - { - // Initialize our year/month pattern from the 1st array value if not set - if (this.yearMonthPattern == null) - { - // Initialize our data - this.yearMonthPattern = this.UnclonedYearMonthPatterns[0]; - } - return this.yearMonthPattern; - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_String); - } - Contract.EndContractBlock(); - - // Remember the new string - this.yearMonthPattern = value; - - // Clear the token hash table, note that even short times could require this - ClearTokenHashTable(); - } - } - - // - // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value" - // - private static void CheckNullValue(String[] values, int length) - { - Debug.Assert(values != null, "value != null"); - Debug.Assert(values.Length >= length); - for (int i = 0; i < length; i++) - { - if (values[i] == null) - { - throw new ArgumentNullException("value", - SR.ArgumentNull_ArrayValue); - } - } - } - - - public String[] AbbreviatedDayNames - { - get - { - return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - ClearTokenHashTable(); - - abbreviatedDayNames = value; - } - } - - // Returns the string array of the one-letter day of week names. - public String[] ShortestDayNames - { - get - { - return ((String[])internalGetSuperShortDayNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - this.m_superShortDayNames = value; - } - } - - - public String[] DayNames - { - get - { - return ((String[])internalGetDayOfWeekNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 7) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length); - ClearTokenHashTable(); - - dayNames = value; - } - } - - - public String[] AbbreviatedMonthNames - { - get - { - return ((String[])internalGetAbbreviatedMonthNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - ClearTokenHashTable(); - abbreviatedMonthNames = value; - } - } - - - public String[] MonthNames - { - get - { - return ((String[])internalGetMonthNames().Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - monthNames = value; - ClearTokenHashTable(); - } - } - - // Whitespaces that we allow in the month names. - // U+00a0 is non-breaking space. - private static readonly char[] s_monthSpaces = { ' ', '\u00a0' }; - - internal bool HasSpacesInMonthNames - { - get - { - return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0; - } - } - - internal bool HasSpacesInDayNames - { - get - { - return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0; - } - } - - - // - // internalGetMonthName - // - // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form - // or full form. - // Arguments: - // month - // style To indicate a form like regular/genitive/month name in a leap year. - // abbreviated When true, return abbreviated form. Otherwise, return a full form. - // Exceptions: - // ArgumentOutOfRangeException When month name is invalid. - // - internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) - { - // - // Right now, style is mutual exclusive, but I make the style to be flag so that - // maybe we can combine flag if there is such a need. - // - String[] monthNamesArray = null; - switch (style) - { - case MonthNameStyles.Genitive: - monthNamesArray = internalGetGenitiveMonthNames(abbreviated); - break; - case MonthNameStyles.LeapYear: - monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/); - break; - default: - monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames() : internalGetMonthNames()); - break; - } - // The month range is from 1 ~ this.m_monthNames.Length - // (actually is 13 right now for all cases) - if ((month < 1) || (month > monthNamesArray.Length)) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, monthNamesArray.Length)); - } - return (monthNamesArray[month - 1]); - } - - // - // internalGetGenitiveMonthNames - // - // Action: Retrieve the array which contains the month names in genitive form. - // If this culture does not use the gentive form, the normal month name is returned. - // Arguments: - // abbreviated When true, return abbreviated form. Otherwise, return a full form. - // - private String[] internalGetGenitiveMonthNames(bool abbreviated) - { - if (abbreviated) - { - if (this.m_genitiveAbbreviatedMonthNames == null) - { - this.m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID); - Debug.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13, - "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year"); - } - return (this.m_genitiveAbbreviatedMonthNames); - } - - if (this.genitiveMonthNames == null) - { - this.genitiveMonthNames = _cultureData.GenitiveMonthNames(this.Calendar.ID); - Debug.Assert(this.genitiveMonthNames.Length == 13, - "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year"); - } - return (this.genitiveMonthNames); - } - - // - // internalGetLeapYearMonthNames - // - // Actions: Retrieve the month names used in a leap year. - // If this culture does not have different month names in a leap year, the normal month name is returned. - // Agruments: None. (can use abbreviated later if needed) - // - internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) - { - if (this.leapYearMonthNames == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0"); - this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID); - Debug.Assert(this.leapYearMonthNames.Length == 13, - "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names"); - } - return (leapYearMonthNames); - } - - - public String GetAbbreviatedDayName(DayOfWeek dayofweek) - { - if ((int)dayofweek < 0 || (int)dayofweek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - // - // Don't call the public property AbbreviatedDayNames here since a clone is needed in that - // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly. - // - return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]); - } - - // Returns the super short day of week names for the specified day of week. - public string GetShortestDayName(DayOfWeek dayOfWeek) - { - if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - // - // Don't call the public property SuperShortDayNames here since a clone is needed in that - // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly. - // - return (internalGetSuperShortDayNames()[(int)dayOfWeek]); - } - - // Get all possible combination of inputs - private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString) - { - Debug.Assert(patterns1 != null); - Debug.Assert(patterns2 != null); - - // Get array size - String[] result = new String[patterns1.Length * patterns2.Length]; - - // Counter of actual results - int k = 0; - for (int i = 0; i < patterns1.Length; i++) - { - for (int j = 0; j < patterns2.Length; j++) - { - // Can't combine if null or empty - result[k++] = patterns1[i] + connectString + patterns2[j]; - } - } - - // Return the combinations - return (result); - } - - public string[] GetAllDateTimePatterns() - { - List results = new List(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() != null); - String[] result = null; - - switch (format) - { - case 'd': - result = this.AllShortDatePatterns; - break; - case 'D': - result = this.AllLongDatePatterns; - break; - case 'f': - result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " "); - break; - case 'F': - case 'U': - result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " "); - break; - case 'g': - result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " "); - break; - case 'G': - result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " "); - break; - case 'm': - case 'M': - result = new String[] { MonthDayPattern }; - break; - case 'o': - case 'O': - result = new String[] { RoundtripFormat }; - break; - case 'r': - case 'R': - result = new String[] { rfc1123Pattern }; - break; - case 's': - result = new String[] { sortableDateTimePattern }; - break; - case 't': - result = this.AllShortTimePatterns; - break; - case 'T': - result = this.AllLongTimePatterns; - break; - case 'u': - result = new String[] { UniversalSortableDateTimePattern }; - break; - case 'y': - case 'Y': - result = this.AllYearMonthPatterns; - break; - default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); - } - return (result); - } - - - public String GetDayName(DayOfWeek dayofweek) - { - if ((int)dayofweek < 0 || (int)dayofweek > 6) - { - throw new ArgumentOutOfRangeException( - nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, - DayOfWeek.Sunday, DayOfWeek.Saturday)); - } - Contract.EndContractBlock(); - - // Use the internal one so that we don't clone the array unnecessarily - return (internalGetDayOfWeekNames()[(int)dayofweek]); - } - - - - public String GetAbbreviatedMonthName(int month) - { - if (month < 1 || month > 13) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, 13)); - } - Contract.EndContractBlock(); - // Use the internal one so we don't clone the array unnecessarily - return (internalGetAbbreviatedMonthNames()[month - 1]); - } - - - public String GetMonthName(int month) - { - if (month < 1 || month > 13) - { - throw new ArgumentOutOfRangeException( - nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, - 1, 13)); - } - Contract.EndContractBlock(); - // Use the internal one so we don't clone the array unnecessarily - return (internalGetMonthNames()[month - 1]); - } - - // For our "patterns" arrays we have 2 variables, a string and a string[] - // - // The string[] contains the list of patterns, EXCEPT the default may not be included. - // The string contains the default pattern. - // When we initially construct our string[], we set the string to string[0] - // - // The resulting [] can get returned to the calling app, so clone it. - private static string[] GetMergedPatterns(string[] patterns, string defaultPattern) - { - Debug.Assert(patterns != null && patterns.Length > 0, - "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern"); - Debug.Assert(defaultPattern != null, - "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string"); - - // If the default happens to be the first in the list just return (a cloned) copy - if (defaultPattern == patterns[0]) - { - return (string[])patterns.Clone(); - } - - // We either need a bigger list, or the pattern from the list. - int i; - for (i = 0; i < patterns.Length; i++) - { - // Stop if we found it - if (defaultPattern == patterns[i]) - break; - } - - // Either way we're going to need a new array - string[] newPatterns; - - // Did we find it - if (i < patterns.Length) - { - // Found it, output will be same size - newPatterns = (string[])patterns.Clone(); - - // Have to move [0] item to [i] so we can re-write default at [0] - // (remember defaultPattern == [i] so this is OK) - newPatterns[i] = newPatterns[0]; - } - else - { - // Not found, make room for it - newPatterns = new String[patterns.Length + 1]; - - // Copy existing array - Array.Copy(patterns, 0, newPatterns, 1, patterns.Length); - } - - // Remember the default - newPatterns[0] = defaultPattern; - - // Return the reconstructed list - return newPatterns; - } - - // Needed by DateTimeFormatInfo and DateTimeFormat - internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; - internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; - - // Default string isn't necessarily in our string array, so get the - // merged patterns of both - private String[] AllYearMonthPatterns - { - get - { - return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern); - } - } - - private String[] AllShortDatePatterns - { - get - { - return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern); - } - } - - private String[] AllShortTimePatterns - { - get - { - return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern); - } - } - - private String[] AllLongDatePatterns - { - get - { - return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern); - } - } - - private String[] AllLongTimePatterns - { - get - { - return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern); - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllYearMonthPatterns - private String[] UnclonedYearMonthPatterns - { - get - { - if (allYearMonthPatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0"); - this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID); - Debug.Assert(this.allYearMonthPatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns"); - } - - return allYearMonthPatterns; - } - } - - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllShortDatePatterns - private String[] UnclonedShortDatePatterns - { - get - { - if (allShortDatePatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0"); - this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID); - Debug.Assert(this.allShortDatePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns"); - } - - return this.allShortDatePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllLongDatePatterns - private String[] UnclonedLongDatePatterns - { - get - { - if (allLongDatePatterns == null) - { - Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0"); - this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID); - Debug.Assert(this.allLongDatePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns"); - } - - return this.allLongDatePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllShortTimePatterns - private String[] UnclonedShortTimePatterns - { - get - { - if (this.allShortTimePatterns == null) - { - this.allShortTimePatterns = _cultureData.ShortTimes; - Debug.Assert(this.allShortTimePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns"); - } - - return this.allShortTimePatterns; - } - } - - // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy. - // This won't include default, call AllLongTimePatterns - private String[] UnclonedLongTimePatterns - { - get - { - if (this.allLongTimePatterns == null) - { - this.allLongTimePatterns = _cultureData.LongTimes; - Debug.Assert(this.allLongTimePatterns.Length > 0, - "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns"); - } - - return this.allLongTimePatterns; - } - } - - public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) - { - if (dtfi == null) - { - throw new ArgumentNullException(nameof(dtfi), - SR.ArgumentNull_Obj); - } - Contract.EndContractBlock(); - if (dtfi.IsReadOnly) - { - return (dtfi); - } - DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone()); - // We can use the data member calendar in the setter, instead of the property Calendar, - // since the cloned copy should have the same state as the original copy. - newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar); - newInfo._isReadOnly = true; - return (newInfo); - } - - public bool IsReadOnly - { - get - { - return (_isReadOnly); - } - } - - // Return the native name for the calendar in DTFI.Calendar. The native name is referred to - // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese. - // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar(); - // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar. - // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized); - // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar. - public string NativeCalendarName - { - get - { - return _cultureData.CalendarName(Calendar.ID); - } - } - - // - // Used by custom cultures and others to set the list of available formats. Note that none of them are - // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items - // from the list. - // - // Most of the format characters that can be used in GetAllDateTimePatterns are - // not really needed since they are one of the following: - // - // r/R/s/u locale-independent constants -- cannot be changed! - // m/M/y/Y fields with a single string in them -- that can be set through props directly - // f/F/g/G/U derived fields based on combinations of various of the below formats - // - // NOTE: No special validation is done here beyond what is done when the actual respective fields - // are used (what would be the point of disallowing here what we allow in the appropriate property?) - // - // WARNING: If more validation is ever done in one place, it should be done in the other. - // - public void SetAllDateTimePatterns(String[] patterns, char format) - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - - if (patterns == null) - { - throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array); - } - - if (patterns.Length == 0) - { - throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns)); - } - - Contract.EndContractBlock(); - - for (int i = 0; i < patterns.Length; i++) - { - if (patterns[i] == null) - { - throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue); - } - } - - // Remember the patterns, and use the 1st as default - switch (format) - { - case 'd': - allShortDatePatterns = patterns; - shortDatePattern = allShortDatePatterns[0]; - break; - - case 'D': - allLongDatePatterns = patterns; - longDatePattern = allLongDatePatterns[0]; - break; - - case 't': - allShortTimePatterns = patterns; - shortTimePattern = allShortTimePatterns[0]; - break; - - case 'T': - allLongTimePatterns = patterns; - longTimePattern = allLongTimePatterns[0]; - break; - - case 'y': - case 'Y': - allYearMonthPatterns = patterns; - yearMonthPattern = allYearMonthPatterns[0]; - break; - - default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); - } - - // Clear the token hash table, note that even short dates could require this - ClearTokenHashTable(); - } - - public String[] AbbreviatedMonthGenitiveNames - { - get - { - return ((String[])internalGetGenitiveMonthNames(true).Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - ClearTokenHashTable(); - this.m_genitiveAbbreviatedMonthNames = value; - } - } - - public String[] MonthGenitiveNames - { - get - { - return ((String[])internalGetGenitiveMonthNames(false).Clone()); - } - - set - { - if (IsReadOnly) - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - if (value == null) - { - throw new ArgumentNullException(nameof(value), - SR.ArgumentNull_Array); - } - if (value.Length != 13) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); - } - Contract.EndContractBlock(); - CheckNullValue(value, value.Length - 1); - genitiveMonthNames = value; - ClearTokenHashTable(); - } - } - - // - // Positive TimeSpan Pattern - // - [NonSerialized] - private string _fullTimeSpanPositivePattern; - internal String FullTimeSpanPositivePattern - { - get - { - if (_fullTimeSpanPositivePattern == null) - { - CultureData cultureDataWithoutUserOverrides; - if (_cultureData.UseUserOverride) - cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false); - else - cultureDataWithoutUserOverrides = _cultureData; - String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator; - - _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF"; - } - return _fullTimeSpanPositivePattern; - } - } - - // - // Negative TimeSpan Pattern - // - [NonSerialized] - private string _fullTimeSpanNegativePattern; - internal String FullTimeSpanNegativePattern - { - get - { - if (_fullTimeSpanNegativePattern == null) - _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern; - return _fullTimeSpanNegativePattern; - } - } - - // - // Get suitable CompareInfo from current DTFI object. - // - internal CompareInfo CompareInfo - { - get - { - if (_compareInfo == null) - { - // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the - // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData. - _compareInfo = CompareInfo.GetCompareInfo(_cultureData.SCOMPAREINFO); - } - - return _compareInfo; - } - } - - - internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite - | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault - | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal - | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind); - - internal static void ValidateStyles(DateTimeStyles style, String parameterName) - { - if ((style & InvalidDateTimeStyles) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName); - } - if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) - { - throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName); - } - Contract.EndContractBlock(); - if (((style & DateTimeStyles.RoundtripKind) != 0) - && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) - { - throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName); - } - } - - // - // Actions: Return the internal flag used in formatting and parsing. - // The flag can be used to indicate things like if genitive forms is used in this DTFi, or if leap year gets different month names. - // - internal DateTimeFormatFlags FormatFlags - { - get - { - if (formatFlags == DateTimeFormatFlags.NotInitialized) - { - // Build the format flags from the data in this DTFI - formatFlags = DateTimeFormatFlags.None; - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth( - MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames( - MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames); - formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID); - } - return (formatFlags); - } - } - - internal Boolean HasForceTwoDigitYears - { - get - { - switch (calendar.ID) - { - // Handle Japanese and Taiwan cases. - // If is y/yy, do not get (year % 100). "y" will print - // year without leading zero. "yy" will print year with two-digit in leading zero. - // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero. - // So year 5 is "05", and year 125 is "125". - // The reason for not doing (year % 100) is for Taiwan calendar. - // If year 125, then output 125 and not 25. - // Note: OS uses "yyyy" for Taiwan calendar by default. - case (CalendarId.JAPAN): - case (CalendarId.TAIWAN): - return true; - } - return false; - } - } - - // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar. - internal Boolean HasYearMonthAdjustment - { - get - { - return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0); - } - } - - // This is a callback that the parser can make back into the DTFI to let it fiddle with special - // cases associated with that culture or calendar. Currently this only has special cases for - // the Hebrew calendar, but this could be extended to other cultures. - // - // The return value is whether the year and month are actually valid for this calendar. - internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) - { - if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) - { - // Special rules to fix up the Hebrew year/month - - // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000. - // E.g. if the year is 5763, we only format as 763. - if (year < 1000) - { - year += 5000; - } - - // Because we need to calculate leap year, we should fall out now for an invalid year. - if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) - { - return false; - } - - // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers. - // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and - // shuffle the other months down. - if (parsedMonthName) - { - if (!Calendar.IsLeapYear(year)) - { - if (month >= 8) - { - month--; - } - else if (month == 7) - { - return false; - } - } - } - } - return true; - } - - // - // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens. - // - [NonSerialized] - private TokenHashValue[] _dtfiTokenHash; - - private const int TOKEN_HASH_SIZE = 199; - private const int SECOND_PRIME = 197; - private const String dateSeparatorOrTimeZoneOffset = "-"; - private const String invariantDateSeparator = "/"; - private const String invariantTimeSeparator = ":"; - - // - // Common Ignorable Symbols - // - internal const String IgnorablePeriod = "."; - internal const String IgnorableComma = ","; - - // - // Year/Month/Day suffixes - // - internal const String CJKYearSuff = "\u5e74"; - internal const String CJKMonthSuff = "\u6708"; - internal const String CJKDaySuff = "\u65e5"; - - internal const String KoreanYearSuff = "\ub144"; - internal const String KoreanMonthSuff = "\uc6d4"; - internal const String KoreanDaySuff = "\uc77c"; - - internal const String KoreanHourSuff = "\uc2dc"; - internal const String KoreanMinuteSuff = "\ubd84"; - internal const String KoreanSecondSuff = "\ucd08"; - - internal const String CJKHourSuff = "\u6642"; - internal const String ChineseHourSuff = "\u65f6"; - - internal const String CJKMinuteSuff = "\u5206"; - internal const String CJKSecondSuff = "\u79d2"; - - internal const String LocalTimeMark = "T"; - - internal const String GMTName = "GMT"; - internal const String ZuluName = "Z"; - - internal const String KoreanLangName = "ko"; - internal const String JapaneseLangName = "ja"; - internal const String EnglishLangName = "en"; - - private static volatile DateTimeFormatInfo s_jajpDTFI; - private static volatile DateTimeFormatInfo s_zhtwDTFI; - - // - // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse - // date string with Japanese era name correctly even when the supplied DTFI - // does not use Japanese calendar. - // The created instance is stored in global s_jajpDTFI. - // - internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() - { - DateTimeFormatInfo temp = s_jajpDTFI; - if (temp == null) - { - temp = new CultureInfo("ja-JP", false).DateTimeFormat; - temp.Calendar = JapaneseCalendar.GetDefaultInstance(); - s_jajpDTFI = temp; - } - return (temp); - } - - // Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse - // date string with era name correctly even when the supplied DTFI - // does not use Taiwan calendar. - // The created instance is stored in global s_zhtwDTFI. - internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() - { - DateTimeFormatInfo temp = s_zhtwDTFI; - if (temp == null) - { - temp = new CultureInfo("zh-TW", false).DateTimeFormat; - temp.Calendar = TaiwanCalendar.GetDefaultInstance(); - s_zhtwDTFI = temp; - } - return (temp); - } - - - // DTFI properties should call this when the setter are called. - private void ClearTokenHashTable() - { - _dtfiTokenHash = null; - formatFlags = DateTimeFormatFlags.NotInitialized; - } - - internal TokenHashValue[] CreateTokenHashTable() - { - TokenHashValue[] temp = _dtfiTokenHash; - if (temp == null) - { - temp = new TokenHashValue[TOKEN_HASH_SIZE]; - - bool koreanLanguage = LanguageName.Equals(KoreanLangName); - - string sep = this.TimeSeparator.Trim(); - if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0); - if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0); - - if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) - { - // - // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like - // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable - // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff - // instead of SEP_HourSuff. - // - InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0); - } - - InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - - // TODO: This ignores similar custom cultures - if (LanguageName.Equals("sq")) - { - // Albanian allows time formats like "12:00.PD" - InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - } - - // CJK suffix - InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0); - InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0); - InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0); - InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0); - InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0); - InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0); - - InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0); - InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0); - - // TODO: This ignores other custom cultures that might want to do something similar - if (koreanLanguage) - { - // Korean suffix - InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0); - InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0); - InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0); - } - - if (LanguageName.Equals("ky")) - { - // For some cultures, the date separator works more like a comma, being allowed before or after any date part - InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0); - } - else - { - InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0); - } - - String[] dateWords = null; - DateTimeFormatInfoScanner scanner = null; - - // We need to rescan the date words since we're always synthetic - scanner = new DateTimeFormatInfoScanner(); - m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this); - // Ensure the formatflags is initialized. - DateTimeFormatFlags flag = FormatFlags; - - // For some cultures, the date separator works more like a comma, being allowed before or after any date part. - // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state. - // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol. - bool useDateSepAsIgnorableSymbol = false; - - String monthPostfix = null; - if (dateWords != null) - { - // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix. - // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix. - for (int i = 0; i < dateWords.Length; i++) - { - switch (dateWords[i][0]) - { - // This is a month postfix - case DateTimeFormatInfoScanner.MonthPostfixChar: - // Get the real month postfix. - monthPostfix = dateWords[i].Substring(1); - // Add the month name + postfix into the token. - AddMonthNames(temp, monthPostfix); - break; - case DateTimeFormatInfoScanner.IgnorableSymbolChar: - String symbol = dateWords[i].Substring(1); - InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0); - if (this.DateSeparator.Trim(null).Equals(symbol)) - { - // The date separator is the same as the ignorable symbol. - useDateSepAsIgnorableSymbol = true; - } - break; - default: - InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0); - // TODO: This ignores similar custom cultures - if (LanguageName.Equals("eu")) - { - // Basque has date words with leading dots - InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0); - } - break; - } - } - } - - if (!useDateSepAsIgnorableSymbol) - { - // Use the normal date separator. - InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0); - } - // Add the regular month names. - AddMonthNames(temp, null); - - // Add the abbreviated month names. - for (int i = 1; i <= 13; i++) - { - InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i); - } - - - if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) - { - for (int i = 1; i <= 13; i++) - { - String str; - str = internalGetMonthName(i, MonthNameStyles.Genitive, false); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) - { - for (int i = 1; i <= 13; i++) - { - String str; - str = internalGetMonthName(i, MonthNameStyles.LeapYear, false); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - for (int i = 0; i < 7; i++) - { - //String str = GetDayOfWeekNames()[i]; - // We have to call public methods here to work with inherited DTFI. - String str = GetDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - - str = GetAbbreviatedDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - } - - int[] eras = calendar.Eras; - for (int i = 1; i <= eras.Length; i++) - { - InsertHash(temp, GetEraName(i), TokenType.EraToken, i); - InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i); - } - - // TODO: This ignores other cultures that might want to do something similar - if (LanguageName.Equals(JapaneseLangName)) - { - // Japanese allows day of week forms like: "(Tue)" - for (int i = 0; i < 7; i++) - { - String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")"; - InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i); - } - if (this.Calendar.GetType() != typeof(JapaneseCalendar)) - { - // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar, - // we will check Japanese Era name as well when the calendar is Gregorian. - DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI(); - for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) - { - InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i); - InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i); - // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. - InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i); - } - } - } - // TODO: This prohibits similar custom cultures, but we hard coded the name - else if (CultureName.Equals("zh-TW")) - { - DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI(); - for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) - { - if (twDtfi.GetEraName(i).Length > 0) - { - InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i); - } - } - } - - InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); - InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); - - // Add invariant month names and day names. - for (int i = 1; i <= 12; i++) - { - String str; - // We have to call public methods here to work with inherited DTFI. - // Insert the month name first, so that they are at the front of abbrevaited - // month names. - str = InvariantInfo.GetMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - str = InvariantInfo.GetAbbreviatedMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - } - - for (int i = 0; i < 7; i++) - { - // We have to call public methods here to work with inherited DTFI. - String str = InvariantInfo.GetDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - - str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i); - InsertHash(temp, str, TokenType.DayOfWeekToken, i); - } - - for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) - { - // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. - InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1); - } - - InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0); - InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0); - InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0); - - InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0); - InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0); - - _dtfiTokenHash = temp; - } - return (temp); - } - - private void AddMonthNames(TokenHashValue[] temp, String monthPostfix) - { - for (int i = 1; i <= 13; i++) - { - String str; - //str = internalGetMonthName(i, MonthNameStyles.Regular, false); - // We have to call public methods here to work with inherited DTFI. - // Insert the month name first, so that they are at the front of abbrevaited - // month names. - str = GetMonthName(i); - if (str.Length > 0) - { - if (monthPostfix != null) - { - // Insert the month name with the postfix first, so it can be matched first. - InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i); - } - else - { - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - str = GetAbbreviatedMonthName(i); - InsertHash(temp, str, TokenType.MonthToken, i); - } - } - - //////////////////////////////////////////////////////////////////////// - // - // Actions: - // Try to parse the current word to see if it is a Hebrew number. - // Tokens will be updated accordingly. - // This is called by the Lexer of DateTime.Parse(). - // - // Unlike most of the functions in this class, the return value indicates - // whether or not it started to parse. The badFormat parameter indicates - // if parsing began, but the format was bad. - // - //////////////////////////////////////////////////////////////////////// - - private static bool TryParseHebrewNumber( -#if CORECLR - ref __DTString str, -#else - ref FormatProvider.__DTString str, -#endif - out Boolean badFormat, - out int number) - { - number = -1; - badFormat = false; - - int i = str.Index; - if (!HebrewNumber.IsDigit(str.Value[i])) - { - // If the current character is not a Hebrew digit, just return false. - // There is no chance that we can parse a valid Hebrew number from here. - return (false); - } - // The current character is a Hebrew digit. Try to parse this word as a Hebrew number. - HebrewNumberParsingContext context = new HebrewNumberParsingContext(0); - HebrewNumberParsingState state; - - do - { - state = HebrewNumber.ParseByChar(str.Value[i++], ref context); - switch (state) - { - case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number. - case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character. - // Break out so that we don't continue to try parse this as a Hebrew number. - return (false); - } - } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)); - - // When we are here, we are either at the end of the string, or we find a valid Hebrew number. - Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber, - "Invalid returned state from HebrewNumber.ParseByChar()"); - - if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) - { - // We reach end of the string but we can't find a terminal state in parsing Hebrew number. - return (false); - } - - // We have found a valid Hebrew number. Update the index. - str.Advance(i - str.Index); - - // Get the final Hebrew number value from the HebrewNumberParsingContext. - number = context.result; - - return (true); - } - - private static bool IsHebrewChar(char ch) - { - return (ch >= '\x0590' && ch <= '\x05ff'); - } - - internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, -#if CORECLR - ref __DTString str) -#else - ref FormatProvider.__DTString str) -#endif - { - tokenType = TokenType.UnknownToken; - tokenValue = 0; - - TokenHashValue value; - Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length"); - - char ch = str.m_current; - bool isLetter = Char.IsLetter(ch); - if (isLetter) - { - ch = this.Culture.TextInfo.ToLower(ch); - if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) - { - bool badFormat; - if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) - { - if (badFormat) - { - tokenType = TokenType.UnknownToken; - return (false); - } - // This is a Hebrew number. - // Do nothing here. TryParseHebrewNumber() will update token accordingly. - tokenType = TokenType.HebrewNumber; - return (true); - } - } - } - - - int hashcode = ch % TOKEN_HASH_SIZE; - int hashProbe = 1 + ch % SECOND_PRIME; - int remaining = str.len - str.Index; - int i = 0; - - TokenHashValue[] hashTable = _dtfiTokenHash; - if (hashTable == null) - { - hashTable = CreateTokenHashTable(); - } - do - { - value = hashTable[hashcode]; - if (value == null) - { - // Not found. - break; - } - // Check this value has the right category (regular token or separator token) that we are looking for. - if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) - { - bool compareStrings = true; - if (isLetter) - { - // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately. - // Also an optimization to avoid string comparison - int nextCharIndex = str.Index + value.tokenString.Length; - if (nextCharIndex > str.len) - { - compareStrings = false; - } - else if (nextCharIndex < str.len) - { - // Check word boundary. The next character should NOT be a letter. - char nextCh = str.Value[nextCharIndex]; - compareStrings = !(Char.IsLetter(nextCh)); - } - } - if (compareStrings && CompareStringIgnoreCaseOptimized(str.Value, str.Index, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length)) - { - tokenType = value.tokenType & TokenMask; - tokenValue = value.tokenValue; - str.Advance(value.tokenString.Length); - return (true); - } - else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) || - (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames)) - { - // For month or day token, we will match the names which have spaces. - int matchStrLen = 0; - if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) - { - tokenType = value.tokenType & TokenMask; - tokenValue = value.tokenValue; - str.Advance(matchStrLen); - return (true); - } - } - } - i++; - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - } while (i < TOKEN_HASH_SIZE); - - return (false); - } - - private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) - { - // Remember the current slot. - TokenHashValue previousNode = hashTable[hashcode]; - - //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert); - // Insert the new node into the current slot. - hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); ; - - while (++pos < TOKEN_HASH_SIZE) - { - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - // Remember this slot - TokenHashValue temp = hashTable[hashcode]; - - if (temp != null && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != ch) - { - continue; - } - // Put the previous slot into this slot. - hashTable[hashcode] = previousNode; - //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode); - if (temp == null) - { - // Done - return; - } - previousNode = temp; - }; - Debug.Assert(false, "The hashtable is full. This should not happen."); - } - - private void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) - { - // The month of the 13th month is allowed to be null, so make sure that we ignore null value here. - if (str == null || str.Length == 0) - { - return; - } - TokenHashValue value; - int i = 0; - // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by - // DateTime.Parse(). - if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) - { - str = str.Trim(null); // Trim white space characters. - // Could have space for separators - if (str.Length == 0) - return; - } - char ch = this.Culture.TextInfo.ToLower(str[0]); - int hashcode = ch % TOKEN_HASH_SIZE; - int hashProbe = 1 + ch % SECOND_PRIME; - do - { - value = hashTable[hashcode]; - if (value == null) - { - //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode); - hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); - return; - } - else - { - // Collision happens. Find another slot. - if (str.Length >= value.tokenString.Length) - { - // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of - // the shorter ones. - if (this.CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length)) - { - if (str.Length > value.tokenString.Length) - { - // The str to be inserted has the same prefix as the current token, and str is longer. - // Insert str into this node, and shift every node behind it. - InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe); - return; - } - else - { - // Same token. If they have different types (regular token vs separator token). Add them. - // If we have the same regular token or separator token in the hash already, do NOT update the hash. - // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash. - - - // - // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , and insert the tokenType into the hash ONLY when we don't have a RegularToken yet. - // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), and insert the tokenType into the hash ONLY when we don't have the SeparatorToken yet. - // - - int nTokenType = (int)tokenType; - int nCurrentTokenTypeInHash = (int)value.tokenType; - - // - // The folowing is the fix for the issue of throwing FormatException when "mar" is passed in string of the short date format dd/MMM/yyyy for es-MX - // - - if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) || - ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0)) - { - value.tokenType |= tokenType; - if (tokenValue != 0) - { - value.tokenValue = tokenValue; - } - } - // The token to be inserted is already in the table. Skip it. - return; - } - } - } - } - //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str); - i++; - hashcode += hashProbe; - if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; - } while (i < TOKEN_HASH_SIZE); - Debug.Assert(false, "The hashtable is full. This should not happen."); - } - - private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2) - { - // Optimize for one character cases which are common due to date and time separators (/ and :) - if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2]) - { - return true; - } - - return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0); - } - - // class DateTimeFormatInfo - - internal class TokenHashValue - { - internal String tokenString; - internal TokenType tokenType; - internal int tokenValue; - - internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) - { - this.tokenString = tokenString; - this.tokenType = tokenType; - this.tokenValue = tokenValue; - } - } - } - -#if !CORECLR - // - // The type of token that will be returned by DateTimeFormatInfo.Tokenize(). - // - internal enum TokenType - { - // The valid token should start from 1. - - // Regular tokens. The range is from 0x00 ~ 0xff. - NumberToken = 1, // The number. E.g. "12" - YearNumberToken = 2, // The number which is considered as year number, which has 3 or more digits. E.g. "2003" - Am = 3, // AM timemark. E.g. "AM" - Pm = 4, // PM timemark. E.g. "PM" - MonthToken = 5, // A word (or words) that represents a month name. E.g. "March" - EndOfString = 6, // End of string - DayOfWeekToken = 7, // A word (or words) that represents a day of week name. E.g. "Monday" or "Mon" - TimeZoneToken = 8, // A word that represents a timezone name. E.g. "GMT" - EraToken = 9, // A word that represents a era name. E.g. "A.D." - DateWordToken = 10, // A word that can appear in a DateTime string, but serves no parsing semantics. E.g. "de" in Spanish culture. - UnknownToken = 11, // An unknown word, which signals an error in parsing. - HebrewNumber = 12, // A number that is composed of Hebrew text. Hebrew calendar uses Hebrew digits for year values, month values, and day values. - JapaneseEraToken = 13, // Era name for JapaneseCalendar - TEraToken = 14, // Era name for TaiwanCalendar - IgnorableSymbol = 15, // A separator like "," that is equivalent to whitespace - - - // Separator tokens. - SEP_Unk = 0x100, // Unknown separator. - SEP_End = 0x200, // The end of the parsing string. - SEP_Space = 0x300, // Whitespace (including comma). - SEP_Am = 0x400, // AM timemark. E.g. "AM" - SEP_Pm = 0x500, // PM timemark. E.g. "PM" - SEP_Date = 0x600, // date separator. E.g. "/" - SEP_Time = 0x700, // time separator. E.g. ":" - SEP_YearSuff = 0x800, // Chinese/Japanese/Korean year suffix. - SEP_MonthSuff = 0x900, // Chinese/Japanese/Korean month suffix. - SEP_DaySuff = 0xa00, // Chinese/Japanese/Korean day suffix. - SEP_HourSuff = 0xb00, // Chinese/Japanese/Korean hour suffix. - SEP_MinuteSuff = 0xc00, // Chinese/Japanese/Korean minute suffix. - SEP_SecondSuff = 0xd00, // Chinese/Japanese/Korean second suffix. - SEP_LocalTimeMark = 0xe00, // 'T', used in ISO 8601 format. - SEP_DateOrOffset = 0xf00, // '-' which could be a date separator or start of a time zone offset - - RegularTokenMask = 0x00ff, - SeparatorTokenMask = 0xff00, - } -#endif - -} diff --git a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs b/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs deleted file mode 100644 index ddf7d7e..0000000 --- a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs +++ /dev/null @@ -1,742 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -//////////////////////////////////////////////////////////////////////////// -// -// DateTimeFormatInfoScanner -// -// Scan a specified DateTimeFormatInfo to search for data used in DateTime.Parse() -// -// The data includes: -// -// DateWords: such as "de" used in es-ES (Spanish) LongDatePattern. -// Postfix: such as "ta" used in fi-FI after the month name. -// -// This class is shared among mscorlib.dll and sysglobl.dll. -// Use conditional CULTURE_AND_REGIONINFO_BUILDER_ONLY to differentiate between -// methods for mscorlib.dll and sysglobl.dll. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Globalization; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace System.Globalization -{ - -#if CORECLR - using StringStringDictionary = Dictionary; - using StringList = List; -#else - using StringStringDictionary = LowLevelDictionary; - using StringList = LowLevelList; -#endif - - // - // from LocaleEx.txt header - // - //; IFORMATFLAGS - //; Parsing/formatting flags. - internal enum FORMATFLAGS - { - None = 0x00000000, - UseGenitiveMonth = 0x00000001, - UseLeapYearMonth = 0x00000002, - UseSpacesInMonthNames = 0x00000004, - UseHebrewParsing = 0x00000008, - UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names. - UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers. - } - - internal enum CalendarId : ushort - { - UNINITIALIZED_VALUE = 0, - GREGORIAN = 1, // Gregorian (localized) calendar - GREGORIAN_US = 2, // Gregorian (U.S.) calendar - JAPAN = 3, // Japanese Emperor Era calendar - /* SSS_WARNINGS_OFF */ - TAIWAN = 4, // Taiwan Era calendar /* SSS_WARNINGS_ON */ - KOREA = 5, // Korean Tangun Era calendar - HIJRI = 6, // Hijri (Arabic Lunar) calendar - THAI = 7, // Thai calendar - HEBREW = 8, // Hebrew (Lunar) calendar - GREGORIAN_ME_FRENCH = 9, // Gregorian Middle East French calendar - GREGORIAN_ARABIC = 10, // Gregorian Arabic calendar - GREGORIAN_XLIT_ENGLISH = 11, // Gregorian Transliterated English calendar - GREGORIAN_XLIT_FRENCH = 12, - // Note that all calendars after this point are MANAGED ONLY for now. - JULIAN = 13, - JAPANESELUNISOLAR = 14, - CHINESELUNISOLAR = 15, - SAKA = 16, // reserved to match Office but not implemented in our code - LUNAR_ETO_CHN = 17, // reserved to match Office but not implemented in our code - LUNAR_ETO_KOR = 18, // reserved to match Office but not implemented in our code - LUNAR_ETO_ROKUYOU = 19, // reserved to match Office but not implemented in our code - KOREANLUNISOLAR = 20, - TAIWANLUNISOLAR = 21, - PERSIAN = 22, - UMALQURA = 23, - LAST_CALENDAR = 23 // Last calendar ID - } - - internal class DateTimeFormatInfoScanner - { - // Special prefix-like flag char in DateWord array. - - // Use char in PUA area since we won't be using them in real data. - // The char used to tell a read date word or a month postfix. A month postfix - // is "ta" in the long date pattern like "d. MMMM'ta 'yyyy" for fi-FI. - // In this case, it will be stored as "\xfffeta" in the date word array. - internal const char MonthPostfixChar = '\xe000'; - - // Add ignorable symbol in a DateWord array. - - // hu-HU has: - // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd - // long date pattern: yyyy. MMMM d. - // Here, "." is the date separator (derived from short date pattern). However, - // "." also appear at the end of long date pattern. In this case, we just - // "." as ignorable symbol so that the DateTime.Parse() state machine will not - // treat the additional date separator at the end of y,m,d pattern as an error - // condition. - internal const char IgnorableSymbolChar = '\xe001'; - - // Known CJK suffix - 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"; - - // The collection fo date words & postfix. - internal StringList m_dateWords = new StringList(); - // Hashtable for the known words. - private static volatile StringStringDictionary s_knownWords; - - static StringStringDictionary KnownWords - { - get - { - if (s_knownWords == null) - { - StringStringDictionary temp = new StringStringDictionary(); - // Add known words into the hash table. - - // Skip these special symbols. - temp.Add("/", String.Empty); - temp.Add("-", String.Empty); - temp.Add(".", String.Empty); - // Skip known CJK suffixes. - temp.Add(CJKYearSuff, String.Empty); - temp.Add(CJKMonthSuff, String.Empty); - temp.Add(CJKDaySuff, String.Empty); - temp.Add(KoreanYearSuff, String.Empty); - temp.Add(KoreanMonthSuff, String.Empty); - temp.Add(KoreanDaySuff, String.Empty); - temp.Add(KoreanHourSuff, String.Empty); - temp.Add(KoreanMinuteSuff, String.Empty); - temp.Add(KoreanSecondSuff, String.Empty); - temp.Add(CJKHourSuff, String.Empty); - temp.Add(ChineseHourSuff, String.Empty); - temp.Add(CJKMinuteSuff, String.Empty); - temp.Add(CJKSecondSuff, String.Empty); - - s_knownWords = temp; - } - return (s_knownWords); - } - } - - //////////////////////////////////////////////////////////////////////////// - // - // Parameters: - // pattern: The pattern to be scanned. - // currentIndex: the current index to start the scan. - // - // Returns: - // Return the index with the first character that is a letter, which will - // be the start of a date word. - // Note that the index can be pattern.Length if we reach the end of the string. - // - //////////////////////////////////////////////////////////////////////////// - internal static int SkipWhiteSpacesAndNonLetter(String pattern, int currentIndex) - { - while (currentIndex < pattern.Length) - { - char ch = pattern[currentIndex]; - if (ch == '\\') - { - // Escaped character. Look ahead one character. - currentIndex++; - if (currentIndex < pattern.Length) - { - ch = pattern[currentIndex]; - if (ch == '\'') - { - // Skip the leading single quote. We will - // stop at the first letter. - continue; - } - // Fall thru to check if this is a letter. - } - else - { - // End of string - break; - } - } - if (Char.IsLetter(ch) || ch == '\'' || ch == '.') - { - break; - } - // Skip the current char since it is not a letter. - currentIndex++; - } - return (currentIndex); - } - - //////////////////////////////////////////////////////////////////////////// - // - // A helper to add the found date word or month postfix into ArrayList for date words. - // - // Parameters: - // formatPostfix: What kind of postfix this is. - // Possible values: - // null: This is a regular date word - // "MMMM": month postfix - // word: The date word or postfix to be added. - // - //////////////////////////////////////////////////////////////////////////// - internal void AddDateWordOrPostfix(String formatPostfix, String str) - { - if (str.Length > 0) - { - // Some cultures use . like an abbreviation - if (str.Equals(".")) - { - AddIgnorableSymbols("."); - return; - } - String words; - if (KnownWords.TryGetValue(str, out words) == false) - { - if (m_dateWords == null) - { - m_dateWords = new StringList(); - } - if (formatPostfix == "MMMM") - { - // Add the word into the ArrayList as "\xfffe" + real month postfix. - String temp = MonthPostfixChar + str; - if (!m_dateWords.Contains(temp)) - { - m_dateWords.Add(temp); - } - } - else - { - if (!m_dateWords.Contains(str)) - { - m_dateWords.Add(str); - } - if (str[str.Length - 1] == '.') - { - // Old version ignore the trialing dot in the date words. Support this as well. - String strWithoutDot = str.Substring(0, str.Length - 1); - if (!m_dateWords.Contains(strWithoutDot)) - { - m_dateWords.Add(strWithoutDot); - } - } - } - } - } - } - - //////////////////////////////////////////////////////////////////////////// - // - // Scan the pattern from the specified index and add the date word/postfix - // when appropriate. - // - // Parameters: - // pattern: The pattern to be scanned. - // index: The starting index to be scanned. - // formatPostfix: The kind of postfix to be scanned. - // Possible values: - // null: This is a regular date word - // "MMMM": month postfix - // - // - //////////////////////////////////////////////////////////////////////////// - internal int AddDateWords(String pattern, int index, String formatPostfix) - { - // Skip any whitespaces so we will start from a letter. - int newIndex = SkipWhiteSpacesAndNonLetter(pattern, index); - if (newIndex != index && formatPostfix != null) - { - // There are whitespaces. This will not be a postfix. - formatPostfix = null; - } - index = newIndex; - - // This is the first char added into dateWord. - // Skip all non-letter character. We will add the first letter into DateWord. - StringBuilder dateWord = new StringBuilder(); - // We assume that date words should start with a letter. - // Skip anything until we see a letter. - - while (index < pattern.Length) - { - char ch = pattern[index]; - if (ch == '\'') - { - // We have seen the end of quote. Add the word if we do not see it before, - // and break the while loop. - AddDateWordOrPostfix(formatPostfix, dateWord.ToString()); - index++; - break; - } - else if (ch == '\\') - { - // - // Escaped character. Look ahead one character - // - - // Skip escaped backslash. - index++; - if (index < pattern.Length) - { - dateWord.Append(pattern[index]); - index++; - } - } - else if (Char.IsWhiteSpace(ch)) - { - // Found a whitespace. We have to add the current date word/postfix. - AddDateWordOrPostfix(formatPostfix, dateWord.ToString()); - if (formatPostfix != null) - { - // Done with postfix. The rest will be regular date word. - formatPostfix = null; - } - // Reset the dateWord. - dateWord.Length = 0; - index++; - } - else - { - dateWord.Append(ch); - index++; - } - } - return (index); - } - - //////////////////////////////////////////////////////////////////////////// - // - // A simple helper to find the repeat count for a specified char. - // - //////////////////////////////////////////////////////////////////////////// - internal static int ScanRepeatChar(String pattern, char ch, int index, out int count) - { - count = 1; - while (++index < pattern.Length && pattern[index] == ch) - { - count++; - } - // Return the updated position. - return (index); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Add the text that is a date separator but is treated like ignroable symbol. - // E.g. - // hu-HU has: - // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd - // long date pattern: yyyy. MMMM d. - // Here, "." is the date separator (derived from short date pattern). However, - // "." also appear at the end of long date pattern. In this case, we just - // "." as ignorable symbol so that the DateTime.Parse() state machine will not - // treat the additional date separator at the end of y,m,d pattern as an error - // condition. - // - //////////////////////////////////////////////////////////////////////////// - - internal void AddIgnorableSymbols(String text) - { - if (m_dateWords == null) - { - // Create the date word array. - m_dateWords = new StringList(); - } - // Add the ignorable symbol into the ArrayList. - String temp = IgnorableSymbolChar + text; - if (!m_dateWords.Contains(temp)) - { - m_dateWords.Add(temp); - } - } - - - // - // Flag used to trace the date patterns (yy/yyyyy/M/MM/MMM/MMM/d/dd) that we have seen. - // - private enum FoundDatePattern - { - None = 0x0000, - FoundYearPatternFlag = 0x0001, - FoundMonthPatternFlag = 0x0002, - FoundDayPatternFlag = 0x0004, - FoundYMDPatternFlag = 0x0007, // FoundYearPatternFlag | FoundMonthPatternFlag | FoundDayPatternFlag; - } - - // Check if we have found all of the year/month/day pattern. - private FoundDatePattern _ymdFlags = FoundDatePattern.None; - - - //////////////////////////////////////////////////////////////////////////// - // - // Given a date format pattern, scan for date word or postfix. - // - // A date word should be always put in a single quoted string. And it will - // start from a letter, so whitespace and symbols will be ignored before - // the first letter. - // - // Examples of date word: - // 'de' in es-SP: dddd, dd' de 'MMMM' de 'yyyy - // "\x0443." in bg-BG: dd.M.yyyy '\x0433.' - // - // Example of postfix: - // month postfix: - // "ta" in fi-FI: d. MMMM'ta 'yyyy - // Currently, only month postfix is supported. - // - // Usage: - // Always call this with Framework-style pattern, instead of Windows style pattern. - // Windows style pattern uses '' for single quote, while .NET uses \' - // - //////////////////////////////////////////////////////////////////////////// - internal void ScanDateWord(String pattern) - { - // Check if we have found all of the year/month/day pattern. - _ymdFlags = FoundDatePattern.None; - - int i = 0; - while (i < pattern.Length) - { - char ch = pattern[i]; - int chCount; - - switch (ch) - { - case '\'': - // Find a beginning quote. Search until the end quote. - i = AddDateWords(pattern, i + 1, null); - break; - case 'M': - i = ScanRepeatChar(pattern, 'M', i, out chCount); - if (chCount >= 4) - { - if (i < pattern.Length && pattern[i] == '\'') - { - i = AddDateWords(pattern, i + 1, "MMMM"); - } - } - _ymdFlags |= FoundDatePattern.FoundMonthPatternFlag; - break; - case 'y': - i = ScanRepeatChar(pattern, 'y', i, out chCount); - _ymdFlags |= FoundDatePattern.FoundYearPatternFlag; - break; - case 'd': - i = ScanRepeatChar(pattern, 'd', i, out chCount); - if (chCount <= 2) - { - // Only count "d" & "dd". - // ddd, dddd are day names. Do not count them. - _ymdFlags |= FoundDatePattern.FoundDayPatternFlag; - } - break; - case '\\': - // Found a escaped char not in a quoted string. Skip the current backslash - // and its next character. - i += 2; - break; - case '.': - if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag) - { - // If we find a dot immediately after the we have seen all of the y, m, d pattern. - // treat it as a ignroable symbol. Check for comments in AddIgnorableSymbols for - // more details. - AddIgnorableSymbols("."); - _ymdFlags = FoundDatePattern.None; - } - i++; - break; - default: - if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag && !Char.IsWhiteSpace(ch)) - { - // We are not seeing "." after YMD. Clear the flag. - _ymdFlags = FoundDatePattern.None; - } - // We are not in quote. Skip the current character. - i++; - break; - } - } - } - - //////////////////////////////////////////////////////////////////////////// - // - // Given a DTFI, get all of the date words from date patterns and time patterns. - // - //////////////////////////////////////////////////////////////////////////// - - internal String[] GetDateWordsOfDTFI(DateTimeFormatInfo dtfi) - { - // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix. - String[] datePatterns = dtfi.GetAllDateTimePatterns('D'); - int i; - - // Scan the long date patterns - for (i = 0; i < datePatterns.Length; i++) - { - ScanDateWord(datePatterns[i]); - } - - // Scan the short date patterns - datePatterns = dtfi.GetAllDateTimePatterns('d'); - for (i = 0; i < datePatterns.Length; i++) - { - ScanDateWord(datePatterns[i]); - } - // Scan the YearMonth patterns. - datePatterns = dtfi.GetAllDateTimePatterns('y'); - for (i = 0; i < datePatterns.Length; i++) - { - ScanDateWord(datePatterns[i]); - } - - // Scan the month/day pattern - ScanDateWord(dtfi.MonthDayPattern); - - // Scan the long time patterns. - datePatterns = dtfi.GetAllDateTimePatterns('T'); - for (i = 0; i < datePatterns.Length; i++) - { - ScanDateWord(datePatterns[i]); - } - - // Scan the short time patterns. - datePatterns = dtfi.GetAllDateTimePatterns('t'); - for (i = 0; i < datePatterns.Length; i++) - { - ScanDateWord(datePatterns[i]); - } - - String[] result = null; - if (m_dateWords != null && m_dateWords.Count > 0) - { - result = new String[m_dateWords.Count]; - for (i = 0; i < m_dateWords.Count; i++) - { - result[i] = m_dateWords[i]; - } - } - return (result); - } - - - //////////////////////////////////////////////////////////////////////////// - // - // Scan the month names to see if genitive month names are used, and return - // the format flag. - // - //////////////////////////////////////////////////////////////////////////// - internal static FORMATFLAGS GetFormatFlagGenitiveMonth(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames) - { - // If we have different names in regular and genitive month names, use genitive month flag. - return ((!EqualStringArrays(monthNames, genitveMonthNames) || !EqualStringArrays(abbrevMonthNames, genetiveAbbrevMonthNames)) - ? FORMATFLAGS.UseGenitiveMonth : 0); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Scan the month names to see if spaces are used or start with a digit, and return the format flag - // - //////////////////////////////////////////////////////////////////////////// - internal static FORMATFLAGS GetFormatFlagUseSpaceInMonthNames(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames) - { - FORMATFLAGS formatFlags = 0; - formatFlags |= (ArrayElementsBeginWithDigit(monthNames) || - ArrayElementsBeginWithDigit(genitveMonthNames) || - ArrayElementsBeginWithDigit(abbrevMonthNames) || - ArrayElementsBeginWithDigit(genetiveAbbrevMonthNames) - ? FORMATFLAGS.UseDigitPrefixInTokens : 0); - - formatFlags |= (ArrayElementsHaveSpace(monthNames) || - ArrayElementsHaveSpace(genitveMonthNames) || - ArrayElementsHaveSpace(abbrevMonthNames) || - ArrayElementsHaveSpace(genetiveAbbrevMonthNames) - ? FORMATFLAGS.UseSpacesInMonthNames : 0); - return (formatFlags); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Scan the day names and set the correct format flag. - // - //////////////////////////////////////////////////////////////////////////// - internal static FORMATFLAGS GetFormatFlagUseSpaceInDayNames(String[] dayNames, String[] abbrevDayNames) - { - return ((ArrayElementsHaveSpace(dayNames) || - ArrayElementsHaveSpace(abbrevDayNames)) - ? FORMATFLAGS.UseSpacesInDayNames : 0); - } - - //////////////////////////////////////////////////////////////////////////// - // - // Check the calendar to see if it is HebrewCalendar and set the Hebrew format flag if necessary. - // - //////////////////////////////////////////////////////////////////////////// - internal static FORMATFLAGS GetFormatFlagUseHebrewCalendar(int calID) - { - return (calID == (int)CalendarId.HEBREW ? - FORMATFLAGS.UseHebrewParsing | FORMATFLAGS.UseLeapYearMonth : 0); - } - - - //----------------------------------------------------------------------------- - // EqualStringArrays - // compares two string arrays and return true if all elements of the first - // array equals to all elmentsof the second array. - // otherwise it returns false. - //----------------------------------------------------------------------------- - - private static bool EqualStringArrays(string[] array1, string[] array2) - { - // Shortcut if they're the same array - if (array1 == array2) - { - return true; - } - - // This is effectively impossible - if (array1.Length != array2.Length) - { - return false; - } - - // Check each string - for (int i = 0; i < array1.Length; i++) - { - if (!array1[i].Equals(array2[i])) - { - return false; - } - } - - return true; - } - - //----------------------------------------------------------------------------- - // ArrayElementsHaveSpace - // It checks all input array elements if any of them has space character - // returns true if found space character in one of the array elements. - // otherwise returns false. - //----------------------------------------------------------------------------- - - private static bool ArrayElementsHaveSpace(string[] array) - { - for (int i = 0; i < array.Length; i++) - { - // it is faster to check for space character manually instead of calling IndexOf - // so we don't have to go to native code side. - for (int j = 0; j < array[i].Length; j++) - { - if (Char.IsWhiteSpace(array[i][j])) - { - return true; - } - } - } - - return false; - } - - - //////////////////////////////////////////////////////////////////////////// - // - // Check if any element of the array start with a digit. - // - //////////////////////////////////////////////////////////////////////////// - private static bool ArrayElementsBeginWithDigit(string[] array) - { - for (int i = 0; i < array.Length; i++) - { - // it is faster to check for space character manually instead of calling IndexOf - // so we don't have to go to native code side. - if (array[i].Length > 0 && - array[i][0] >= '0' && array[i][0] <= '9') - { - int index = 1; - while (index < array[i].Length && array[i][index] >= '0' && array[i][index] <= '9') - { - // Skip other digits. - index++; - } - if (index == array[i].Length) - { - return (false); - } - - if (index == array[i].Length - 1) - { - // Skip known CJK month suffix. - // CJK uses month name like "1\x6708", since \x6708 is a known month suffix, - // we don't need the UseDigitPrefixInTokens since it is slower. - switch (array[i][index]) - { - case '\x6708': // CJKMonthSuff - case '\xc6d4': // KoreanMonthSuff - return (false); - } - } - - if (index == array[i].Length - 4) - { - // Skip known CJK month suffix. - // Starting with Windows 8, the CJK months for some cultures looks like: "1' \x6708'" - // instead of just "1\x6708" - if (array[i][index] == '\'' && array[i][index + 1] == ' ' && - array[i][index + 2] == '\x6708' && array[i][index + 3] == '\'') - { - return (false); - } - } - return (true); - } - } - - return false; - } - } -} - diff --git a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeParse.cs b/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeParse.cs deleted file mode 100644 index 6056b5c..0000000 --- a/src/coreclr/src/mscorlib/src/System/Globalization/DateTimeParse.cs +++ /dev/null @@ -1,5694 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -//////////////////////////////////////////////////////////////////////////// -// -// -// Purpose: This class is called by DateTime to parse a date/time string. -// -//////////////////////////////////////////////////////////////////////////// - -namespace System -{ - using System; - using System.Text; - using System.Globalization; - using System.Threading; - using System.Collections; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Runtime.Versioning; - using System.Security; - using System.Diagnostics; - using System.Diagnostics.Contracts; - - //////////////////////////////////////////////////////////////////////// - - //This class contains only static members - - internal static - class DateTimeParse - { - internal const Int32 MaxDateTimeNumberDigits = 8; - - internal delegate bool MatchNumberDelegate(ref __DTString str, int digitLen, out int result); - - internal static MatchNumberDelegate m_hebrewNumberParser = new MatchNumberDelegate(DateTimeParse.MatchHebrewDigits); - - internal static DateTime ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - result.Init(); - if (TryParseExact(s, format, dtfi, style, ref result)) - { - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - internal static DateTime ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - offset = TimeSpan.Zero; - result.Init(); - result.flags |= ParseFlags.CaptureOffset; - if (TryParseExact(s, format, dtfi, style, ref result)) - { - offset = result.timeZoneOffset; - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result) - { - result = DateTime.MinValue; - DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. - resultData.Init(); - if (TryParseExact(s, format, dtfi, style, ref resultData)) - { - result = resultData.parsedDate; - return true; - } - return false; - } - - internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset) - { - result = DateTime.MinValue; - offset = TimeSpan.Zero; - DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. - resultData.Init(); - resultData.flags |= ParseFlags.CaptureOffset; - if (TryParseExact(s, format, dtfi, style, ref resultData)) - { - result = resultData.parsedDate; - offset = resultData.timeZoneOffset; - return true; - } - return false; - } - - internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) - { - if (s == null) - { - result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(s)); - return false; - } - if (format == null) - { - result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(format)); - return false; - } - if (s.Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (format.Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - - Debug.Assert(dtfi != null, "dtfi == null"); - - return DoStrictParse(s, format, style, dtfi, ref result); - } - - internal static DateTime ParseExactMultiple(String s, String[] formats, - DateTimeFormatInfo dtfi, DateTimeStyles style) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - result.Init(); - if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) - { - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - - internal static DateTime ParseExactMultiple(String s, String[] formats, - DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - offset = TimeSpan.Zero; - result.Init(); - result.flags |= ParseFlags.CaptureOffset; - if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) - { - offset = result.timeZoneOffset; - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - internal static bool TryParseExactMultiple(String s, String[] formats, - DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset) - { - result = DateTime.MinValue; - offset = TimeSpan.Zero; - DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. - resultData.Init(); - resultData.flags |= ParseFlags.CaptureOffset; - if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) - { - result = resultData.parsedDate; - offset = resultData.timeZoneOffset; - return true; - } - return false; - } - - - internal static bool TryParseExactMultiple(String s, String[] formats, - DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result) - { - result = DateTime.MinValue; - DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. - resultData.Init(); - if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) - { - result = resultData.parsedDate; - return true; - } - return false; - } - - internal static bool TryParseExactMultiple(String s, String[] formats, - DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) - { - if (s == null) - { - result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(s)); - return false; - } - if (formats == null) - { - result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(formats)); - return false; - } - - if (s.Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (formats.Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - - Debug.Assert(dtfi != null, "dtfi == null"); - - // - // Do a loop through the provided formats and see if we can parse succesfully in - // one of the formats. - // - for (int i = 0; i < formats.Length; i++) - { - if (formats[i] == null || formats[i].Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - // Create a new result each time to ensure the runs are independent. Carry through - // flags from the caller and return the result. - DateTimeResult innerResult = new DateTimeResult(); // The buffer to store the parsing result. - innerResult.Init(); - innerResult.flags = result.flags; - if (TryParseExact(s, formats[i], dtfi, style, ref innerResult)) - { - result.parsedDate = innerResult.parsedDate; - result.timeZoneOffset = innerResult.timeZoneOffset; - return (true); - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - - //////////////////////////////////////////////////////////////////////////// - // Date Token Types - // - // Following is the set of tokens that can be generated from a date - // string. Notice that the legal set of trailing separators have been - // folded in with the date number, and month name tokens. This set - // of tokens is chosen to reduce the number of date parse states. - // - //////////////////////////////////////////////////////////////////////////// - - internal enum DTT : int - { - End = 0, // '\0' - NumEnd = 1, // Num[ ]*[\0] - NumAmpm = 2, // Num[ ]+AmPm - NumSpace = 3, // Num[ ]+^[Dsep|Tsep|'0\'] - NumDatesep = 4, // Num[ ]*Dsep - NumTimesep = 5, // Num[ ]*Tsep - MonthEnd = 6, // Month[ ]*'\0' - MonthSpace = 7, // Month[ ]+^[Dsep|Tsep|'\0'] - MonthDatesep = 8, // Month[ ]*Dsep - NumDatesuff = 9, // Month[ ]*DSuff - NumTimesuff = 10, // Month[ ]*TSuff - DayOfWeek = 11, // Day of week name - YearSpace = 12, // Year+^[Dsep|Tsep|'0\'] - YearDateSep = 13, // Year+Dsep - YearEnd = 14, // Year+['\0'] - TimeZone = 15, // timezone name - Era = 16, // era name - NumUTCTimeMark = 17, // Num + 'Z' - // When you add a new token which will be in the - // state table, add it after NumLocalTimeMark. - Unk = 18, // unknown - NumLocalTimeMark = 19, // Num + 'T' - Max = 20, // marker - } - - internal enum TM - { - NotSet = -1, - AM = 0, - PM = 1, - } - - - //////////////////////////////////////////////////////////////////////////// - // - // DateTime parsing state enumeration (DS.*) - // - //////////////////////////////////////////////////////////////////////////// - - internal enum DS - { - BEGIN = 0, - N = 1, // have one number - NN = 2, // have two numbers - - // The following are known to be part of a date - - D_Nd = 3, // date string: have number followed by date separator - D_NN = 4, // date string: have two numbers - D_NNd = 5, // date string: have two numbers followed by date separator - - D_M = 6, // date string: have a month - D_MN = 7, // date string: have a month and a number - D_NM = 8, // date string: have a number and a month - D_MNd = 9, // date string: have a month and number followed by date separator - D_NDS = 10, // date string: have one number followed a date suffix. - - D_Y = 11, // date string: have a year. - D_YN = 12, // date string: have a year and a number - D_YNd = 13, // date string: have a year and a number and a date separator - D_YM = 14, // date string: have a year and a month - D_YMd = 15, // date string: have a year and a month and a date separator - D_S = 16, // have numbers followed by a date suffix. - T_S = 17, // have numbers followed by a time suffix. - - // The following are known to be part of a time - - T_Nt = 18, // have num followed by time separator - T_NNt = 19, // have two numbers followed by time separator - - - ERROR = 20, - - // The following are terminal states. These all have an action - // associated with them; and transition back to BEGIN. - - DX_NN = 21, // day from two numbers - DX_NNN = 22, // day from three numbers - DX_MN = 23, // day from month and one number - DX_NM = 24, // day from month and one number - DX_MNN = 25, // day from month and two numbers - DX_DS = 26, // a set of date suffixed numbers. - DX_DSN = 27, // day from date suffixes and one number. - DX_NDS = 28, // day from one number and date suffixes . - DX_NNDS = 29, // day from one number and date suffixes . - - DX_YNN = 30, // date string: have a year and two number - DX_YMN = 31, // date string: have a year, a month, and a number. - DX_YN = 32, // date string: have a year and one number - DX_YM = 33, // date string: have a year, a month. - TX_N = 34, // time from one number (must have ampm) - TX_NN = 35, // time from two numbers - TX_NNN = 36, // time from three numbers - TX_TS = 37, // a set of time suffixed numbers. - DX_NNY = 38, - } - - //////////////////////////////////////////////////////////////////////////// - // - // NOTE: The following state machine table is dependent on the order of the - // DS and DTT enumerations. - // - // For each non terminal state, the following table defines the next state - // for each given date token type. - // - //////////////////////////////////////////////////////////////////////////// - - // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCTimeMark - private static DS[][] dateParsingStates = { -// DS.BEGIN // DS.BEGIN -new DS[] { DS.BEGIN, DS.ERROR, DS.TX_N, DS.N, DS.D_Nd, DS.T_Nt, DS.ERROR, DS.D_M, DS.D_M, DS.D_S, DS.T_S, DS.BEGIN, DS.D_Y, DS.D_Y, DS.ERROR, DS.BEGIN, DS.BEGIN, DS.ERROR}, - -// DS.N // DS.N -new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_NM, DS.D_MNd, DS.D_NDS, DS.ERROR, DS.N, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.N, DS.N, DS.ERROR}, - -// DS.NN // DS.NN -new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.T_S, DS.NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.NN, DS.NN, DS.ERROR}, - -// DS.D_Nd // DS.D_Nd -new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.D_NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.D_Nd, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.ERROR, DS.D_Nd, DS.ERROR}, - -// DS.D_NN // DS.D_NN -new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NN, DS.ERROR}, - -// DS.D_NNd // DS.D_NNd -new DS[] { DS.ERROR, DS.DX_NNN, DS.DX_NNN, DS.DX_NNN, DS.ERROR, DS.ERROR, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.ERROR, DS.D_NNd, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NNd, DS.ERROR}, - -// DS.D_M // DS.D_M -new DS[] { DS.ERROR, DS.DX_MN, DS.ERROR, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_M, DS.D_YM, DS.D_YMd, DS.DX_YM, DS.ERROR, DS.D_M, DS.ERROR}, - -// DS.D_MN // DS.D_MN -new DS[] { DS.DX_MN, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_MN, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MN, DS.ERROR}, - -// DS.D_NM // DS.D_NM -new DS[] { DS.DX_NM, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NM, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_NM, DS.ERROR}, - -// DS.D_MNd // DS.D_MNd -new DS[] { DS.ERROR, DS.DX_MNN, DS.ERROR, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_MNd, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MNd, DS.ERROR}, - -// DS.D_NDS, // DS.D_NDS, -new DS[] { DS.DX_NDS,DS.DX_NNDS, DS.DX_NNDS, DS.DX_NNDS, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.T_S, DS.D_NDS, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.ERROR}, - -// DS.D_Y // DS.D_Y -new DS[] { DS.ERROR, DS.DX_YN, DS.ERROR, DS.D_YN, DS.D_YNd, DS.ERROR, DS.DX_YM, DS.D_YM, DS.D_YMd, DS.D_YM, DS.ERROR, DS.D_Y, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_Y, DS.ERROR}, - -// DS.D_YN // DS.D_YN -new DS[] { DS.DX_YN, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR}, - -// DS.D_YNd // DS.D_YNd -new DS[] { DS.ERROR, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR}, - -// DS.D_YM // DS.D_YM -new DS[] { DS.DX_YM, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR}, - -// DS.D_YMd // DS.D_YMd -new DS[] { DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR}, - -// DS.D_S // DS.D_S -new DS[] { DS.DX_DS, DS.DX_DSN, DS.TX_N, DS.T_Nt, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.D_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.ERROR}, - -// DS.T_S // DS.T_S -new DS[] { DS.TX_TS, DS.TX_TS, DS.TX_TS, DS.T_Nt, DS.D_Nd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_S, DS.ERROR}, - -// DS.T_Nt // DS.T_Nt -new DS[] { DS.ERROR, DS.TX_NN, DS.TX_NN, DS.TX_NN, DS.ERROR, DS.T_NNt, DS.DX_NM, DS.D_NM, DS.ERROR, DS.ERROR, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_Nt, DS.T_Nt, DS.TX_NN}, - -// DS.T_NNt // DS.T_NNt -new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_NNt, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_NNt, DS.T_NNt, DS.TX_NNN}, -}; - // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCMark - - internal const String GMTName = "GMT"; - internal const String ZuluName = "Z"; - - // - // Search from the index of str at str.Index to see if the target string exists in the str. - // - private static bool MatchWord(ref __DTString str, String target) - { - int length = target.Length; - if (length > (str.Value.Length - str.Index)) - { - return false; - } - - if (str.CompareInfo.Compare(str.Value, str.Index, length, - target, 0, length, CompareOptions.IgnoreCase) != 0) - { - return (false); - } - - int nextCharIndex = str.Index + target.Length; - - if (nextCharIndex < str.Value.Length) - { - char nextCh = str.Value[nextCharIndex]; - if (Char.IsLetter(nextCh)) - { - return (false); - } - } - str.Index = nextCharIndex; - if (str.Index < str.len) - { - str.m_current = str.Value[str.Index]; - } - - return (true); - } - - - // - // Check the word at the current index to see if it matches GMT name or Zulu name. - // - private static bool GetTimeZoneName(ref __DTString str) - { - if (MatchWord(ref str, GMTName)) - { - return (true); - } - - if (MatchWord(ref str, ZuluName)) - { - return (true); - } - - return (false); - } - - internal static bool IsDigit(char ch) - { - return (ch >= '0' && ch <= '9'); - } - - - /*=================================ParseFraction========================== - **Action: Starting at the str.Index, which should be a decimal symbol. - ** if the current character is a digit, parse the remaining - ** numbers as fraction. For example, if the sub-string starting at str.Index is "123", then - ** the method will return 0.123 - **Returns: The fraction number. - **Arguments: - ** str the parsing string - **Exceptions: - ============================================================================*/ - - private static bool ParseFraction(ref __DTString str, out double result) - { - result = 0; - double decimalBase = 0.1; - int digits = 0; - char ch; - while (str.GetNext() - && IsDigit(ch = str.m_current)) - { - result += (ch - '0') * decimalBase; - decimalBase *= 0.1; - digits++; - } - return (digits > 0); - } - - /*=================================ParseTimeZone========================== - **Action: Parse the timezone offset in the following format: - ** "+8", "+08", "+0800", "+0800" - ** This method is used by DateTime.Parse(). - **Returns: The TimeZone offset. - **Arguments: - ** str the parsing string - **Exceptions: - ** FormatException if invalid timezone format is found. - ============================================================================*/ - - private static bool ParseTimeZone(ref __DTString str, ref TimeSpan result) - { - // The hour/minute offset for timezone. - int hourOffset = 0; - int minuteOffset = 0; - DTSubString sub; - - // Consume the +/- character that has already been read - sub = str.GetSubString(); - if (sub.length != 1) - { - return false; - } - char offsetChar = sub[0]; - if (offsetChar != '+' && offsetChar != '-') - { - return false; - } - str.ConsumeSubString(sub); - - sub = str.GetSubString(); - if (sub.type != DTSubStringType.Number) - { - return false; - } - int value = sub.value; - int length = sub.length; - if (length == 1 || length == 2) - { - // Parsing "+8" or "+08" - hourOffset = value; - str.ConsumeSubString(sub); - // See if we have minutes - sub = str.GetSubString(); - if (sub.length == 1 && sub[0] == ':') - { - // Parsing "+8:00" or "+08:00" - str.ConsumeSubString(sub); - sub = str.GetSubString(); - if (sub.type != DTSubStringType.Number || sub.length < 1 || sub.length > 2) - { - return false; - } - minuteOffset = sub.value; - str.ConsumeSubString(sub); - } - } - else if (length == 3 || length == 4) - { - // Parsing "+800" or "+0800" - hourOffset = value / 100; - minuteOffset = value % 100; - str.ConsumeSubString(sub); - } - else - { - // Wrong number of digits - return false; - } - Debug.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99"); - Debug.Assert(minuteOffset >= 0 && minuteOffset <= 99, "minuteOffset >= 0 && minuteOffset <= 99"); - if (minuteOffset < 0 || minuteOffset >= 60) - { - return false; - } - - result = new TimeSpan(hourOffset, minuteOffset, 0); - if (offsetChar == '-') - { - result = result.Negate(); - } - return true; - } - - // This is the helper function to handle timezone in string in the format like +/-0800 - private static bool HandleTimeZone(ref __DTString str, ref DateTimeResult result) - { - if ((str.Index < str.len - 1)) - { - char nextCh = str.Value[str.Index]; - // Skip whitespace, but don't update the index unless we find a time zone marker - int whitespaceCount = 0; - while (Char.IsWhiteSpace(nextCh) && str.Index + whitespaceCount < str.len - 1) - { - whitespaceCount++; - nextCh = str.Value[str.Index + whitespaceCount]; - } - if (nextCh == '+' || nextCh == '-') - { - str.Index += whitespaceCount; - if ((result.flags & ParseFlags.TimeZoneUsed) != 0) - { - // Should not have two timezone offsets. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - result.flags |= ParseFlags.TimeZoneUsed; - if (!ParseTimeZone(ref str, ref result.timeZoneOffset)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - } - return true; - } - - // - // This is the lexer. Check the character at the current index, and put the found token in dtok and - // some raw date/time information in raw. - // - private static Boolean Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles) - { - TokenType tokenType; - int tokenValue; - int indexBeforeSeparator; - char charBeforeSeparator; - - TokenType sep; - dtok.dtt = DTT.Unk; // Assume the token is unkown. - - str.GetRegularToken(out tokenType, out tokenValue, dtfi); - -#if _LOGGING - // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing - // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console: - // - // COMPlus_LogEnable=1 - // COMPlus_LogToConsole=1 - // COMPlus_LogLevel=9 - // COMPlus_ManagedLogFacility=0x00001000 - if (_tracingEnabled) - { - BCLDebug.Trace("DATETIME", "[DATETIME] Lex({0})\tpos:{1}({2}), {3}, DS.{4}", Hex(str.Value), - str.Index, Hex(str.m_current), tokenType, dps); - } -#endif // _LOGGING - - // Look at the regular token. - switch (tokenType) - { - case TokenType.NumberToken: - case TokenType.YearNumberToken: - if (raw.numCount == 3 || tokenValue == -1) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0010", dps); - return false; - } - // - // This is a digit. - // - // If the previous parsing state is DS.T_NNt (like 12:01), and we got another number, - // so we will have a terminal state DS.TX_NNN (like 12:01:02). - // If the previous parsing state is DS.T_Nt (like 12:), and we got another number, - // so we will have a terminal state DS.TX_NN (like 12:01). - // - // Look ahead to see if the following character is a decimal point or timezone offset. - // This enables us to parse time in the forms of: - // "11:22:33.1234" or "11:22:33-08". - if (dps == DS.T_NNt) - { - if ((str.Index < str.len - 1)) - { - char nextCh = str.Value[str.Index]; - if (nextCh == '.') - { - // While ParseFraction can fail, it just means that there were no digits after - // the dot. In this case ParseFraction just removes the dot. This is actually - // valid for cultures like Albanian, that join the time marker to the time with - // with a dot: e.g. "9:03.MD" - ParseFraction(ref str, out raw.fraction); - } - } - } - if (dps == DS.T_NNt || dps == DS.T_Nt) - { - if ((str.Index < str.len - 1)) - { - if (false == HandleTimeZone(ref str, ref result)) - { - LexTraceExit("0020 (value like \"12:01\" or \"12:\" followed by a non-TZ number", dps); - return false; - } - } - } - - dtok.num = tokenValue; - if (tokenType == TokenType.YearNumberToken) - { - if (raw.year == -1) - { - raw.year = tokenValue; - // - // If we have number which has 3 or more digits (like "001" or "0001"), - // we assume this number is a year. Save the currnet raw.numCount in - // raw.year. - // - switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) - { - case TokenType.SEP_End: - dtok.dtt = DTT.YearEnd; - break; - case TokenType.SEP_Am: - case TokenType.SEP_Pm: - if (raw.timeMark == TM.NotSet) - { - raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM); - dtok.dtt = DTT.YearSpace; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0030 (TM.AM/TM.PM Happened more than 1x)", dps); - } - break; - case TokenType.SEP_Space: - dtok.dtt = DTT.YearSpace; - break; - case TokenType.SEP_Date: - dtok.dtt = DTT.YearDateSep; - break; - case TokenType.SEP_Time: - if (!raw.hasSameDateAndTimeSeparators) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0040 (Invalid separator after number)", dps); - return false; - } - - // we have the date and time separators are same and getting a year number, then change the token to YearDateSep as - // we are sure we are not parsing time. - dtok.dtt = DTT.YearDateSep; - break; - case TokenType.SEP_DateOrOffset: - // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then - // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset. - if ((dateParsingStates[(int)dps][(int)DTT.YearDateSep] == DS.ERROR) - && (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR)) - { - str.Index = indexBeforeSeparator; - str.m_current = charBeforeSeparator; - dtok.dtt = DTT.YearSpace; - } - else - { - dtok.dtt = DTT.YearDateSep; - } - break; - case TokenType.SEP_YearSuff: - case TokenType.SEP_MonthSuff: - case TokenType.SEP_DaySuff: - dtok.dtt = DTT.NumDatesuff; - dtok.suffix = sep; - break; - case TokenType.SEP_HourSuff: - case TokenType.SEP_MinuteSuff: - case TokenType.SEP_SecondSuff: - dtok.dtt = DTT.NumTimesuff; - dtok.suffix = sep; - break; - default: - // Invalid separator after number number. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0040 (Invalid separator after number)", dps); - return false; - } - // - // Found the token already. Return now. - // - LexTraceExit("0050 (success)", dps); - return true; - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0060", dps); - return false; - } - switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) - { - // - // Note here we check if the numCount is less than three. - // When we have more than three numbers, it will be caught as error in the state machine. - // - case TokenType.SEP_End: - dtok.dtt = DTT.NumEnd; - raw.AddNumber(dtok.num); - break; - case TokenType.SEP_Am: - case TokenType.SEP_Pm: - if (raw.timeMark == TM.NotSet) - { - raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM); - dtok.dtt = DTT.NumAmpm; - // Fix AM/PM parsing case, e.g. "1/10 5 AM" - if (dps == DS.D_NN) - { - if (!ProcessTerminaltState(DS.DX_NN, ref result, ref styles, ref raw, dtfi)) - { - return false; - } - } - - raw.AddNumber(dtok.num); - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - break; - } - if (dps == DS.T_NNt || dps == DS.T_Nt) - { - if (false == HandleTimeZone(ref str, ref result)) - { - LexTraceExit("0070 (HandleTimeZone returned false)", dps); - return false; - } - } - break; - case TokenType.SEP_Space: - dtok.dtt = DTT.NumSpace; - raw.AddNumber(dtok.num); - break; - case TokenType.SEP_Date: - dtok.dtt = DTT.NumDatesep; - raw.AddNumber(dtok.num); - break; - case TokenType.SEP_DateOrOffset: - // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then - // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset. - if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR) - && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR)) - { - str.Index = indexBeforeSeparator; - str.m_current = charBeforeSeparator; - dtok.dtt = DTT.NumSpace; - } - else - { - dtok.dtt = DTT.NumDatesep; - } - raw.AddNumber(dtok.num); - break; - case TokenType.SEP_Time: - if (raw.hasSameDateAndTimeSeparators && - (dps == DS.D_Y || dps == DS.D_YN || dps == DS.D_YNd || dps == DS.D_YM || dps == DS.D_YMd)) - { - // we are parsing a date and we have the time separator same as date separator, so we mark the token as date separator - dtok.dtt = DTT.NumDatesep; - raw.AddNumber(dtok.num); - break; - } - dtok.dtt = DTT.NumTimesep; - raw.AddNumber(dtok.num); - break; - case TokenType.SEP_YearSuff: - try - { - dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue); - } - catch (ArgumentOutOfRangeException e) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e); - LexTraceExit("0075 (Calendar.ToFourDigitYear failed)", dps); - return false; - } - dtok.dtt = DTT.NumDatesuff; - dtok.suffix = sep; - break; - case TokenType.SEP_MonthSuff: - case TokenType.SEP_DaySuff: - dtok.dtt = DTT.NumDatesuff; - dtok.suffix = sep; - break; - case TokenType.SEP_HourSuff: - case TokenType.SEP_MinuteSuff: - case TokenType.SEP_SecondSuff: - dtok.dtt = DTT.NumTimesuff; - dtok.suffix = sep; - break; - case TokenType.SEP_LocalTimeMark: - dtok.dtt = DTT.NumLocalTimeMark; - raw.AddNumber(dtok.num); - break; - default: - // Invalid separator after number number. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0080", dps); - return false; - } - break; - case TokenType.HebrewNumber: - if (tokenValue >= 100) - { - // This is a year number - if (raw.year == -1) - { - raw.year = tokenValue; - // - // If we have number which has 3 or more digits (like "001" or "0001"), - // we assume this number is a year. Save the currnet raw.numCount in - // raw.year. - // - switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) - { - case TokenType.SEP_End: - dtok.dtt = DTT.YearEnd; - break; - case TokenType.SEP_Space: - dtok.dtt = DTT.YearSpace; - break; - case TokenType.SEP_DateOrOffset: - // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then - // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset. - if (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR) - { - str.Index = indexBeforeSeparator; - str.m_current = charBeforeSeparator; - dtok.dtt = DTT.YearSpace; - break; - } - goto default; - default: - // Invalid separator after number number. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0090", dps); - return false; - } - } - else - { - // Invalid separator after number number. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0100", dps); - return false; - } - } - else - { - // This is a day number - dtok.num = tokenValue; - raw.AddNumber(dtok.num); - - switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) - { - // - // Note here we check if the numCount is less than three. - // When we have more than three numbers, it will be caught as error in the state machine. - // - case TokenType.SEP_End: - dtok.dtt = DTT.NumEnd; - break; - case TokenType.SEP_Space: - case TokenType.SEP_Date: - dtok.dtt = DTT.NumDatesep; - break; - case TokenType.SEP_DateOrOffset: - // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then - // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset. - if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR) - && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR)) - { - str.Index = indexBeforeSeparator; - str.m_current = charBeforeSeparator; - dtok.dtt = DTT.NumSpace; - } - else - { - dtok.dtt = DTT.NumDatesep; - } - break; - default: - // Invalid separator after number number. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0110", dps); - return false; - } - } - break; - case TokenType.DayOfWeekToken: - if (raw.dayOfWeek == -1) - { - // - // This is a day of week name. - // - raw.dayOfWeek = tokenValue; - dtok.dtt = DTT.DayOfWeek; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0120 (DayOfWeek seen more than 1x)", dps); - return false; - } - break; - case TokenType.MonthToken: - if (raw.month == -1) - { - // - // This is a month name - // - switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) - { - case TokenType.SEP_End: - dtok.dtt = DTT.MonthEnd; - break; - case TokenType.SEP_Space: - dtok.dtt = DTT.MonthSpace; - break; - case TokenType.SEP_Date: - dtok.dtt = DTT.MonthDatesep; - break; - case TokenType.SEP_Time: - if (!raw.hasSameDateAndTimeSeparators) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0130 (Invalid separator after month name)", dps); - return false; - } - - // we have the date and time separators are same and getting a Month name, then change the token to MonthDatesep as - // we are sure we are not parsing time. - dtok.dtt = DTT.MonthDatesep; - break; - case TokenType.SEP_DateOrOffset: - // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then - // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset. - if ((dateParsingStates[(int)dps][(int)DTT.MonthDatesep] == DS.ERROR) - && (dateParsingStates[(int)dps][(int)DTT.MonthSpace] > DS.ERROR)) - { - str.Index = indexBeforeSeparator; - str.m_current = charBeforeSeparator; - dtok.dtt = DTT.MonthSpace; - } - else - { - dtok.dtt = DTT.MonthDatesep; - } - break; - default: - //Invalid separator after month name - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0130 (Invalid separator after month name)", dps); - return false; - } - raw.month = tokenValue; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0140 (MonthToken seen more than 1x)", dps); - return false; - } - break; - case TokenType.EraToken: - if (result.era != -1) - { - result.era = tokenValue; - dtok.dtt = DTT.Era; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0150 (EraToken seen when result.era already set)", dps); - return false; - } - break; - case TokenType.JapaneseEraToken: - // Special case for Japanese. We allow Japanese era name to be used even if the calendar is not Japanese Calendar. - result.calendar = JapaneseCalendar.GetDefaultInstance(); - dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI(); - if (result.era != -1) - { - result.era = tokenValue; - dtok.dtt = DTT.Era; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0160 (JapaneseEraToken seen when result.era already set)", dps); - return false; - } - break; - case TokenType.TEraToken: - result.calendar = TaiwanCalendar.GetDefaultInstance(); - dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI(); - if (result.era != -1) - { - result.era = tokenValue; - dtok.dtt = DTT.Era; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0170 (TEraToken seen when result.era already set)", dps); - return false; - } - break; - case TokenType.TimeZoneToken: - // - // This is a timezone designator - // - // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time). - // - if ((result.flags & ParseFlags.TimeZoneUsed) != 0) - { - // Should not have two timezone offsets. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0180 (seen GMT or Z more than 1x)", dps); - return false; - } - dtok.dtt = DTT.TimeZone; - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = new TimeSpan(0); - result.flags |= ParseFlags.TimeZoneUtc; - break; - case TokenType.EndOfString: - dtok.dtt = DTT.End; - break; - case TokenType.DateWordToken: - case TokenType.IgnorableSymbol: - // Date words and ignorable symbols can just be skipped over - break; - case TokenType.Am: - case TokenType.Pm: - if (raw.timeMark == TM.NotSet) - { - raw.timeMark = (TM)tokenValue; - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0190 (AM/PM timeMark already set)", dps); - return false; - } - break; - case TokenType.UnknownToken: - if (Char.IsLetter(str.m_current)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index); - LexTraceExit("0200", dps); - return (false); - } - - if ((str.m_current == '-' || str.m_current == '+') && ((result.flags & ParseFlags.TimeZoneUsed) == 0)) - { - Int32 originalIndex = str.Index; - if (ParseTimeZone(ref str, ref result.timeZoneOffset)) - { - result.flags |= ParseFlags.TimeZoneUsed; - LexTraceExit("0220 (success)", dps); - return true; - } - else - { - // Time zone parse attempt failed. Fall through to punctuation handling. - str.Index = originalIndex; - } - } - - // Visual Basic implements string to date conversions on top of DateTime.Parse: - // CDate("#10/10/95#") - // - if (VerifyValidPunctuation(ref str)) - { - LexTraceExit("0230 (success)", dps); - return true; - } - - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - LexTraceExit("0240", dps); - return false; - } - - LexTraceExit("0250 (success)", dps); - return true; - } - - private static Boolean VerifyValidPunctuation(ref __DTString str) - { - // Compatability Behavior. Allow trailing nulls and surrounding hashes - Char ch = str.Value[str.Index]; - if (ch == '#') - { - bool foundStart = false; - bool foundEnd = false; - for (int i = 0; i < str.len; i++) - { - ch = str.Value[i]; - if (ch == '#') - { - if (foundStart) - { - if (foundEnd) - { - // Having more than two hashes is invalid - return false; - } - else - { - foundEnd = true; - } - } - else - { - foundStart = true; - } - } - else if (ch == '\0') - { - // Allow nulls only at the end - if (!foundEnd) - { - return false; - } - } - else if ((!Char.IsWhiteSpace(ch))) - { - // Anthyhing other than whitespace outside hashes is invalid - if (!foundStart || foundEnd) - { - return false; - } - } - } - if (!foundEnd) - { - // The has was un-paired - return false; - } - // Valid Hash usage: eat the hash and continue. - str.GetNext(); - return true; - } - else if (ch == '\0') - { - for (int i = str.Index; i < str.len; i++) - { - if (str.Value[i] != '\0') - { - // Nulls are only valid if they are the only trailing character - return false; - } - } - // Move to the end of the string - str.Index = str.len; - return true; - } - return false; - } - - private const int ORDER_YMD = 0; // The order of date is Year/Month/Day. - private const int ORDER_MDY = 1; // The order of date is Month/Day/Year. - private const int ORDER_DMY = 2; // The order of date is Day/Month/Year. - private const int ORDER_YDM = 3; // The order of date is Year/Day/Month - private const int ORDER_YM = 4; // Year/Month order. - private const int ORDER_MY = 5; // Month/Year order. - private const int ORDER_MD = 6; // Month/Day order. - private const int ORDER_DM = 7; // Day/Month order. - - // - // Decide the year/month/day order from the datePattern. - // - // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1. - // - private static Boolean GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi, out int order) - { - int yearOrder = -1; - int monthOrder = -1; - int dayOrder = -1; - int orderCount = 0; - - bool inQuote = false; - - for (int i = 0; i < datePattern.Length && orderCount < 3; i++) - { - char ch = datePattern[i]; - if (ch == '\\' || ch == '%') - { - i++; - continue; // Skip next character that is escaped by this backslash - } - - if (ch == '\'' || ch == '"') - { - inQuote = !inQuote; - } - - if (!inQuote) - { - if (ch == 'y') - { - yearOrder = orderCount++; - - // - // Skip all year pattern charaters. - // - for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'y'; i++) - { - // Do nothing here. - } - } - else if (ch == 'M') - { - monthOrder = orderCount++; - // - // Skip all month pattern characters. - // - for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'M'; i++) - { - // Do nothing here. - } - } - else if (ch == 'd') - { - int patternCount = 1; - // - // Skip all day pattern characters. - // - for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'd'; i++) - { - patternCount++; - } - // - // Make sure this is not "ddd" or "dddd", which means day of week. - // - if (patternCount <= 2) - { - dayOrder = orderCount++; - } - } - } - } - - if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2) - { - order = ORDER_YMD; - return true; - } - if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2) - { - order = ORDER_MDY; - return true; - } - if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2) - { - order = ORDER_DMY; - return true; - } - if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2) - { - order = ORDER_YDM; - return true; - } - order = -1; - return false; - } - - // - // Decide the year/month order from the pattern. - // - // Return 0 for YM, 1 for MY, otherwise -1. - // - private static Boolean GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi, out int order) - { - int yearOrder = -1; - int monthOrder = -1; - int orderCount = 0; - - bool inQuote = false; - for (int i = 0; i < pattern.Length && orderCount < 2; i++) - { - char ch = pattern[i]; - if (ch == '\\' || ch == '%') - { - i++; - continue; // Skip next character that is escaped by this backslash - } - - if (ch == '\'' || ch == '"') - { - inQuote = !inQuote; - } - - if (!inQuote) - { - if (ch == 'y') - { - yearOrder = orderCount++; - - // - // Skip all year pattern charaters. - // - for (; i + 1 < pattern.Length && pattern[i + 1] == 'y'; i++) - { - } - } - else if (ch == 'M') - { - monthOrder = orderCount++; - // - // Skip all month pattern characters. - // - for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++) - { - } - } - } - } - - if (yearOrder == 0 && monthOrder == 1) - { - order = ORDER_YM; - return true; - } - if (monthOrder == 0 && yearOrder == 1) - { - order = ORDER_MY; - return true; - } - order = -1; - return false; - } - - // - // Decide the month/day order from the pattern. - // - // Return 0 for MD, 1 for DM, otherwise -1. - // - private static Boolean GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi, out int order) - { - int monthOrder = -1; - int dayOrder = -1; - int orderCount = 0; - - bool inQuote = false; - for (int i = 0; i < pattern.Length && orderCount < 2; i++) - { - char ch = pattern[i]; - if (ch == '\\' || ch == '%') - { - i++; - continue; // Skip next character that is escaped by this backslash - } - - if (ch == '\'' || ch == '"') - { - inQuote = !inQuote; - } - - if (!inQuote) - { - if (ch == 'd') - { - int patternCount = 1; - // - // Skip all day pattern charaters. - // - for (; i + 1 < pattern.Length && pattern[i + 1] == 'd'; i++) - { - patternCount++; - } - - // - // Make sure this is not "ddd" or "dddd", which means day of week. - // - if (patternCount <= 2) - { - dayOrder = orderCount++; - } - } - else if (ch == 'M') - { - monthOrder = orderCount++; - // - // Skip all month pattern characters. - // - for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++) - { - } - } - } - } - - if (monthOrder == 0 && dayOrder == 1) - { - order = ORDER_MD; - return true; - } - if (dayOrder == 0 && monthOrder == 1) - { - order = ORDER_DM; - return true; - } - order = -1; - return false; - } - - // - // Adjust the two-digit year if necessary. - // - private static bool TryAdjustYear(ref DateTimeResult result, int year, out int adjustedYear) - { - if (year < 100) - { - try - { - // the Calendar classes need some real work. Many of the calendars that throw - // don't implement a fast/non-allocating (and non-throwing) IsValid{Year|Day|Month} method. - // we are making a targeted try/catch fix in the in-place release but will revisit this code - // in the next side-by-side release. - year = result.calendar.ToFourDigitYear(year); - } - catch (ArgumentOutOfRangeException) - { - adjustedYear = -1; - return false; - } - } - adjustedYear = year; - return true; - } - - private static bool SetDateYMD(ref DateTimeResult result, int year, int month, int day) - { - // Note, longer term these checks should be done at the end of the parse. This current - // way of checking creates order dependence with parsing the era name. - if (result.calendar.IsValidDay(year, month, day, result.era)) - { - result.SetDate(year, month, day); // YMD - return (true); - } - return (false); - } - - private static bool SetDateMDY(ref DateTimeResult result, int month, int day, int year) - { - return (SetDateYMD(ref result, year, month, day)); - } - - private static bool SetDateDMY(ref DateTimeResult result, int day, int month, int year) - { - return (SetDateYMD(ref result, year, month, day)); - } - - private static bool SetDateYDM(ref DateTimeResult result, int year, int day, int month) - { - return (SetDateYMD(ref result, year, month, day)); - } - - private static void GetDefaultYear(ref DateTimeResult result, ref DateTimeStyles styles) - { - result.Year = result.calendar.GetYear(GetDateTimeNow(ref result, ref styles)); - result.flags |= ParseFlags.YearDefault; - } - - // Processing teriminal case: DS.DX_NN - private static Boolean GetDayOfNN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - int n1 = raw.GetNumber(0); - int n2 = raw.GetNumber(1); - - GetDefaultYear(ref result, ref styles); - - int order; - if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out order)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern); - return false; - } - - if (order == ORDER_MD) - { - if (SetDateYMD(ref result, result.Year, n1, n2)) // MD - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else - { - // ORDER_DM - if (SetDateYMD(ref result, result.Year, n2, n1)) // DM - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - // Processing teriminal case: DS.DX_NNN - private static Boolean GetDayOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - int n1 = raw.GetNumber(0); - int n2 = raw.GetNumber(1); ; - int n3 = raw.GetNumber(2); - - int order; - if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern); - return false; - } - int year; - - if (order == ORDER_YMD) - { - if (TryAdjustYear(ref result, n1, out year) && SetDateYMD(ref result, year, n2, n3)) // YMD - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else if (order == ORDER_MDY) - { - if (TryAdjustYear(ref result, n3, out year) && SetDateMDY(ref result, n1, n2, year)) // MDY - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else if (order == ORDER_DMY) - { - if (TryAdjustYear(ref result, n3, out year) && SetDateDMY(ref result, n1, n2, year)) // DMY - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else if (order == ORDER_YDM) - { - if (TryAdjustYear(ref result, n1, out year) && SetDateYDM(ref result, year, n2, n3)) // YDM - { - result.flags |= ParseFlags.HaveDate; - return true; - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfMN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - // The interpretation is based on the MonthDayPattern and YearMonthPattern - // - // MonthDayPattern YearMonthPattern Interpretation - // --------------- ---------------- --------------- - // MMMM dd MMMM yyyy Day - // MMMM dd yyyy MMMM Day - // dd MMMM MMMM yyyy Year - // dd MMMM yyyy MMMM Day - // - // In the first and last cases, it could be either or neither, but a day is a better default interpretation - // than a 2 digit year. - - int monthDayOrder; - if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern); - return false; - } - if (monthDayOrder == ORDER_DM) - { - int yearMonthOrder; - if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern); - return false; - } - if (yearMonthOrder == ORDER_MY) - { - int year; - if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - return true; - } - } - - GetDefaultYear(ref result, ref styles); - if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0))) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - return true; - } - - //////////////////////////////////////////////////////////////////////// - // Actions: - // Deal with the terminal state for Hebrew Month/Day pattern - // - //////////////////////////////////////////////////////////////////////// - - private static Boolean GetHebrewDayOfNM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - int monthDayOrder; - if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern); - return false; - } - result.Month = raw.month; - if (monthDayOrder == ORDER_DM || monthDayOrder == ORDER_MD) - { - if (result.calendar.IsValidDay(result.Year, result.Month, raw.GetNumber(0), result.era)) - { - result.Day = raw.GetNumber(0); - return true; - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfNM(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - // The interpretation is based on the MonthDayPattern and YearMonthPattern - // - // MonthDayPattern YearMonthPattern Interpretation - // --------------- ---------------- --------------- - // MMMM dd MMMM yyyy Day - // MMMM dd yyyy MMMM Year - // dd MMMM MMMM yyyy Day - // dd MMMM yyyy MMMM Day - // - // In the first and last cases, it could be either or neither, but a day is a better default interpretation - // than a 2 digit year. - - int monthDayOrder; - if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern); - return false; - } - if (monthDayOrder == ORDER_MD) - { - int yearMonthOrder; - if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern); - return false; - } - if (yearMonthOrder == ORDER_YM) - { - int year; - if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - return true; - } - } - - GetDefaultYear(ref result, ref styles); - if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0))) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - return true; - } - - private static Boolean GetDayOfMNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - int n1 = raw.GetNumber(0); - int n2 = raw.GetNumber(1); - - int order; - if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern); - return false; - } - int year; - - if (order == ORDER_MDY) - { - if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era)) - { - result.SetDate(year, raw.month, n1); // MDY - result.flags |= ParseFlags.HaveDate; - return true; - } - else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era)) - { - result.SetDate(year, raw.month, n2); // YMD - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else if (order == ORDER_YMD) - { - if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era)) - { - result.SetDate(year, raw.month, n2); // YMD - result.flags |= ParseFlags.HaveDate; - return true; - } - else if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era)) - { - result.SetDate(year, raw.month, n1); // DMY - result.flags |= ParseFlags.HaveDate; - return true; - } - } - else if (order == ORDER_DMY) - { - if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era)) - { - result.SetDate(year, raw.month, n1); // DMY - result.flags |= ParseFlags.HaveDate; - return true; - } - else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era)) - { - result.SetDate(year, raw.month, n2); // YMD - result.flags |= ParseFlags.HaveDate; - return true; - } - } - - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfYNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - int n1 = raw.GetNumber(0); - int n2 = raw.GetNumber(1); - String pattern = dtfi.ShortDatePattern; - - // For compatibility, don't throw if we can't determine the order, but default to YMD instead - int order; - if (GetYearMonthDayOrder(pattern, dtfi, out order) && order == ORDER_YDM) - { - if (SetDateYMD(ref result, raw.year, n2, n1)) - { - result.flags |= ParseFlags.HaveDate; - return true; // Year + DM - } - } - else - { - if (SetDateYMD(ref result, raw.year, n1, n2)) - { - result.flags |= ParseFlags.HaveDate; - return true; // Year + MD - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfNNY(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - int n1 = raw.GetNumber(0); - int n2 = raw.GetNumber(1); - - int order; - if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern); - return false; - } - - if (order == ORDER_MDY || order == ORDER_YMD) - { - if (SetDateYMD(ref result, raw.year, n1, n2)) - { - result.flags |= ParseFlags.HaveDate; - return true; // MD + Year - } - } - else - { - if (SetDateYMD(ref result, raw.year, n2, n1)) - { - result.flags |= ParseFlags.HaveDate; - return true; // DM + Year - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - - private static Boolean GetDayOfYMN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (SetDateYMD(ref result, raw.year, raw.month, raw.GetNumber(0))) - { - result.flags |= ParseFlags.HaveDate; - return true; - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfYN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (SetDateYMD(ref result, raw.year, raw.GetNumber(0), 1)) - { - result.flags |= ParseFlags.HaveDate; - return true; - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static Boolean GetDayOfYM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - if ((result.flags & ParseFlags.HaveDate) != 0) - { - // Multiple dates in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (SetDateYMD(ref result, raw.year, raw.month, 1)) - { - result.flags |= ParseFlags.HaveDate; - return true; - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - private static void AdjustTimeMark(DateTimeFormatInfo dtfi, ref DateTimeRawInfo raw) - { - // Specail case for culture which uses AM as empty string. - // E.g. af-ZA (0x0436) - // S1159 \x0000 - // S2359 nm - // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM. - - if (raw.timeMark == TM.NotSet) - { - if (dtfi.AMDesignator != null && dtfi.PMDesignator != null) - { - if (dtfi.AMDesignator.Length == 0 && dtfi.PMDesignator.Length != 0) - { - raw.timeMark = TM.AM; - } - if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0) - { - raw.timeMark = TM.PM; - } - } - } - } - - // - // Adjust hour according to the time mark. - // - private static Boolean AdjustHour(ref int hour, TM timeMark) - { - if (timeMark != TM.NotSet) - { - if (timeMark == TM.AM) - { - if (hour < 0 || hour > 12) - { - return false; - } - hour = (hour == 12) ? 0 : hour; - } - else - { - if (hour < 0 || hour > 23) - { - return false; - } - if (hour < 12) - { - hour += 12; - } - } - } - return true; - } - - private static Boolean GetTimeOfN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw) - { - if ((result.flags & ParseFlags.HaveTime) != 0) - { - // Multiple times in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - // - // In this case, we need a time mark. Check if so. - // - if (raw.timeMark == TM.NotSet) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - result.Hour = raw.GetNumber(0); - result.flags |= ParseFlags.HaveTime; - return true; - } - - private static Boolean GetTimeOfNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw) - { - Debug.Assert(raw.numCount >= 2, "raw.numCount >= 2"); - if ((result.flags & ParseFlags.HaveTime) != 0) - { - // Multiple times in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - result.Hour = raw.GetNumber(0); - result.Minute = raw.GetNumber(1); - result.flags |= ParseFlags.HaveTime; - return true; - } - - private static Boolean GetTimeOfNNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw) - { - if ((result.flags & ParseFlags.HaveTime) != 0) - { - // Multiple times in the input string - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - Debug.Assert(raw.numCount >= 3, "raw.numCount >= 3"); - result.Hour = raw.GetNumber(0); - result.Minute = raw.GetNumber(1); - result.Second = raw.GetNumber(2); - result.flags |= ParseFlags.HaveTime; - return true; - } - - // - // Processing terminal state: A Date suffix followed by one number. - // - private static Boolean GetDateOfDSN(ref DateTimeResult result, ref DateTimeRawInfo raw) - { - if (raw.numCount != 1 || result.Day != -1) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - result.Day = raw.GetNumber(0); - return true; - } - - private static Boolean GetDateOfNDS(ref DateTimeResult result, ref DateTimeRawInfo raw) - { - if (result.Month == -1) - { - //Should have a month suffix - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - if (result.Year != -1) - { - // Aleady has a year suffix - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - if (!TryAdjustYear(ref result, raw.GetNumber(0), out result.Year)) - { - // the year value is out of range - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - result.Day = 1; - return true; - } - - private static Boolean GetDateOfNNDS(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - // For partial CJK Dates, the only valid formats are with a specified year, followed by two numbers, which - // will be the Month and Day, and with a specified Month, when the numbers are either the year and day or - // day and year, depending on the short date pattern. - - if ((result.flags & ParseFlags.HaveYear) != 0) - { - if (((result.flags & ParseFlags.HaveMonth) == 0) && ((result.flags & ParseFlags.HaveDay) == 0)) - { - if (TryAdjustYear(ref result, raw.year, out result.Year) && SetDateYMD(ref result, result.Year, raw.GetNumber(0), raw.GetNumber(1))) - { - return true; - } - } - } - else if ((result.flags & ParseFlags.HaveMonth) != 0) - { - if (((result.flags & ParseFlags.HaveYear) == 0) && ((result.flags & ParseFlags.HaveDay) == 0)) - { - int order; - if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern); - return false; - } - int year; - if (order == ORDER_YMD) - { - if (TryAdjustYear(ref result, raw.GetNumber(0), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(1))) - { - return true; - } - } - else - { - if (TryAdjustYear(ref result, raw.GetNumber(1), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(0))) - { - return true; - } - } - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - // - // A date suffix is found, use this method to put the number into the result. - // - private static bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok) - { - switch (dtok.suffix) - { - case TokenType.SEP_YearSuff: - if ((result.flags & ParseFlags.HaveYear) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveYear; - result.Year = raw.year = dtok.num; - break; - case TokenType.SEP_MonthSuff: - if ((result.flags & ParseFlags.HaveMonth) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveMonth; - result.Month = raw.month = dtok.num; - break; - case TokenType.SEP_DaySuff: - if ((result.flags & ParseFlags.HaveDay) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveDay; - result.Day = dtok.num; - break; - case TokenType.SEP_HourSuff: - if ((result.flags & ParseFlags.HaveHour) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveHour; - result.Hour = dtok.num; - break; - case TokenType.SEP_MinuteSuff: - if ((result.flags & ParseFlags.HaveMinute) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveMinute; - result.Minute = dtok.num; - break; - case TokenType.SEP_SecondSuff: - if ((result.flags & ParseFlags.HaveSecond) != 0) - { - return false; - } - result.flags |= ParseFlags.HaveSecond; - result.Second = dtok.num; - break; - } - return true; - } - - //////////////////////////////////////////////////////////////////////// - // - // Actions: - // This is used by DateTime.Parse(). - // Process the terminal state for the Hebrew calendar parsing. - // - //////////////////////////////////////////////////////////////////////// - - internal static Boolean ProcessHebrewTerminalState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - // The following are accepted terminal state for Hebrew date. - switch (dps) - { - case DS.DX_MNN: - // Deal with the default long/short date format when the year number is ambigous (i.e. year < 100). - raw.year = raw.GetNumber(1); - if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - if (!GetDayOfMNN(ref result, ref raw, dtfi)) - { - return false; - } - break; - case DS.DX_YMN: - // Deal with the default long/short date format when the year number is NOT ambigous (i.e. year >= 100). - if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - if (!GetDayOfYMN(ref result, ref raw, dtfi)) - { - return false; - } - break; - case DS.DX_NM: - case DS.DX_MN: - // Deal with Month/Day pattern. - GetDefaultYear(ref result, ref styles); - if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - if (!GetHebrewDayOfNM(ref result, ref raw, dtfi)) - { - return false; - } - break; - case DS.DX_YM: - // Deal with Year/Month pattern. - if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - if (!GetDayOfYM(ref result, ref raw, dtfi)) - { - return false; - } - break; - case DS.TX_N: - // Deal hour + AM/PM - if (!GetTimeOfN(dtfi, ref result, ref raw)) - { - return false; - } - break; - case DS.TX_NN: - if (!GetTimeOfNN(dtfi, ref result, ref raw)) - { - return false; - } - break; - case DS.TX_NNN: - if (!GetTimeOfNNN(dtfi, ref result, ref raw)) - { - return false; - } - break; - default: - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - if (dps > DS.ERROR) - { - // - // We have reached a terminal state. Reset the raw num count. - // - raw.numCount = 0; - } - return true; - } - - // - // A terminal state has been reached, call the appropriate function to fill in the parsing result. - // Return true if the state is a terminal state. - // - internal static Boolean ProcessTerminaltState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) - { - bool passed = true; - switch (dps) - { - case DS.DX_NN: - passed = GetDayOfNN(ref result, ref styles, ref raw, dtfi); - break; - case DS.DX_NNN: - passed = GetDayOfNNN(ref result, ref raw, dtfi); - break; - case DS.DX_MN: - passed = GetDayOfMN(ref result, ref styles, ref raw, dtfi); - break; - case DS.DX_NM: - passed = GetDayOfNM(ref result, ref styles, ref raw, dtfi); - break; - case DS.DX_MNN: - passed = GetDayOfMNN(ref result, ref raw, dtfi); - break; - case DS.DX_DS: - // The result has got the correct value. No need to process. - passed = true; - break; - case DS.DX_YNN: - passed = GetDayOfYNN(ref result, ref raw, dtfi); - break; - case DS.DX_NNY: - passed = GetDayOfNNY(ref result, ref raw, dtfi); - break; - case DS.DX_YMN: - passed = GetDayOfYMN(ref result, ref raw, dtfi); - break; - case DS.DX_YN: - passed = GetDayOfYN(ref result, ref raw, dtfi); - break; - case DS.DX_YM: - passed = GetDayOfYM(ref result, ref raw, dtfi); - break; - case DS.TX_N: - passed = GetTimeOfN(dtfi, ref result, ref raw); - break; - case DS.TX_NN: - passed = GetTimeOfNN(dtfi, ref result, ref raw); - break; - case DS.TX_NNN: - passed = GetTimeOfNNN(dtfi, ref result, ref raw); - break; - case DS.TX_TS: - // The result has got the correct value. Nothing to do. - passed = true; - break; - case DS.DX_DSN: - passed = GetDateOfDSN(ref result, ref raw); - break; - case DS.DX_NDS: - passed = GetDateOfNDS(ref result, ref raw); - break; - case DS.DX_NNDS: - passed = GetDateOfNNDS(ref result, ref raw, dtfi); - break; - } - - PTSTraceExit(dps, passed); - if (!passed) - { - return false; - } - - if (dps > DS.ERROR) - { - // - // We have reached a terminal state. Reset the raw num count. - // - raw.numCount = 0; - } - return true; - } - - internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - result.Init(); - if (TryParse(s, dtfi, styles, ref result)) - { - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out TimeSpan offset) - { - DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. - result.Init(); - result.flags |= ParseFlags.CaptureOffset; - if (TryParse(s, dtfi, styles, ref result)) - { - offset = result.timeZoneOffset; - return result.parsedDate; - } - else - { - throw GetDateTimeParseException(ref result); - } - } - - - internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result) - { - result = DateTime.MinValue; - DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. - resultData.Init(); - if (TryParse(s, dtfi, styles, ref resultData)) - { - result = resultData.parsedDate; - return true; - } - return false; - } - - internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result, out TimeSpan offset) - { - result = DateTime.MinValue; - offset = TimeSpan.Zero; - DateTimeResult parseResult = new DateTimeResult(); // The buffer to store the parsing result. - parseResult.Init(); - parseResult.flags |= ParseFlags.CaptureOffset; - if (TryParse(s, dtfi, styles, ref parseResult)) - { - result = parseResult.parsedDate; - offset = parseResult.timeZoneOffset; - return true; - } - return false; - } - - - // - // This is the real method to do the parsing work. - // - internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) - { - if (s == null) - { - result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(s)); - return false; - } - if (s.Length == 0) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - Debug.Assert(dtfi != null, "dtfi == null"); - -#if _LOGGING - DTFITrace(dtfi); -#endif - - DateTime time; - // - // First try the predefined format. - // - - DS dps = DS.BEGIN; // Date Parsing State. - bool reachTerminalState = false; - - DateTimeToken dtok = new DateTimeToken(); // The buffer to store the parsing token. - dtok.suffix = TokenType.SEP_Unk; - DateTimeRawInfo raw = new DateTimeRawInfo(); // The buffer to store temporary parsing information. - unsafe - { - Int32* numberPointer = stackalloc Int32[3]; - raw.Init(numberPointer); - } - raw.hasSameDateAndTimeSeparators = dtfi.DateSeparator.Equals(dtfi.TimeSeparator, StringComparison.Ordinal); - - result.calendar = dtfi.Calendar; - result.era = Calendar.CurrentEra; - - // - // The string to be parsed. Use a __DTString wrapper so that we can trace the index which - // indicates the begining of next token. - // - __DTString str = new __DTString(s, dtfi); - - str.GetNext(); - - // - // The following loop will break out when we reach the end of the str. - // - do - { - // - // Call the lexer to get the next token. - // - // If we find a era in Lex(), the era value will be in raw.era. - if (!Lex(dps, ref str, ref dtok, ref raw, ref result, ref dtfi, styles)) - { - TPTraceExit("0000", dps); - return false; - } - - // - // If the token is not unknown, process it. - // Otherwise, just discard it. - // - if (dtok.dtt != DTT.Unk) - { - // - // Check if we got any CJK Date/Time suffix. - // Since the Date/Time suffix tells us the number belongs to year/month/day/hour/minute/second, - // store the number in the appropriate field in the result. - // - if (dtok.suffix != TokenType.SEP_Unk) - { - if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - TPTraceExit("0010", dps); - return false; - } - - dtok.suffix = TokenType.SEP_Unk; // Reset suffix to SEP_Unk; - } - - if (dtok.dtt == DTT.NumLocalTimeMark) - { - if (dps == DS.D_YNd || dps == DS.D_YN) - { - // Consider this as ISO 8601 format: - // "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00 - TPTraceExit("0020", dps); - return (ParseISO8601(ref raw, ref str, styles, ref result)); - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - TPTraceExit("0030", dps); - return false; - } - } - - if (raw.hasSameDateAndTimeSeparators) - { - if (dtok.dtt == DTT.YearEnd || dtok.dtt == DTT.YearSpace || dtok.dtt == DTT.YearDateSep) - { - // When time and date separators are same and we are hitting a year number while the first parsed part of the string was recognized - // as part of time (and not a date) DS.T_Nt, DS.T_NNt then change the state to be a date so we try to parse it as a date instead - if (dps == DS.T_Nt) - { - dps = DS.D_Nd; - } - if (dps == DS.T_NNt) - { - dps = DS.D_NNd; - } - } - - bool atEnd = str.AtEnd(); - if (dateParsingStates[(int)dps][(int)dtok.dtt] == DS.ERROR || atEnd) - { - switch (dtok.dtt) - { - // we have the case of Serbia have dates in forms 'd.M.yyyy.' so we can expect '.' after the date parts. - // changing the token to end with space instead of Date Separator will avoid failing the parsing. - - case DTT.YearDateSep: dtok.dtt = atEnd ? DTT.YearEnd : DTT.YearSpace; break; - case DTT.NumDatesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break; - case DTT.NumTimesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break; - case DTT.MonthDatesep: dtok.dtt = atEnd ? DTT.MonthEnd : DTT.MonthSpace; break; - } - } - } - - // - // Advance to the next state, and continue - // - dps = dateParsingStates[(int)dps][(int)dtok.dtt]; - - if (dps == DS.ERROR) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - TPTraceExit("0040 (invalid state transition)", dps); - return false; - } - else if (dps > DS.ERROR) - { - if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) - { - if (!ProcessHebrewTerminalState(dps, ref result, ref styles, ref raw, dtfi)) - { - TPTraceExit("0050 (ProcessHebrewTerminalState)", dps); - return false; - } - } - else - { - if (!ProcessTerminaltState(dps, ref result, ref styles, ref raw, dtfi)) - { - TPTraceExit("0060 (ProcessTerminaltState)", dps); - return false; - } - } - reachTerminalState = true; - - // - // If we have reached a terminal state, start over from DS.BEGIN again. - // For example, when we parsed "1999-12-23 13:30", we will reach a terminal state at "1999-12-23", - // and we start over so we can continue to parse "12:30". - // - dps = DS.BEGIN; - } - } - } while (dtok.dtt != DTT.End && dtok.dtt != DTT.NumEnd && dtok.dtt != DTT.MonthEnd); - - if (!reachTerminalState) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - TPTraceExit("0070 (did not reach terminal state)", dps); - return false; - } - - AdjustTimeMark(dtfi, ref raw); - if (!AdjustHour(ref result.Hour, raw.timeMark)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - TPTraceExit("0080 (AdjustHour)", dps); - return false; - } - - // Check if the parased string only contains hour/minute/second values. - bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1); - - // - // Check if any year/month/day is missing in the parsing string. - // If yes, get the default value from today's date. - // - if (!CheckDefaultDateTime(ref result, ref result.calendar, styles)) - { - TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps); - return false; - } - - if (!result.calendar.TryToDateTime(result.Year, result.Month, result.Day, - result.Hour, result.Minute, result.Second, 0, result.era, out time)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - TPTraceExit("0100 (result.calendar.TryToDateTime)", dps); - return false; - } - if (raw.fraction > 0) - { - time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond)); - } - - // - // We have to check day of week before we adjust to the time zone. - // Otherwise, the value of day of week may change after adjustting to the time zone. - // - if (raw.dayOfWeek != -1) - { - // - // Check if day of week is correct. - // - if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null); - TPTraceExit("0110 (dayOfWeek check)", dps); - return false; - } - } - - result.parsedDate = time; - - if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) - { - TPTraceExit("0120 (DetermineTimeZoneAdjustments)", dps); - return false; - } - TPTraceExit("0130 (success)", dps); - return true; - } - - - // Handles time zone adjustments and sets DateTimeKind values as required by the styles - private static Boolean DetermineTimeZoneAdjustments(ref DateTimeResult result, DateTimeStyles styles, Boolean bTimeOnly) - { - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - // This is a DateTimeOffset parse, so the offset will actually be captured directly, and - // no adjustment is required in most cases - return DateTimeOffsetTimeZonePostProcessing(ref result, styles); - } - else - { - Int64 offsetTicks = result.timeZoneOffset.Ticks; - - // the DateTime offset must be within +- 14:00 hours. - if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset) - { - result.SetFailure(ParseFailureKind.Format, "Format_OffsetOutOfRange", null); - return false; - } - } - - // The flags AssumeUniveral and AssumeLocal only apply when the input does not have a time zone - if ((result.flags & ParseFlags.TimeZoneUsed) == 0) - { - // If AssumeLocal or AssumeLocal is used, there will always be a kind specified. As in the - // case when a time zone is present, it will default to being local unless AdjustToUniversal - // is present. These comparisons determine whether setting the kind is sufficient, or if a - // time zone adjustment is required. For consistentcy with the rest of parsing, it is desirable - // to fall through to the Adjust methods below, so that there is consist handling of boundary - // cases like wrapping around on time-only dates and temporarily allowing an adjusted date - // to exceed DateTime.MaxValue - if ((styles & DateTimeStyles.AssumeLocal) != 0) - { - if ((styles & DateTimeStyles.AdjustToUniversal) != 0) - { - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime); - } - else - { - result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Local); - return true; - } - } - else if ((styles & DateTimeStyles.AssumeUniversal) != 0) - { - if ((styles & DateTimeStyles.AdjustToUniversal) != 0) - { - result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc); - return true; - } - else - { - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeSpan.Zero; - } - } - else - { - // No time zone and no Assume flags, so DateTimeKind.Unspecified is fine - Debug.Assert(result.parsedDate.Kind == DateTimeKind.Unspecified, "result.parsedDate.Kind == DateTimeKind.Unspecified"); - return true; - } - } - - if (((styles & DateTimeStyles.RoundtripKind) != 0) && ((result.flags & ParseFlags.TimeZoneUtc) != 0)) - { - result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc); - return true; - } - - if ((styles & DateTimeStyles.AdjustToUniversal) != 0) - { - return (AdjustTimeZoneToUniversal(ref result)); - } - return (AdjustTimeZoneToLocal(ref result, bTimeOnly)); - } - - // Apply validation and adjustments specific to DateTimeOffset - private static Boolean DateTimeOffsetTimeZonePostProcessing(ref DateTimeResult result, DateTimeStyles styles) - { - // For DateTimeOffset, default to the Utc or Local offset when an offset was not specified by - // the input string. - if ((result.flags & ParseFlags.TimeZoneUsed) == 0) - { - if ((styles & DateTimeStyles.AssumeUniversal) != 0) - { - // AssumeUniversal causes the offset to default to zero (0) - result.timeZoneOffset = TimeSpan.Zero; - } - else - { - // AssumeLocal causes the offset to default to Local. This flag is on by default for DateTimeOffset. - result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime); - } - } - - Int64 offsetTicks = result.timeZoneOffset.Ticks; - - // there should be no overflow, because the offset can be no more than -+100 hours and the date already - // fits within a DateTime. - Int64 utcTicks = result.parsedDate.Ticks - offsetTicks; - - // For DateTimeOffset, both the parsed time and the corresponding UTC value must be within the boundaries - // of a DateTime instance. - if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks) - { - result.SetFailure(ParseFailureKind.Format, "Format_UTCOutOfRange", null); - return false; - } - - // the offset must be within +- 14:00 hours. - if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset) - { - result.SetFailure(ParseFailureKind.Format, "Format_OffsetOutOfRange", null); - return false; - } - - // DateTimeOffset should still honor the AdjustToUniversal flag for consistency with DateTime. It means you - // want to return an adjusted UTC value, so store the utcTicks in the DateTime and set the offset to zero - if ((styles & DateTimeStyles.AdjustToUniversal) != 0) - { - if (((result.flags & ParseFlags.TimeZoneUsed) == 0) && ((styles & DateTimeStyles.AssumeUniversal) == 0)) - { - // Handle the special case where the timeZoneOffset was defaulted to Local - Boolean toUtcResult = AdjustTimeZoneToUniversal(ref result); - result.timeZoneOffset = TimeSpan.Zero; - return toUtcResult; - } - - // The constructor should always succeed because of the range check earlier in the function - // Althought it is UTC, internally DateTimeOffset does not use this flag - result.parsedDate = new DateTime(utcTicks, DateTimeKind.Utc); - result.timeZoneOffset = TimeSpan.Zero; - } - - return true; - } - - - // - // Adjust the specified time to universal time based on the supplied timezone. - // E.g. when parsing "2001/06/08 14:00-07:00", - // the time is 2001/06/08 14:00, and timeZoneOffset = -07:00. - // The result will be "2001/06/08 21:00" - // - private static Boolean AdjustTimeZoneToUniversal(ref DateTimeResult result) - { - long resultTicks = result.parsedDate.Ticks; - resultTicks -= result.timeZoneOffset.Ticks; - if (resultTicks < 0) - { - resultTicks += Calendar.TicksPerDay; - } - - if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) - { - result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null); - return false; - } - result.parsedDate = new DateTime(resultTicks, DateTimeKind.Utc); - return true; - } - - // - // Adjust the specified time to universal time based on the supplied timezone, - // and then convert to local time. - // E.g. when parsing "2001/06/08 14:00-04:00", and local timezone is GMT-7. - // the time is 2001/06/08 14:00, and timeZoneOffset = -05:00. - // The result will be "2001/06/08 11:00" - // - private static Boolean AdjustTimeZoneToLocal(ref DateTimeResult result, bool bTimeOnly) - { - long resultTicks = result.parsedDate.Ticks; - // Convert to local ticks - TimeZoneInfo tz = TimeZoneInfo.Local; - Boolean isAmbiguousLocalDst = false; - if (resultTicks < Calendar.TicksPerDay) - { - // - // This is time of day. - // - - // Adjust timezone. - resultTicks -= result.timeZoneOffset.Ticks; - // If the time is time of day, use the current timezone offset. - resultTicks += tz.GetUtcOffset(bTimeOnly ? DateTime.Now : result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; - - if (resultTicks < 0) - { - resultTicks += Calendar.TicksPerDay; - } - } - else - { - // Adjust timezone to GMT. - resultTicks -= result.timeZoneOffset.Ticks; - if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) - { - // If the result ticks is greater than DateTime.MaxValue, we can not create a DateTime from this ticks. - // In this case, keep using the old code. - resultTicks += tz.GetUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; - } - else - { - // Convert the GMT time to local time. - DateTime utcDt = new DateTime(resultTicks, DateTimeKind.Utc); - Boolean isDaylightSavings = false; - resultTicks += TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks; - } - } - if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) - { - result.parsedDate = DateTime.MinValue; - result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null); - return false; - } - result.parsedDate = new DateTime(resultTicks, DateTimeKind.Local, isAmbiguousLocalDst); - return true; - } - - // - // Parse the ISO8601 format string found during Parse(); - // - // - private static bool ParseISO8601(ref DateTimeRawInfo raw, ref __DTString str, DateTimeStyles styles, ref DateTimeResult result) - { - if (raw.year < 0 || raw.GetNumber(0) < 0 || raw.GetNumber(1) < 0) - { - } - str.Index--; - int hour, minute; - int second = 0; - double partSecond = 0; - - str.SkipWhiteSpaces(); - if (!ParseDigits(ref str, 2, out hour)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.SkipWhiteSpaces(); - if (!str.Match(':')) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.SkipWhiteSpaces(); - if (!ParseDigits(ref str, 2, out minute)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.SkipWhiteSpaces(); - if (str.Match(':')) - { - str.SkipWhiteSpaces(); - if (!ParseDigits(ref str, 2, out second)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - if (str.Match('.')) - { - if (!ParseFraction(ref str, out partSecond)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.Index--; - } - str.SkipWhiteSpaces(); - } - if (str.GetNext()) - { - char ch = str.GetChar(); - if (ch == '+' || ch == '-') - { - result.flags |= ParseFlags.TimeZoneUsed; - if (!ParseTimeZone(ref str, ref result.timeZoneOffset)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - else if (ch == 'Z' || ch == 'z') - { - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeSpan.Zero; - result.flags |= ParseFlags.TimeZoneUtc; - } - else - { - str.Index--; - } - str.SkipWhiteSpaces(); - if (str.Match('#')) - { - if (!VerifyValidPunctuation(ref str)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.SkipWhiteSpaces(); - } - if (str.Match('\0')) - { - if (!VerifyValidPunctuation(ref str)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - if (str.GetNext()) - { - // If this is true, there were non-white space characters remaining in the DateTime - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - - DateTime time; - Calendar calendar = GregorianCalendar.GetDefaultInstance(); - if (!calendar.TryToDateTime(raw.year, raw.GetNumber(0), raw.GetNumber(1), - hour, minute, second, 0, result.era, out time)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - - time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond)); - result.parsedDate = time; - if (!DetermineTimeZoneAdjustments(ref result, styles, false)) - { - return false; - } - return true; - } - - - //////////////////////////////////////////////////////////////////////// - // - // Actions: - // Parse the current word as a Hebrew number. - // This is used by DateTime.ParseExact(). - // - //////////////////////////////////////////////////////////////////////// - - internal static bool MatchHebrewDigits(ref __DTString str, int digitLen, out int number) - { - number = 0; - - // Create a context object so that we can parse the Hebrew number text character by character. - HebrewNumberParsingContext context = new HebrewNumberParsingContext(0); - - // Set this to ContinueParsing so that we will run the following while loop in the first time. - HebrewNumberParsingState state = HebrewNumberParsingState.ContinueParsing; - - while (state == HebrewNumberParsingState.ContinueParsing && str.GetNext()) - { - state = HebrewNumber.ParseByChar(str.GetChar(), ref context); - } - - if (state == HebrewNumberParsingState.FoundEndOfHebrewNumber) - { - // If we have reached a terminal state, update the result and returns. - number = context.result; - return (true); - } - - // If we run out of the character before reaching FoundEndOfHebrewNumber, or - // the state is InvalidHebrewNumber or ContinueParsing, we fail to match a Hebrew number. - // Return an error. - return false; - } - - /*=================================ParseDigits================================== - **Action: Parse the number string in __DTString that are formatted using - ** the following patterns: - ** "0", "00", and "000..0" - **Returns: the integer value - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if error in parsing number. - ==============================================================================*/ - - internal static bool ParseDigits(ref __DTString str, int digitLen, out int result) - { - if (digitLen == 1) - { - // 1 really means 1 or 2 for this call - return ParseDigits(ref str, 1, 2, out result); - } - else - { - return ParseDigits(ref str, digitLen, digitLen, out result); - } - } - - internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDigitLen, out int result) - { - Debug.Assert(minDigitLen > 0, "minDigitLen > 0"); - Debug.Assert(maxDigitLen < 9, "maxDigitLen < 9"); - Debug.Assert(minDigitLen <= maxDigitLen, "minDigitLen <= maxDigitLen"); - result = 0; - int startingIndex = str.Index; - int tokenLength = 0; - while (tokenLength < maxDigitLen) - { - if (!str.GetNextDigit()) - { - str.Index--; - break; - } - result = result * 10 + str.GetDigit(); - tokenLength++; - } - if (tokenLength < minDigitLen) - { - str.Index = startingIndex; - return false; - } - return true; - } - - /*=================================ParseFractionExact================================== - **Action: Parse the number string in __DTString that are formatted using - ** the following patterns: - ** "0", "00", and "000..0" - **Returns: the fraction value - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if error in parsing number. - ==============================================================================*/ - - private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref double result) - { - if (!str.GetNextDigit()) - { - str.Index--; - return false; - } - result = str.GetDigit(); - - int digitLen = 1; - for (; digitLen < maxDigitLen; digitLen++) - { - if (!str.GetNextDigit()) - { - str.Index--; - break; - } - result = result * 10 + str.GetDigit(); - } - - result = ((double)result / Math.Pow(10, digitLen)); - return (digitLen == maxDigitLen); - } - - /*=================================ParseSign================================== - **Action: Parse a positive or a negative sign. - **Returns: true if postive sign. flase if negative sign. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if end of string is encountered or a sign - ** symbol is not found. - ==============================================================================*/ - - private static bool ParseSign(ref __DTString str, ref bool result) - { - if (!str.GetNext()) - { - // A sign symbol ('+' or '-') is expected. However, end of string is encountered. - return false; - } - char ch = str.GetChar(); - if (ch == '+') - { - result = true; - return (true); - } - else if (ch == '-') - { - result = false; - return (true); - } - // A sign symbol ('+' or '-') is expected. - return false; - } - - /*=================================ParseTimeZoneOffset================================== - **Action: Parse the string formatted using "z", "zz", "zzz" in DateTime.Format(). - **Returns: the TimeSpan for the parsed timezone offset. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - ** len: the repeated number of the "z" - **Exceptions: FormatException if errors in parsing. - ==============================================================================*/ - - private static bool ParseTimeZoneOffset(ref __DTString str, int len, ref TimeSpan result) - { - bool isPositive = true; - int hourOffset; - int minuteOffset = 0; - - switch (len) - { - case 1: - case 2: - if (!ParseSign(ref str, ref isPositive)) - { - return (false); - } - if (!ParseDigits(ref str, len, out hourOffset)) - { - return (false); - } - break; - default: - if (!ParseSign(ref str, ref isPositive)) - { - return (false); - } - - // Parsing 1 digit will actually parse 1 or 2. - if (!ParseDigits(ref str, 1, out hourOffset)) - { - return (false); - } - // ':' is optional. - if (str.Match(":")) - { - // Found ':' - if (!ParseDigits(ref str, 2, out minuteOffset)) - { - return (false); - } - } - else - { - // Since we can not match ':', put the char back. - str.Index--; - if (!ParseDigits(ref str, 2, out minuteOffset)) - { - return (false); - } - } - break; - } - if (minuteOffset < 0 || minuteOffset >= 60) - { - return false; - } - - result = (new TimeSpan(hourOffset, minuteOffset, 0)); - if (!isPositive) - { - result = result.Negate(); - } - return (true); - } - - /*=================================MatchAbbreviatedMonthName================================== - **Action: Parse the abbreviated month name from string starting at str.Index. - **Returns: A value from 1 to 12 for the first month to the twelveth month. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if an abbreviated month name can not be found. - ==============================================================================*/ - - private static bool MatchAbbreviatedMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) - { - int maxMatchStrLen = 0; - result = -1; - if (str.GetNext()) - { - // - // Scan the month names (note that some calendars has 13 months) and find - // the matching month name which has the max string length. - // We need to do this because some cultures (e.g. "cs-CZ") which have - // abbreviated month names with the same prefix. - // - int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13); - for (int i = 1; i <= monthsInYear; i++) - { - String searchStr = dtfi.GetAbbreviatedMonthName(i); - int matchStrLen = searchStr.Length; - if (dtfi.HasSpacesInMonthNames - ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen) - : str.MatchSpecifiedWord(searchStr)) - { - if (matchStrLen > maxMatchStrLen) - { - maxMatchStrLen = matchStrLen; - result = i; - } - } - } - - // Search leap year form. - if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) - { - int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen); - // We found a longer match in the leap year month name. Use this as the result. - // The result from MatchLongestWords is 0 ~ length of word array. - // So we increment the result by one to become the month value. - if (tempResult >= 0) - { - result = tempResult + 1; - } - } - } - if (result > 0) - { - str.Index += (maxMatchStrLen - 1); - return (true); - } - return false; - } - - /*=================================MatchMonthName================================== - **Action: Parse the month name from string starting at str.Index. - **Returns: A value from 1 to 12 indicating the first month to the twelveth month. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if a month name can not be found. - ==============================================================================*/ - - private static bool MatchMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) - { - int maxMatchStrLen = 0; - result = -1; - if (str.GetNext()) - { - // - // Scan the month names (note that some calendars has 13 months) and find - // the matching month name which has the max string length. - // We need to do this because some cultures (e.g. "vi-VN") which have - // month names with the same prefix. - // - int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13); - for (int i = 1; i <= monthsInYear; i++) - { - String searchStr = dtfi.GetMonthName(i); - int matchStrLen = searchStr.Length; - if (dtfi.HasSpacesInMonthNames - ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen) - : str.MatchSpecifiedWord(searchStr)) - { - if (matchStrLen > maxMatchStrLen) - { - maxMatchStrLen = matchStrLen; - result = i; - } - } - } - - // Search genitive form. - if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) - { - int tempResult = str.MatchLongestWords(dtfi.MonthGenitiveNames, ref maxMatchStrLen); - // We found a longer match in the genitive month name. Use this as the result. - // The result from MatchLongestWords is 0 ~ length of word array. - // So we increment the result by one to become the month value. - if (tempResult >= 0) - { - result = tempResult + 1; - } - } - - // Search leap year form. - if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) - { - int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen); - // We found a longer match in the leap year month name. Use this as the result. - // The result from MatchLongestWords is 0 ~ length of word array. - // So we increment the result by one to become the month value. - if (tempResult >= 0) - { - result = tempResult + 1; - } - } - } - - if (result > 0) - { - str.Index += (maxMatchStrLen - 1); - return (true); - } - return false; - } - - /*=================================MatchAbbreviatedDayName================================== - **Action: Parse the abbreviated day of week name from string starting at str.Index. - **Returns: A value from 0 to 6 indicating Sunday to Saturday. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if a abbreviated day of week name can not be found. - ==============================================================================*/ - - private static bool MatchAbbreviatedDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) - { - int maxMatchStrLen = 0; - result = -1; - if (str.GetNext()) - { - for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) - { - String searchStr = dtfi.GetAbbreviatedDayName(i); - int matchStrLen = searchStr.Length; - if (dtfi.HasSpacesInDayNames - ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen) - : str.MatchSpecifiedWord(searchStr)) - { - if (matchStrLen > maxMatchStrLen) - { - maxMatchStrLen = matchStrLen; - result = (int)i; - } - } - } - } - if (result >= 0) - { - str.Index += maxMatchStrLen - 1; - return (true); - } - return false; - } - - /*=================================MatchDayName================================== - **Action: Parse the day of week name from string starting at str.Index. - **Returns: A value from 0 to 6 indicating Sunday to Saturday. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if a day of week name can not be found. - ==============================================================================*/ - - private static bool MatchDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) - { - // Turkish (tr-TR) got day names with the same prefix. - int maxMatchStrLen = 0; - result = -1; - if (str.GetNext()) - { - for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) - { - String searchStr = dtfi.GetDayName(i); - int matchStrLen = searchStr.Length; - if (dtfi.HasSpacesInDayNames - ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen) - : str.MatchSpecifiedWord(searchStr)) - { - if (matchStrLen > maxMatchStrLen) - { - maxMatchStrLen = matchStrLen; - result = (int)i; - } - } - } - } - if (result >= 0) - { - str.Index += maxMatchStrLen - 1; - return (true); - } - return false; - } - - /*=================================MatchEraName================================== - **Action: Parse era name from string starting at str.Index. - **Returns: An era value. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if an era name can not be found. - ==============================================================================*/ - - private static bool MatchEraName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) - { - if (str.GetNext()) - { - int[] eras = dtfi.Calendar.Eras; - - if (eras != null) - { - for (int i = 0; i < eras.Length; i++) - { - String searchStr = dtfi.GetEraName(eras[i]); - if (str.MatchSpecifiedWord(searchStr)) - { - str.Index += (searchStr.Length - 1); - result = eras[i]; - return (true); - } - searchStr = dtfi.GetAbbreviatedEraName(eras[i]); - if (str.MatchSpecifiedWord(searchStr)) - { - str.Index += (searchStr.Length - 1); - result = eras[i]; - return (true); - } - } - } - } - return false; - } - - /*=================================MatchTimeMark================================== - **Action: Parse the time mark (AM/PM) from string starting at str.Index. - **Returns: TM_AM or TM_PM. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if a time mark can not be found. - ==============================================================================*/ - - private static bool MatchTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result) - { - result = TM.NotSet; - // In some cultures have empty strings in AM/PM mark. E.g. af-ZA (0x0436), the AM mark is "", and PM mark is "nm". - if (dtfi.AMDesignator.Length == 0) - { - result = TM.AM; - } - if (dtfi.PMDesignator.Length == 0) - { - result = TM.PM; - } - - if (str.GetNext()) - { - String searchStr = dtfi.AMDesignator; - if (searchStr.Length > 0) - { - if (str.MatchSpecifiedWord(searchStr)) - { - // Found an AM timemark with length > 0. - str.Index += (searchStr.Length - 1); - result = TM.AM; - return (true); - } - } - searchStr = dtfi.PMDesignator; - if (searchStr.Length > 0) - { - if (str.MatchSpecifiedWord(searchStr)) - { - // Found a PM timemark with length > 0. - str.Index += (searchStr.Length - 1); - result = TM.PM; - return (true); - } - } - str.Index--; // Undo the GetNext call. - } - if (result != TM.NotSet) - { - // If one of the AM/PM marks is empty string, return the result. - return (true); - } - return false; - } - - /*=================================MatchAbbreviatedTimeMark================================== - **Action: Parse the abbreviated time mark (AM/PM) from string starting at str.Index. - **Returns: TM_AM or TM_PM. - **Arguments: str: a __DTString. The parsing will start from the - ** next character after str.Index. - **Exceptions: FormatException if a abbreviated time mark can not be found. - ==============================================================================*/ - - private static bool MatchAbbreviatedTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result) - { - // NOTENOTE : the assumption here is that abbreviated time mark is the first - // character of the AM/PM designator. If this invariant changes, we have to - // change the code below. - if (str.GetNext()) - { - if (str.GetChar() == dtfi.AMDesignator[0]) - { - result = TM.AM; - return (true); - } - if (str.GetChar() == dtfi.PMDesignator[0]) - { - result = TM.PM; - return (true); - } - } - return false; - } - - /*=================================CheckNewValue================================== - **Action: Check if currentValue is initialized. If not, return the newValue. - ** If yes, check if the current value is equal to newValue. Return false - ** if they are not equal. This is used to check the case like "d" and "dd" are both - ** used to format a string. - **Returns: the correct value for currentValue. - **Arguments: - **Exceptions: - ==============================================================================*/ - - private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, ref DateTimeResult result) - { - if (currentValue == -1) - { - currentValue = newValue; - return (true); - } - else - { - if (newValue != currentValue) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", patternChar); - return (false); - } - } - return (true); - } - - private static DateTime GetDateTimeNow(ref DateTimeResult result, ref DateTimeStyles styles) - { - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - if ((result.flags & ParseFlags.TimeZoneUsed) != 0) - { - // use the supplied offset to calculate 'Now' - return new DateTime(DateTime.UtcNow.Ticks + result.timeZoneOffset.Ticks, DateTimeKind.Unspecified); - } - else if ((styles & DateTimeStyles.AssumeUniversal) != 0) - { - // assume the offset is Utc - return DateTime.UtcNow; - } - } - - // assume the offset is Local - return DateTime.Now; - } - - private static bool CheckDefaultDateTime(ref DateTimeResult result, ref Calendar cal, DateTimeStyles styles) - { - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - // DateTimeOffset.Parse should allow dates without a year, but only if there is also no time zone marker; - // e.g. "May 1 5pm" is OK, but "May 1 5pm -08:30" is not. This is somewhat pragmatic, since we would - // have to rearchitect parsing completely to allow this one case to correctly handle things like leap - // years and leap months. Is is an extremely corner case, and DateTime is basically incorrect in that - // case today. - // - // values like "11:00Z" or "11:00 -3:00" are also acceptable - // - // if ((month or day is set) and (year is not set and time zone is set)) - // - if (((result.Month != -1) || (result.Day != -1)) - && ((result.Year == -1 || ((result.flags & ParseFlags.YearDefault) != 0)) && (result.flags & ParseFlags.TimeZoneUsed) != 0)) - { - result.SetFailure(ParseFailureKind.Format, "Format_MissingIncompleteDate", null); - return false; - } - } - - - if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1)) - { - /* - The following table describes the behaviors of getting the default value - when a certain year/month/day values are missing. - - An "X" means that the value exists. And "--" means that value is missing. - - Year Month Day => ResultYear ResultMonth ResultDay Note - - X X X Parsed year Parsed month Parsed day - X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month. - X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year. - X -- -- Parsed year First month First day If we have only the year, assume the first day of that year. - - -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year. - -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day. - -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month. - -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date. - - */ - - DateTime now = GetDateTimeNow(ref result, ref styles); - if (result.Month == -1 && result.Day == -1) - { - if (result.Year == -1) - { - if ((styles & DateTimeStyles.NoCurrentDateDefault) != 0) - { - // If there is no year/month/day values, and NoCurrentDateDefault flag is used, - // set the year/month/day value to the beginning year/month/day of DateTime(). - // Note we should be using Gregorian for the year/month/day. - cal = GregorianCalendar.GetDefaultInstance(); - result.Year = result.Month = result.Day = 1; - } - else - { - // Year/Month/Day are all missing. - result.Year = cal.GetYear(now); - result.Month = cal.GetMonth(now); - result.Day = cal.GetDayOfMonth(now); - } - } - else - { - // Month/Day are both missing. - result.Month = 1; - result.Day = 1; - } - } - else - { - if (result.Year == -1) - { - result.Year = cal.GetYear(now); - } - if (result.Month == -1) - { - result.Month = 1; - } - if (result.Day == -1) - { - result.Day = 1; - } - } - } - // Set Hour/Minute/Second to zero if these value are not in str. - if (result.Hour == -1) result.Hour = 0; - if (result.Minute == -1) result.Minute = 0; - if (result.Second == -1) result.Second = 0; - if (result.era == -1) result.era = Calendar.CurrentEra; - return true; - } - - // Expand a pre-defined format string (like "D" for long date) to the real format that - // we are going to use in the date time parsing. - // This method also set the dtfi according/parseInfo to some special pre-defined - // formats. - // - private static String ExpandPredefinedFormat(String format, ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result) - { - // - // Check the format to see if we need to override the dtfi to be InvariantInfo, - // and see if we need to set up the userUniversalTime flag. - // - switch (format[0]) - { - case 'o': - case 'O': // Round Trip Format - parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); - dtfi = DateTimeFormatInfo.InvariantInfo; - break; - case 'r': - case 'R': // RFC 1123 Standard. (in Universal time) - parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); - dtfi = DateTimeFormatInfo.InvariantInfo; - - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - result.flags |= ParseFlags.Rfc1123Pattern; - } - break; - case 's': // Sortable format (in local time) - dtfi = DateTimeFormatInfo.InvariantInfo; - parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); - break; - case 'u': // Universal time format in sortable format. - parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); - dtfi = DateTimeFormatInfo.InvariantInfo; - - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - result.flags |= ParseFlags.UtcSortPattern; - } - break; - case 'U': // Universal time format with culture-dependent format. - parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = new TimeSpan(0); - result.flags |= ParseFlags.TimeZoneUtc; - if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) - { - dtfi = (DateTimeFormatInfo)dtfi.Clone(); - dtfi.Calendar = GregorianCalendar.GetDefaultInstance(); - } - break; - } - - // - // Expand the pre-defined format character to the real format from DateTimeFormatInfo. - // - return (DateTimeFormat.GetRealFormat(format, dtfi)); - } - - - - - - // Given a specified format character, parse and update the parsing result. - // - private static bool ParseByFormat( - ref __DTString str, - ref __DTString format, - ref ParsingInfo parseInfo, - DateTimeFormatInfo dtfi, - ref DateTimeResult result) - { - int tokenLen = 0; - int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0; - double tempFraction = 0; - TM tempTimeMark = 0; - - char ch = format.GetChar(); - - switch (ch) - { - case 'y': - tokenLen = format.GetRepeatCount(); - bool parseResult; - if (dtfi.HasForceTwoDigitYears) - { - parseResult = ParseDigits(ref str, 1, 4, out tempYear); - } - else - { - if (tokenLen <= 2) - { - parseInfo.fUseTwoDigitYear = true; - } - parseResult = ParseDigits(ref str, tokenLen, out tempYear); - } - if (!parseResult && parseInfo.fCustomNumberParser) - { - parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear); - } - if (!parseResult) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if (!CheckNewValue(ref result.Year, tempYear, ch, ref result)) - { - return (false); - } - break; - case 'M': - tokenLen = format.GetRepeatCount(); - if (tokenLen <= 2) - { - if (!ParseDigits(ref str, tokenLen, out tempMonth)) - { - if (!parseInfo.fCustomNumberParser || - !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - } - else - { - if (tokenLen == 3) - { - if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - else - { - if (!MatchMonthName(ref str, dtfi, ref tempMonth)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - result.flags |= ParseFlags.ParsedMonthName; - } - if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result)) - { - return (false); - } - break; - case 'd': - // Day & Day of week - tokenLen = format.GetRepeatCount(); - if (tokenLen <= 2) - { - // "d" & "dd" - - if (!ParseDigits(ref str, tokenLen, out tempDay)) - { - if (!parseInfo.fCustomNumberParser || - !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - if (!CheckNewValue(ref result.Day, tempDay, ch, ref result)) - { - return (false); - } - } - else - { - if (tokenLen == 3) - { - // "ddd" - if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - else - { - // "dddd*" - if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result)) - { - return (false); - } - } - break; - case 'g': - tokenLen = format.GetRepeatCount(); - // Put the era value in result.era. - if (!MatchEraName(ref str, dtfi, ref result.era)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - break; - case 'h': - parseInfo.fUseHour12 = true; - tokenLen = format.GetRepeatCount(); - if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) - { - return (false); - } - break; - case 'H': - tokenLen = format.GetRepeatCount(); - if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) - { - return (false); - } - break; - case 'm': - tokenLen = format.GetRepeatCount(); - if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempMinute)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result)) - { - return (false); - } - break; - case 's': - tokenLen = format.GetRepeatCount(); - if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempSecond)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result)) - { - return (false); - } - break; - case 'f': - case 'F': - tokenLen = format.GetRepeatCount(); - if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits) - { - if (!ParseFractionExact(ref str, tokenLen, ref tempFraction)) - { - if (ch == 'f') - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - if (result.fraction < 0) - { - result.fraction = tempFraction; - } - else - { - if (tempFraction != result.fraction) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch); - return (false); - } - } - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - break; - case 't': - // AM/PM designator - tokenLen = format.GetRepeatCount(); - if (tokenLen == 1) - { - if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - else - { - if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - } - - if (parseInfo.timeMark == TM.NotSet) - { - parseInfo.timeMark = tempTimeMark; - } - else - { - if (parseInfo.timeMark != tempTimeMark) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch); - return (false); - } - } - break; - case 'z': - // timezone offset - tokenLen = format.GetRepeatCount(); - { - TimeSpan tempTimeZoneOffset = new TimeSpan(0); - if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'z'); - return (false); - } - result.timeZoneOffset = tempTimeZoneOffset; - result.flags |= ParseFlags.TimeZoneUsed; - } - break; - case 'Z': - if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'Z'); - return (false); - } - - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = new TimeSpan(0); - result.flags |= ParseFlags.TimeZoneUtc; - - // The updating of the indexes is to reflect that ParseExact MatchXXX methods assume that - // they need to increment the index and Parse GetXXX do not. Since we are calling a Parse - // method from inside ParseExact we need to adjust this. Long term, we should try to - // eliminate this discrepancy. - str.Index++; - if (!GetTimeZoneName(ref str)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - str.Index--; - break; - case 'K': - // This should parse either as a blank, the 'Z' character or a local offset like "-07:00" - if (str.Match('Z')) - { - if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K'); - return (false); - } - - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = new TimeSpan(0); - result.flags |= ParseFlags.TimeZoneUtc; - } - else if (str.Match('+') || str.Match('-')) - { - str.Index--; // Put the character back for the parser - TimeSpan tempTimeZoneOffset = new TimeSpan(0); - if (!ParseTimeZoneOffset(ref str, 3, ref tempTimeZoneOffset)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return (false); - } - if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K'); - return (false); - } - result.timeZoneOffset = tempTimeZoneOffset; - result.flags |= ParseFlags.TimeZoneUsed; - } - // Otherwise it is unspecified and we consume no characters - break; - case ':': - // We match the separator in time pattern with the character in the time string if both equal to ':' or the date separator is matching the characters in the date string - // We have to exclude the case when the time separator is more than one character and starts with ':' something like "::" for instance. - if (((dtfi.TimeSeparator.Length > 1 && dtfi.TimeSeparator[0] == ':') || !str.Match(':')) && - !str.Match(dtfi.TimeSeparator)) - { - // A time separator is expected. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - break; - case '/': - // We match the separator in date pattern with the character in the date string if both equal to '/' or the date separator is matching the characters in the date string - // We have to exclude the case when the date separator is more than one character and starts with '/' something like "//" for instance. - if (((dtfi.DateSeparator.Length > 1 && dtfi.DateSeparator[0] == '/') || !str.Match('/')) && - !str.Match(dtfi.DateSeparator)) - { - // A date separator is expected. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - break; - case '\"': - case '\'': - StringBuilder enquotedString = new StringBuilder(); - // Use ParseQuoteString so that we can handle escape characters within the quoted string. - if (!TryParseQuoteString(format.Value, format.Index, enquotedString, out tokenLen)) - { - result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch); - return (false); - } - format.Index += tokenLen - 1; - - // Some cultures uses space in the quoted string. E.g. Spanish has long date format as: - // "dddd, dd' de 'MMMM' de 'yyyy". When inner spaces flag is set, we should skip whitespaces if there is space - // in the quoted string. - String quotedStr = enquotedString.ToString(); - - for (int i = 0; i < quotedStr.Length; i++) - { - if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite) - { - str.SkipWhiteSpaces(); - } - else if (!str.Match(quotedStr[i])) - { - // Can not find the matching quoted string. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - - // The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively. We cannot - // correct this mistake for DateTime.ParseExact for compatibility reasons, but we can - // fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released - // with this issue. - if ((result.flags & ParseFlags.CaptureOffset) != 0) - { - if ((result.flags & ParseFlags.Rfc1123Pattern) != 0 && quotedStr == GMTName) - { - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeSpan.Zero; - } - else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName) - { - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeSpan.Zero; - } - } - - break; - case '%': - // Skip this so we can get to the next pattern character. - // Used in case like "%d", "%y" - - // Make sure the next character is not a '%' again. - if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%') - { - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - break; - case '\\': - // Escape character. For example, "\d". - // Get the next character in format, and see if we can - // find a match in str. - if (format.GetNext()) - { - if (!str.Match(format.GetChar())) - { - // Can not find a match for the escaped character. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - else - { - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - break; - case '.': - if (!str.Match(ch)) - { - if (format.GetNext()) - { - // If we encounter the pattern ".F", and the dot is not present, it is an optional - // second fraction and we can skip this format. - if (format.Match('F')) - { - format.GetRepeatCount(); - break; - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - break; - default: - if (ch == ' ') - { - if (parseInfo.fAllowInnerWhite) - { - // Skip whitespaces if AllowInnerWhite. - // Do nothing here. - } - else - { - if (!str.Match(ch)) - { - // If the space does not match, and trailing space is allowed, we do - // one more step to see if the next format character can lead to - // successful parsing. - // This is used to deal with special case that a empty string can match - // a specific pattern. - // The example here is af-ZA, which has a time format like "hh:mm:ss tt". However, - // its AM symbol is "" (empty string). If fAllowTrailingWhite is used, and time is in - // the AM, we will trim the whitespaces at the end, which will lead to a failure - // when we are trying to match the space before "tt". - if (parseInfo.fAllowTrailingWhite) - { - if (format.GetNext()) - { - if (ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) - { - return (true); - } - } - } - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - // Found a macth. - } - } - else - { - if (format.MatchSpecifiedWord(GMTName)) - { - format.Index += (GMTName.Length - 1); - // Found GMT string in format. This means the DateTime string - // is in GMT timezone. - result.flags |= ParseFlags.TimeZoneUsed; - result.timeZoneOffset = TimeSpan.Zero; - if (!str.Match(GMTName)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - else if (!str.Match(ch)) - { - // ch is expected. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - break; - } // switch - return (true); - } - - // - // The pos should point to a quote character. This method will - // get the string enclosed by the quote character. - // - internal static bool TryParseQuoteString(String format, int pos, StringBuilder result, out int returnValue) - { - // - // NOTE : pos will be the index of the quote character in the 'format' string. - // - returnValue = 0; - int formatLen = format.Length; - int beginPos = pos; - char quoteChar = format[pos++]; // Get the character used to quote the following string. - - bool foundQuote = false; - while (pos < formatLen) - { - char ch = format[pos++]; - if (ch == quoteChar) - { - foundQuote = true; - break; - } - else if (ch == '\\') - { - // The following are used to support escaped character. - // Escaped character is also supported in the quoted string. - // Therefore, someone can use a format like "'minute:' mm\"" to display: - // minute: 45" - // because the second double quote is escaped. - if (pos < formatLen) - { - result.Append(format[pos++]); - } - else - { - // - // This means that '\' is at the end of the formatting string. - // - return false; - } - } - else - { - result.Append(ch); - } - } - - if (!foundQuote) - { - // Here we can't find the matching quote. - return false; - } - - // - // Return the character count including the begin/end quote characters and enclosed string. - // - returnValue = (pos - beginPos); - return true; - } - - - - - /*=================================DoStrictParse================================== - **Action: Do DateTime parsing using the format in formatParam. - **Returns: The parsed DateTime. - **Arguments: - **Exceptions: - ** - **Notes: - ** When the following general formats are used, InvariantInfo is used in dtfi: - ** 'r', 'R', 's'. - ** When the following general formats are used, the time is assumed to be in Universal time. - ** - **Limitations: - ** Only GregarianCalendar is supported for now. - ** Only support GMT timezone. - ==============================================================================*/ - - private static bool DoStrictParse( - String s, - String formatParam, - DateTimeStyles styles, - DateTimeFormatInfo dtfi, - ref DateTimeResult result) - { - ParsingInfo parseInfo = new ParsingInfo(); - parseInfo.Init(); - - parseInfo.calendar = dtfi.Calendar; - parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0); - parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0); - - // We need the original values of the following two below. - String originalFormat = formatParam; - - if (formatParam.Length == 1) - { - if (((result.flags & ParseFlags.CaptureOffset) != 0) && formatParam[0] == 'U') - { - // The 'U' format is not allowed for DateTimeOffset - result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null); - return false; - } - formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result); - } - - bool bTimeOnly = false; - result.calendar = parseInfo.calendar; - - if (parseInfo.calendar.ID == Calendar.CAL_HEBREW) - { - parseInfo.parseNumberDelegate = m_hebrewNumberParser; - parseInfo.fCustomNumberParser = true; - } - - // Reset these values to negative one so that we could throw exception - // if we have parsed every item twice. - result.Hour = result.Minute = result.Second = -1; - - __DTString format = new __DTString(formatParam, dtfi, false); - __DTString str = new __DTString(s, dtfi, false); - - if (parseInfo.fAllowTrailingWhite) - { - // Trim trailing spaces if AllowTrailingWhite. - format.TrimTail(); - format.RemoveTrailingInQuoteSpaces(); - str.TrimTail(); - } - - if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) - { - format.SkipWhiteSpaces(); - format.RemoveLeadingInQuoteSpaces(); - str.SkipWhiteSpaces(); - } - - // - // Scan every character in format and match the pattern in str. - // - while (format.GetNext()) - { - // We trim inner spaces here, so that we will not eat trailing spaces when - // AllowTrailingWhite is not used. - if (parseInfo.fAllowInnerWhite) - { - str.SkipWhiteSpaces(); - } - if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) - { - return (false); - } - } - - if (str.Index < str.Value.Length - 1) - { - // There are still remaining character in str. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - - if (parseInfo.fUseTwoDigitYear && ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) == 0)) - { - // A two digit year value is expected. Check if the parsed year value is valid. - if (result.Year >= 100) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - try - { - result.Year = parseInfo.calendar.ToFourDigitYear(result.Year); - } - catch (ArgumentOutOfRangeException e) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e); - return false; - } - } - - if (parseInfo.fUseHour12) - { - if (parseInfo.timeMark == TM.NotSet) - { - // hh is used, but no AM/PM designator is specified. - // Assume the time is AM. - // Don't throw exceptions in here becasue it is very confusing for the caller. - // I always got confused myself when I use "hh:mm:ss" to parse a time string, - // and ParseExact() throws on me (because I didn't use the 24-hour clock 'HH'). - parseInfo.timeMark = TM.AM; - } - if (result.Hour > 12) - { - // AM/PM is used, but the value for HH is too big. - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - if (parseInfo.timeMark == TM.AM) - { - if (result.Hour == 12) - { - result.Hour = 0; - } - } - else - { - result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12; - } - } - else - { - // Military (24-hour time) mode - // - // AM cannot be set with a 24-hour time like 17:15. - // PM cannot be set with a 24-hour time like 03:15. - if ((parseInfo.timeMark == TM.AM && result.Hour >= 12) - || (parseInfo.timeMark == TM.PM && result.Hour < 12)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); - return false; - } - } - - - // Check if the parased string only contains hour/minute/second values. - bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1); - if (!CheckDefaultDateTime(ref result, ref parseInfo.calendar, styles)) - { - return false; - } - - if (!bTimeOnly && dtfi.HasYearMonthAdjustment) - { - if (!dtfi.YearMonthAdjustment(ref result.Year, ref result.Month, ((result.flags & ParseFlags.ParsedMonthName) != 0))) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - } - if (!parseInfo.calendar.TryToDateTime(result.Year, result.Month, result.Day, - result.Hour, result.Minute, result.Second, 0, result.era, out result.parsedDate)) - { - result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null); - return false; - } - if (result.fraction > 0) - { - result.parsedDate = result.parsedDate.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond)); - } - - // - // We have to check day of week before we adjust to the time zone. - // It is because the value of day of week may change after adjusting - // to the time zone. - // - if (parseInfo.dayOfWeek != -1) - { - // - // Check if day of week is correct. - // - if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(result.parsedDate)) - { - result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null); - return false; - } - } - - - if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) - { - return false; - } - return true; - } - - private static Exception GetDateTimeParseException(ref DateTimeResult result) - { - switch (result.failure) - { - case ParseFailureKind.ArgumentNull: - return new ArgumentNullException(result.failureArgumentName, SR.GetResourceString(result.failureMessageID)); - case ParseFailureKind.Format: - return new FormatException(SR.GetResourceString(result.failureMessageID)); - case ParseFailureKind.FormatWithParameter: - return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.failureMessageFormatArgument)); - case ParseFailureKind.FormatBadDateTimeCalendar: - return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.calendar)); - default: - Debug.Assert(false, "Unkown DateTimeParseFailure: " + result); - return null; - } - } - - // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing - // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console: - // - // COMPlus_LogEnable=1 - // COMPlus_LogToConsole=1 - // COMPlus_LogLevel=9 - // COMPlus_ManagedLogFacility=0x00001000 - [Pure] - [Conditional("_LOGGING")] - internal static void LexTraceExit(string message, DS dps) - { -#if _LOGGING - if (!_tracingEnabled) - return; - BCLDebug.Trace("DATETIME", "[DATETIME] Lex return {0}, DS.{1}", message, dps); -#endif // _LOGGING - } - [Pure] - [Conditional("_LOGGING")] - internal static void PTSTraceExit(DS dps, bool passed) - { -#if _LOGGING - if (!_tracingEnabled) - return; - BCLDebug.Trace("DATETIME", "[DATETIME] ProcessTerminalState {0} @ DS.{1}", passed ? "passed" : "failed", dps); -#endif // _LOGGING - } - [Pure] - [Conditional("_LOGGING")] - internal static void TPTraceExit(string message, DS dps) - { -#if _LOGGING - if (!_tracingEnabled) - return; - BCLDebug.Trace("DATETIME", "[DATETIME] TryParse return {0}, DS.{1}", message, dps); -#endif // _LOGGING - } - [Pure] - [Conditional("_LOGGING")] - internal static void DTFITrace(DateTimeFormatInfo dtfi) - { -#if _LOGGING - if (!_tracingEnabled) - return; - - BCLDebug.Trace("DATETIME", "[DATETIME] DateTimeFormatInfo Properties"); -#if !FEATURE_COREFX_GLOBALIZATION - BCLDebug.Trace("DATETIME", " NativeCalendarName {0}", Hex(dtfi.NativeCalendarName)); -#endif - BCLDebug.Trace("DATETIME", " AMDesignator {0}", Hex(dtfi.AMDesignator)); - BCLDebug.Trace("DATETIME", " PMDesignator {0}", Hex(dtfi.PMDesignator)); - BCLDebug.Trace("DATETIME", " TimeSeparator {0}", Hex(dtfi.TimeSeparator)); - BCLDebug.Trace("DATETIME", " AbbrvDayNames {0}", Hex(dtfi.AbbreviatedDayNames)); - BCLDebug.Trace("DATETIME", " ShortestDayNames {0}", Hex(dtfi.ShortestDayNames)); - BCLDebug.Trace("DATETIME", " DayNames {0}", Hex(dtfi.DayNames)); - BCLDebug.Trace("DATETIME", " AbbrvMonthNames {0}", Hex(dtfi.AbbreviatedMonthNames)); - BCLDebug.Trace("DATETIME", " MonthNames {0}", Hex(dtfi.MonthNames)); - BCLDebug.Trace("DATETIME", " AbbrvMonthGenNames {0}", Hex(dtfi.AbbreviatedMonthGenitiveNames)); - BCLDebug.Trace("DATETIME", " MonthGenNames {0}", Hex(dtfi.MonthGenitiveNames)); -#endif // _LOGGING - } -#if _LOGGING - [Pure] - // return a string in the form: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - internal static string Hex(string[] strs) - { - if (strs == null || strs.Length == 0) - return String.Empty; - if (strs.Length == 1) - return Hex(strs[0]); - - int curLineLength = 0; - int maxLineLength = 55; - int newLinePadding = 20; - - - //invariant: strs.Length >= 2 - StringBuilder buffer = new StringBuilder(); - buffer.Append(Hex(strs[0])); - curLineLength = buffer.Length; - String s; - - for (int i = 1; i < strs.Length - 1; i++) - { - s = Hex(strs[i]); - - if (s.Length > maxLineLength || (curLineLength + s.Length + 2) > maxLineLength) - { - buffer.Append(','); - buffer.Append(Environment.NewLine); - buffer.Append(' ', newLinePadding); - curLineLength = 0; - } - else - { - buffer.Append(", "); - curLineLength += 2; - } - buffer.Append(s); - curLineLength += s.Length; - } - - buffer.Append(','); - s = Hex(strs[strs.Length - 1]); - if (s.Length > maxLineLength || (curLineLength + s.Length + 6) > maxLineLength) - { - buffer.Append(Environment.NewLine); - buffer.Append(' ', newLinePadding); - } - else - { - buffer.Append(' '); - } - buffer.Append(s); - return buffer.ToString(); - } - [Pure] - // return a string in the form: "Sun" - internal static string Hex(string str) - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("\""); - for (int i = 0; i < str.Length; i++) - { - if (str[i] <= '\x007f') - buffer.Append(str[i]); - else - buffer.Append("\\u" + ((int)str[i]).ToString("x4", CultureInfo.InvariantCulture)); - } - buffer.Append("\""); - return buffer.ToString(); - } - [Pure] - // return an unicode escaped string form of char c - internal static String Hex(char c) - { - if (c <= '\x007f') - return c.ToString(CultureInfo.InvariantCulture); - else - return "\\u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture); - } - - internal static bool _tracingEnabled = BCLDebug.CheckEnabled("DATETIME"); -#endif // _LOGGING - } - - - // - // This is a string parsing helper which wraps a String object. - // It has a Index property which tracks - // the current parsing pointer of the string. - // - internal - struct __DTString - { - // - // Value propery: stores the real string to be parsed. - // - internal String Value; - - // - // Index property: points to the character that we are currently parsing. - // - internal int Index; - - // The length of Value string. - internal int len; - - // The current chracter to be looked at. - internal char m_current; - - private CompareInfo m_info; - // Flag to indicate if we encouter an digit, we should check for token or not. - // In some cultures, such as mn-MN, it uses "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440" in month names. - private bool m_checkDigitToken; - - internal __DTString(String str, DateTimeFormatInfo dtfi, bool checkDigitToken) : this(str, dtfi) - { - m_checkDigitToken = checkDigitToken; - } - - internal __DTString(String str, DateTimeFormatInfo dtfi) - { - Index = -1; - Value = str; - len = Value.Length; - - m_current = '\0'; - if (dtfi != null) - { - m_info = dtfi.CompareInfo; - m_checkDigitToken = ((dtfi.FormatFlags & DateTimeFormatFlags.UseDigitPrefixInTokens) != 0); - } - else - { - m_info = Thread.CurrentThread.CurrentCulture.CompareInfo; - m_checkDigitToken = false; - } - } - - internal CompareInfo CompareInfo - { - get { return m_info; } - } - - // - // Advance the Index. - // Return true if Index is NOT at the end of the string. - // - // Typical usage: - // while (str.GetNext()) - // { - // char ch = str.GetChar() - // } - internal bool GetNext() - { - Index++; - if (Index < len) - { - m_current = Value[Index]; - return (true); - } - return (false); - } - - internal bool AtEnd() - { - return Index < len ? false : true; - } - - internal bool Advance(int count) - { - Debug.Assert(Index + count <= len, "__DTString::Advance: Index + count <= len"); - Index += count; - if (Index < len) - { - m_current = Value[Index]; - return (true); - } - return (false); - } - - - // Used by DateTime.Parse() to get the next token. - internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateTimeFormatInfo dtfi) - { - tokenValue = 0; - if (Index >= len) - { - tokenType = TokenType.EndOfString; - return; - } - - tokenType = TokenType.UnknownToken; - - Start: - if (DateTimeParse.IsDigit(m_current)) - { - // This is a digit. - tokenValue = m_current - '0'; - int value; - int start = Index; - - // - // Collect other digits. - // - while (++Index < len) - { - m_current = Value[Index]; - value = m_current - '0'; - if (value >= 0 && value <= 9) - { - tokenValue = tokenValue * 10 + value; - } - else - { - break; - } - } - if (Index - start > DateTimeParse.MaxDateTimeNumberDigits) - { - tokenType = TokenType.NumberToken; - tokenValue = -1; - } - else if (Index - start < 3) - { - tokenType = TokenType.NumberToken; - } - else - { - // If there are more than 3 digits, assume that it's a year value. - tokenType = TokenType.YearNumberToken; - } - if (m_checkDigitToken) - { - int save = Index; - char saveCh = m_current; - // Re-scan using the staring Index to see if this is a token. - Index = start; // To include the first digit. - m_current = Value[Index]; - TokenType tempType; - int tempValue; - // This DTFI has tokens starting with digits. - // E.g. mn-MN has month name like "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440" - if (dtfi.Tokenize(TokenType.RegularTokenMask, out tempType, out tempValue, ref this)) - { - tokenType = tempType; - tokenValue = tempValue; - // This is a token, so the Index has been advanced propertly in DTFI.Tokenizer(). - } - else - { - // Use the number token value. - // Restore the index. - Index = save; - m_current = saveCh; - } - } - } - else if (Char.IsWhiteSpace(m_current)) - { - // Just skip to the next character. - while (++Index < len) - { - m_current = Value[Index]; - if (!(Char.IsWhiteSpace(m_current))) - { - goto Start; - } - } - // We have reached the end of string. - tokenType = TokenType.EndOfString; - } - else - { - dtfi.Tokenize(TokenType.RegularTokenMask, out tokenType, out tokenValue, ref this); - } - } - - internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBeforeSeparator, out char charBeforeSeparator) - { - indexBeforeSeparator = Index; - charBeforeSeparator = m_current; - TokenType tokenType; - if (!SkipWhiteSpaceCurrent()) - { - // Reach the end of the string. - return (TokenType.SEP_End); - } - if (!DateTimeParse.IsDigit(m_current)) - { - // Not a digit. Tokenize it. - int tokenValue; - bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out tokenValue, ref this); - if (!found) - { - tokenType = TokenType.SEP_Space; - } - } - else - { - // Do nothing here. If we see a number, it will not be a separator. There is no need wasting time trying to find the - // separator token. - tokenType = TokenType.SEP_Space; - } - return (tokenType); - } - - internal bool MatchSpecifiedWord(String target) - { - return MatchSpecifiedWord(target, target.Length + Index); - } - - internal bool MatchSpecifiedWord(String target, int endIndex) - { - int count = endIndex - Index; - - if (count != target.Length) - { - return false; - } - - if (Index + count > len) - { - return false; - } - - return (m_info.Compare(Value, Index, count, target, 0, count, CompareOptions.IgnoreCase) == 0); - } - - private static Char[] WhiteSpaceChecks = new Char[] { ' ', '\u00A0' }; - - internal bool MatchSpecifiedWords(String target, bool checkWordBoundary, ref int matchLength) - { - int valueRemaining = Value.Length - Index; - matchLength = target.Length; - - if (matchLength > valueRemaining || m_info.Compare(Value, Index, matchLength, target, 0, matchLength, CompareOptions.IgnoreCase) != 0) - { - // Check word by word - int targetPosition = 0; // Where we are in the target string - int thisPosition = Index; // Where we are in this string - int wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition); - if (wsIndex == -1) - { - return false; - } - do - { - int segmentLength = wsIndex - targetPosition; - if (thisPosition >= Value.Length - segmentLength) - { // Subtraction to prevent overflow. - return false; - } - if (segmentLength == 0) - { - // If segmentLength == 0, it means that we have leading space in the target string. - // In that case, skip the leading spaces in the target and this string. - matchLength--; - } - else - { - // Make sure we also have whitespace in the input string - if (!Char.IsWhiteSpace(Value[thisPosition + segmentLength])) - { - return false; - } - if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) != 0) - { - return false; - } - // Advance the input string - thisPosition = thisPosition + segmentLength + 1; - } - // Advance our target string - targetPosition = wsIndex + 1; - - - // Skip past multiple whitespace - while (thisPosition < Value.Length && Char.IsWhiteSpace(Value[thisPosition])) - { - thisPosition++; - matchLength++; - } - } while ((wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition)) >= 0); - // now check the last segment; - if (targetPosition < target.Length) - { - int segmentLength = target.Length - targetPosition; - if (thisPosition > Value.Length - segmentLength) - { - return false; - } - if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) != 0) - { - return false; - } - } - } - - if (checkWordBoundary) - { - int nextCharIndex = Index + matchLength; - if (nextCharIndex < Value.Length) - { - if (Char.IsLetter(Value[nextCharIndex])) - { - return (false); - } - } - } - return (true); - } - - // - // Check to see if the string starting from Index is a prefix of - // str. - // If a match is found, true value is returned and Index is updated to the next character to be parsed. - // Otherwise, Index is unchanged. - // - internal bool Match(String str) - { - if (++Index >= len) - { - return (false); - } - - if (str.Length > (Value.Length - Index)) - { - return false; - } - - if (m_info.Compare(Value, Index, str.Length, str, 0, str.Length, CompareOptions.Ordinal) == 0) - { - // Update the Index to the end of the matching string. - // So the following GetNext()/Match() opeartion will get - // the next character to be parsed. - Index += (str.Length - 1); - return (true); - } - return (false); - } - - internal bool Match(char ch) - { - if (++Index >= len) - { - return (false); - } - if (Value[Index] == ch) - { - m_current = ch; - return (true); - } - Index--; - return (false); - } - - // - // Actions: From the current position, try matching the longest word in the specified string array. - // E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF", - // MatchLongestWords(words, ref MaxMatchStrLen) will return 1 (the index), and maxMatchLen will be 3. - // Returns: - // The index that contains the longest word to match - // Arguments: - // words The string array that contains words to search. - // maxMatchStrLen [in/out] the initailized maximum length. This parameter can be used to - // find the longest match in two string arrays. - // - internal int MatchLongestWords(String[] words, ref int maxMatchStrLen) - { - int result = -1; - for (int i = 0; i < words.Length; i++) - { - String word = words[i]; - int matchLength = word.Length; - if (MatchSpecifiedWords(word, false, ref matchLength)) - { - if (matchLength > maxMatchStrLen) - { - maxMatchStrLen = matchLength; - result = i; - } - } - } - - return (result); - } - - // - // Get the number of repeat character after the current character. - // For a string "hh:mm:ss" at Index of 3. GetRepeatCount() = 2, and Index - // will point to the second ':'. - // - internal int GetRepeatCount() - { - char repeatChar = Value[Index]; - int pos = Index + 1; - while ((pos < len) && (Value[pos] == repeatChar)) - { - pos++; - } - int repeatCount = (pos - Index); - // Update the Index to the end of the repeated characters. - // So the following GetNext() opeartion will get - // the next character to be parsed. - Index = pos - 1; - return (repeatCount); - } - - // Return false when end of string is encountered or a non-digit character is found. - internal bool GetNextDigit() - { - if (++Index >= len) - { - return (false); - } - return (DateTimeParse.IsDigit(Value[Index])); - } - - // - // Get the current character. - // - internal char GetChar() - { - Debug.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len"); - return (Value[Index]); - } - - // - // Convert the current character to a digit, and return it. - // - internal int GetDigit() - { - Debug.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len"); - Debug.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])"); - return (Value[Index] - '0'); - } - - // - // Eat White Space ahead of the current position - // - // Return false if end of string is encountered. - // - internal void SkipWhiteSpaces() - { - // Look ahead to see if the next character - // is a whitespace. - while (Index + 1 < len) - { - char ch = Value[Index + 1]; - if (!Char.IsWhiteSpace(ch)) - { - return; - } - Index++; - } - return; - } - - // - // Skip white spaces from the current position - // - // Return false if end of string is encountered. - // - internal bool SkipWhiteSpaceCurrent() - { - if (Index >= len) - { - return (false); - } - - if (!Char.IsWhiteSpace(m_current)) - { - return (true); - } - - while (++Index < len) - { - m_current = Value[Index]; - if (!Char.IsWhiteSpace(m_current)) - { - return (true); - } - // Nothing here. - } - return (false); - } - - internal void TrimTail() - { - int i = len - 1; - while (i >= 0 && Char.IsWhiteSpace(Value[i])) - { - i--; - } - Value = Value.Substring(0, i + 1); - len = Value.Length; - } - - // Trim the trailing spaces within a quoted string. - // Call this after TrimTail() is done. - internal void RemoveTrailingInQuoteSpaces() - { - int i = len - 1; - if (i <= 1) - { - return; - } - char ch = Value[i]; - // Check if the last character is a quote. - if (ch == '\'' || ch == '\"') - { - if (Char.IsWhiteSpace(Value[i - 1])) - { - i--; - while (i >= 1 && Char.IsWhiteSpace(Value[i - 1])) - { - i--; - } - Value = Value.Remove(i, Value.Length - 1 - i); - len = Value.Length; - } - } - } - - // Trim the leading spaces within a quoted string. - // Call this after the leading spaces before quoted string are trimmed. - internal void RemoveLeadingInQuoteSpaces() - { - if (len <= 2) - { - return; - } - int i = 0; - char ch = Value[i]; - // Check if the last character is a quote. - if (ch == '\'' || ch == '\"') - { - while ((i + 1) < len && Char.IsWhiteSpace(Value[i + 1])) - { - i++; - } - if (i != 0) - { - Value = Value.Remove(1, i); - len = Value.Length; - } - } - } - - internal DTSubString GetSubString() - { - DTSubString sub = new DTSubString(); - sub.index = Index; - sub.s = Value; - while (Index + sub.length < len) - { - DTSubStringType currentType; - Char ch = Value[Index + sub.length]; - if (ch >= '0' && ch <= '9') - { - currentType = DTSubStringType.Number; - } - else - { - currentType = DTSubStringType.Other; - } - - if (sub.length == 0) - { - sub.type = currentType; - } - else - { - if (sub.type != currentType) - { - break; - } - } - sub.length++; - if (currentType == DTSubStringType.Number) - { - // Incorporate the number into the value - // Limit the digits to prevent overflow - if (sub.length > DateTimeParse.MaxDateTimeNumberDigits) - { - sub.type = DTSubStringType.Invalid; - return sub; - } - int number = ch - '0'; - Debug.Assert(number >= 0 && number <= 9, "number >= 0 && number <= 9"); - sub.value = sub.value * 10 + number; - } - else - { - // For non numbers, just return this length 1 token. This should be expanded - // to more types of thing if this parsing approach is used for things other - // than numbers and single characters - break; - } - } - if (sub.length == 0) - { - sub.type = DTSubStringType.End; - return sub; - } - - return sub; - } - - internal void ConsumeSubString(DTSubString sub) - { - Debug.Assert(sub.index == Index, "sub.index == Index"); - Debug.Assert(sub.index + sub.length <= len, "sub.index + sub.length <= len"); - Index = sub.index + sub.length; - if (Index < len) - { - m_current = Value[Index]; - } - } - } - - internal enum DTSubStringType - { - Unknown = 0, - Invalid = 1, - Number = 2, - End = 3, - Other = 4, - } - - internal struct DTSubString - { - internal String s; - internal Int32 index; - internal Int32 length; - internal DTSubStringType type; - internal Int32 value; - - internal Char this[Int32 relativeIndex] - { - get - { - return s[index + relativeIndex]; - } - } - } - - // - // The buffer to store the parsing token. - // - internal - struct DateTimeToken - { - internal DateTimeParse.DTT dtt; // Store the token - internal TokenType suffix; // Store the CJK Year/Month/Day suffix (if any) - internal int num; // Store the number that we are parsing (if any) - } - - // - // The buffer to store temporary parsing information. - // - internal - unsafe struct DateTimeRawInfo - { - private int* num; - internal int numCount; - internal int month; - internal int year; - internal int dayOfWeek; - internal int era; - internal DateTimeParse.TM timeMark; - internal double fraction; - internal bool hasSameDateAndTimeSeparators; - // - - internal bool timeZone; - - internal void Init(int* numberBuffer) - { - month = -1; - year = -1; - dayOfWeek = -1; - era = -1; - timeMark = DateTimeParse.TM.NotSet; - fraction = -1; - num = numberBuffer; - } - internal unsafe void AddNumber(int value) - { - num[numCount++] = value; - } - internal unsafe int GetNumber(int index) - { - return num[index]; - } - } - - internal enum ParseFailureKind - { - None = 0, - ArgumentNull = 1, - Format = 2, - FormatWithParameter = 3, - FormatBadDateTimeCalendar = 4, // FormatException when ArgumentOutOfRange is thrown by a Calendar.TryToDateTime(). - }; - - [Flags] - internal enum ParseFlags - { - HaveYear = 0x00000001, - HaveMonth = 0x00000002, - HaveDay = 0x00000004, - HaveHour = 0x00000008, - HaveMinute = 0x00000010, - HaveSecond = 0x00000020, - HaveTime = 0x00000040, - HaveDate = 0x00000080, - TimeZoneUsed = 0x00000100, - TimeZoneUtc = 0x00000200, - ParsedMonthName = 0x00000400, - CaptureOffset = 0x00000800, - YearDefault = 0x00001000, - Rfc1123Pattern = 0x00002000, - UtcSortPattern = 0x00004000, - } - - // - // This will store the result of the parsing. And it will be eventually - // used to construct a DateTime instance. - // - internal - struct DateTimeResult - { - internal int Year; - internal int Month; - internal int Day; - // - // Set time defualt to 00:00:00. - // - internal int Hour; - internal int Minute; - internal int Second; - internal double fraction; - - internal int era; - - internal ParseFlags flags; - - internal TimeSpan timeZoneOffset; - - internal Calendar calendar; - - internal DateTime parsedDate; - - internal ParseFailureKind failure; - internal string failureMessageID; - internal object failureMessageFormatArgument; - internal string failureArgumentName; - - internal void Init() - { - Year = -1; - Month = -1; - Day = -1; - fraction = -1; - era = -1; - } - - internal void SetDate(int year, int month, int day) - { - Year = year; - Month = month; - Day = day; - } - internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) - { - this.failure = failure; - this.failureMessageID = failureMessageID; - this.failureMessageFormatArgument = failureMessageFormatArgument; - } - - internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, string failureArgumentName) - { - this.failure = failure; - this.failureMessageID = failureMessageID; - this.failureMessageFormatArgument = failureMessageFormatArgument; - this.failureArgumentName = failureArgumentName; - } - } - - // This is the helper data structure used in ParseExact(). - internal struct ParsingInfo - { - internal Calendar calendar; - internal int dayOfWeek; - internal DateTimeParse.TM timeMark; - - internal bool fUseHour12; - internal bool fUseTwoDigitYear; - internal bool fAllowInnerWhite; - internal bool fAllowTrailingWhite; - internal bool fCustomNumberParser; - internal DateTimeParse.MatchNumberDelegate parseNumberDelegate; - - internal void Init() - { - dayOfWeek = -1; - timeMark = DateTimeParse.TM.NotSet; - } - } - - // - // The type of token that will be returned by DateTimeFormatInfo.Tokenize(). - // - internal enum TokenType - { - // The valid token should start from 1. - - // Regular tokens. The range is from 0x00 ~ 0xff. - NumberToken = 1, // The number. E.g. "12" - YearNumberToken = 2, // The number which is considered as year number, which has 3 or more digits. E.g. "2003" - Am = 3, // AM timemark. E.g. "AM" - Pm = 4, // PM timemark. E.g. "PM" - MonthToken = 5, // A word (or words) that represents a month name. E.g. "March" - EndOfString = 6, // End of string - DayOfWeekToken = 7, // A word (or words) that represents a day of week name. E.g. "Monday" or "Mon" - TimeZoneToken = 8, // A word that represents a timezone name. E.g. "GMT" - EraToken = 9, // A word that represents a era name. E.g. "A.D." - DateWordToken = 10, // A word that can appear in a DateTime string, but serves no parsing semantics. E.g. "de" in Spanish culture. - UnknownToken = 11, // An unknown word, which signals an error in parsing. - HebrewNumber = 12, // A number that is composed of Hebrew text. Hebrew calendar uses Hebrew digits for year values, month values, and day values. - JapaneseEraToken = 13, // Era name for JapaneseCalendar - TEraToken = 14, // Era name for TaiwanCalendar - IgnorableSymbol = 15, // A separator like "," that is equivalent to whitespace - - - // Separator tokens. - SEP_Unk = 0x100, // Unknown separator. - SEP_End = 0x200, // The end of the parsing string. - SEP_Space = 0x300, // Whitespace (including comma). - SEP_Am = 0x400, // AM timemark. E.g. "AM" - SEP_Pm = 0x500, // PM timemark. E.g. "PM" - SEP_Date = 0x600, // date separator. E.g. "/" - SEP_Time = 0x700, // time separator. E.g. ":" - SEP_YearSuff = 0x800, // Chinese/Japanese/Korean year suffix. - SEP_MonthSuff = 0x900, // Chinese/Japanese/Korean month suffix. - SEP_DaySuff = 0xa00, // Chinese/Japanese/Korean day suffix. - SEP_HourSuff = 0xb00, // Chinese/Japanese/Korean hour suffix. - SEP_MinuteSuff = 0xc00, // Chinese/Japanese/Korean minute suffix. - SEP_SecondSuff = 0xd00, // Chinese/Japanese/Korean second suffix. - SEP_LocalTimeMark = 0xe00, // 'T', used in ISO 8601 format. - SEP_DateOrOffset = 0xf00, // '-' which could be a date separator or start of a time zone offset - - RegularTokenMask = 0x00ff, - SeparatorTokenMask = 0xff00, - } -} -- 2.7.4