diff options
author | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
---|---|---|
committer | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
commit | 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch) | |
tree | 98110734c91668dfdbb126fcc0e15ddbd93738ca /src/mscorlib/src/System/Globalization/DateTimeParse.cs | |
parent | fa45f57ed55137c75ac870356a1b8f76c84b229c (diff) | |
download | coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2 coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip |
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/mscorlib/src/System/Globalization/DateTimeParse.cs')
-rw-r--r-- | src/mscorlib/src/System/Globalization/DateTimeParse.cs | 5069 |
1 files changed, 5069 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/DateTimeParse.cs b/src/mscorlib/src/System/Globalization/DateTimeParse.cs new file mode 100644 index 0000000000..8de3242f30 --- /dev/null +++ b/src/mscorlib/src/System/Globalization/DateTimeParse.cs @@ -0,0 +1,5069 @@ +// 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); + +#if !FEATURE_CORECLR + [SecuritySafeCritical] + internal static bool GetAmPmParseFlag() + { + return DateTime.EnableAmPmParseAdjustment(); + } + + internal static bool enableAmPmParseAdjustment = GetAmPmParseFlag(); +#endif + + 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, "s"); + return false; + } + if (format == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "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; + } + + Contract.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, "s"); + return false; + } + if (formats == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "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; + } + + Contract.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; + } + Contract.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99"); + Contract.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. + // + [System.Security.SecuritySafeCritical] // auto-generated + 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 !FEATURE_CORECLR + && enableAmPmParseAdjustment +#endif + ) + { + 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 !FEATURE_CORECLR + // If DateTimeParseIgnorePunctuation is defined, we want to have the V1.1 behavior of just + // ignoring any unrecognized punctuation and moving on to the next character + if (Environment.GetCompatibilityFlag(CompatibilityFlag.DateTimeParseIgnorePunctuation) && ((result.flags & ParseFlags.CaptureOffset) == 0)) { + str.GetNext(); + LexTraceExit("0210 (success)", dps); + return true; + } +#endif // FEATURE_CORECLR + + 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) + { + Contract.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; + } + Contract.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. + // + [System.Security.SecuritySafeCritical] // auto-generated + internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) { + if (s == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s"); + return false; + } + if (s.Length == 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); + return false; + } + + Contract.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); + } +#if FEATURE_CORECLR // on CoreCLR DateTime is also restricted to +- 14:00, just like DateTimeOffset + 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; + } + } +#endif // FEATURE_CORECLR + + // 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 + Contract.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) { + Contract.Assert(minDigitLen > 0, "minDigitLen > 0"); + Contract.Assert(maxDigitLen < 9, "maxDigitLen < 9"); + Contract.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, Environment.GetResourceString(result.failureMessageID)); + case ParseFailureKind.Format: + return new FormatException(Environment.GetResourceString(result.failureMessageID)); + case ParseFailureKind.FormatWithParameter: + return new FormatException(Environment.GetResourceString(result.failureMessageID, result.failureMessageFormatArgument)); + case ParseFailureKind.FormatBadDateTimeCalendar: + return new FormatException(Environment.GetResourceString(result.failureMessageID, result.calendar)); + default: + Contract.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) { + Contract.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. + [System.Security.SecurityCritical] // auto-generated + 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); + } + } + + [System.Security.SecurityCritical] // auto-generated + 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() { + Contract.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() { + Contract.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len"); + Contract.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'; + Contract.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) { + Contract.Assert(sub.index == Index, "sub.index == Index"); + Contract.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 { + [SecurityCritical] + 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; + + [System.Security.SecurityCritical] // auto-generated + internal void Init(int * numberBuffer) { + month = -1; + year = -1; + dayOfWeek = -1; + era = -1; + timeMark = DateTimeParse.TM.NotSet; + fraction = -1; + num = numberBuffer; + } + [System.Security.SecuritySafeCritical] // auto-generated + internal unsafe void AddNumber(int value) { + num[numCount++] = value; + } + [System.Security.SecuritySafeCritical] // auto-generated + 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, + } +} |