private static bool Lex(DateTimeParse.DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi) { dtok.dtt = DateTimeParse.DTT.Unk; TokenType tokenType; int num; str.GetRegularToken(out tokenType, out num, dtfi); switch (tokenType) { case TokenType.NumberToken: case TokenType.YearNumberToken: { if (raw.numCount == 3 || num == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (dps == DateTimeParse.DS.T_NNt && str.Index < str.len - 1) { char c = str.Value[str.Index]; if (c == '.') { DateTimeParse.ParseFraction(ref str, out raw.fraction); } } if ((dps == DateTimeParse.DS.T_NNt || dps == DateTimeParse.DS.T_Nt) && str.Index < str.len - 1 && !DateTimeParse.HandleTimeZone(ref str, ref result)) { return false; } dtok.num = num; if (tokenType != TokenType.YearNumberToken) { int index; char current; TokenType separatorToken; TokenType tokenType2 = separatorToken = str.GetSeparatorToken(dtfi, out index, out current); if (separatorToken > TokenType.SEP_YearSuff) { if (separatorToken <= TokenType.SEP_HourSuff) { if (separatorToken == TokenType.SEP_MonthSuff || separatorToken == TokenType.SEP_DaySuff) { dtok.dtt = DateTimeParse.DTT.NumDatesuff; dtok.suffix = tokenType2; break; } if (separatorToken != TokenType.SEP_HourSuff) { goto IL_52A; } } else { if (separatorToken <= TokenType.SEP_SecondSuff) { if (separatorToken != TokenType.SEP_MinuteSuff && separatorToken != TokenType.SEP_SecondSuff) { goto IL_52A; } } else { if (separatorToken == TokenType.SEP_LocalTimeMark) { dtok.dtt = DateTimeParse.DTT.NumLocalTimeMark; raw.AddNumber(dtok.num); break; } if (separatorToken != TokenType.SEP_DateOrOffset) { goto IL_52A; } if (DateTimeParse.dateParsingStates[(int)dps][4] == DateTimeParse.DS.ERROR && DateTimeParse.dateParsingStates[(int)dps][3] > DateTimeParse.DS.ERROR) { str.Index = index; str.m_current = current; dtok.dtt = DateTimeParse.DTT.NumSpace; } else { dtok.dtt = DateTimeParse.DTT.NumDatesep; } raw.AddNumber(dtok.num); break; } } dtok.dtt = DateTimeParse.DTT.NumTimesuff; dtok.suffix = tokenType2; break; } if (separatorToken <= TokenType.SEP_Am) { if (separatorToken == TokenType.SEP_End) { dtok.dtt = DateTimeParse.DTT.NumEnd; raw.AddNumber(dtok.num); break; } if (separatorToken == TokenType.SEP_Space) { dtok.dtt = DateTimeParse.DTT.NumSpace; raw.AddNumber(dtok.num); break; } if (separatorToken != TokenType.SEP_Am) { goto IL_52A; } } else { if (separatorToken <= TokenType.SEP_Date) { if (separatorToken != TokenType.SEP_Pm) { if (separatorToken != TokenType.SEP_Date) { goto IL_52A; } dtok.dtt = DateTimeParse.DTT.NumDatesep; raw.AddNumber(dtok.num); break; } } else { if (separatorToken == TokenType.SEP_Time) { dtok.dtt = DateTimeParse.DTT.NumTimesep; raw.AddNumber(dtok.num); break; } if (separatorToken != TokenType.SEP_YearSuff) { goto IL_52A; } dtok.num = dtfi.Calendar.ToFourDigitYear(num); dtok.dtt = DateTimeParse.DTT.NumDatesuff; dtok.suffix = tokenType2; break; } } if (raw.timeMark != DateTimeParse.TM.NotSet) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); break; } raw.timeMark = ((tokenType2 == TokenType.SEP_Am) ? DateTimeParse.TM.AM : DateTimeParse.TM.PM); dtok.dtt = DateTimeParse.DTT.NumAmpm; raw.AddNumber(dtok.num); if ((dps == DateTimeParse.DS.T_NNt || dps == DateTimeParse.DS.T_Nt) && !DateTimeParse.HandleTimeZone(ref str, ref result)) { return false; } break; IL_52A: result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (raw.year == -1) { raw.year = num; int index; char current; TokenType separatorToken2; TokenType tokenType2 = separatorToken2 = str.GetSeparatorToken(dtfi, out index, out current); if (separatorToken2 > TokenType.SEP_YearSuff) { if (separatorToken2 <= TokenType.SEP_HourSuff) { if (separatorToken2 == TokenType.SEP_MonthSuff || separatorToken2 == TokenType.SEP_DaySuff) { goto IL_26A; } if (separatorToken2 != TokenType.SEP_HourSuff) { goto IL_28E; } } else { if (separatorToken2 != TokenType.SEP_MinuteSuff && separatorToken2 != TokenType.SEP_SecondSuff) { if (separatorToken2 != TokenType.SEP_DateOrOffset) { goto IL_28E; } if (DateTimeParse.dateParsingStates[(int)dps][13] == DateTimeParse.DS.ERROR && DateTimeParse.dateParsingStates[(int)dps][12] > DateTimeParse.DS.ERROR) { str.Index = index; str.m_current = current; dtok.dtt = DateTimeParse.DTT.YearSpace; return true; } dtok.dtt = DateTimeParse.DTT.YearDateSep; return true; } } dtok.dtt = DateTimeParse.DTT.NumTimesuff; dtok.suffix = tokenType2; return true; } if (separatorToken2 <= TokenType.SEP_Am) { if (separatorToken2 == TokenType.SEP_End) { dtok.dtt = DateTimeParse.DTT.YearEnd; return true; } if (separatorToken2 == TokenType.SEP_Space) { dtok.dtt = DateTimeParse.DTT.YearSpace; return true; } if (separatorToken2 != TokenType.SEP_Am) { goto IL_28E; } } else { if (separatorToken2 != TokenType.SEP_Pm) { if (separatorToken2 == TokenType.SEP_Date) { dtok.dtt = DateTimeParse.DTT.YearDateSep; return true; } if (separatorToken2 != TokenType.SEP_YearSuff) { goto IL_28E; } goto IL_26A; } } if (raw.timeMark == DateTimeParse.TM.NotSet) { raw.timeMark = ((tokenType2 == TokenType.SEP_Am) ? DateTimeParse.TM.AM : DateTimeParse.TM.PM); dtok.dtt = DateTimeParse.DTT.YearSpace; return true; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return true; IL_26A: dtok.dtt = DateTimeParse.DTT.NumDatesuff; dtok.suffix = tokenType2; return true; IL_28E: result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } case TokenType.Am: case TokenType.Pm: { if (raw.timeMark != DateTimeParse.TM.NotSet) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.timeMark = (DateTimeParse.TM)num; break; } case TokenType.MonthToken: { if (raw.month != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } int index; char current; TokenType separatorToken3 = str.GetSeparatorToken(dtfi, out index, out current); if (separatorToken3 <= TokenType.SEP_Space) { if (separatorToken3 == TokenType.SEP_End) { dtok.dtt = DateTimeParse.DTT.MonthEnd; goto IL_786; } if (separatorToken3 == TokenType.SEP_Space) { dtok.dtt = DateTimeParse.DTT.MonthSpace; goto IL_786; } } else { if (separatorToken3 == TokenType.SEP_Date) { dtok.dtt = DateTimeParse.DTT.MonthDatesep; goto IL_786; } if (separatorToken3 == TokenType.SEP_DateOrOffset) { if (DateTimeParse.dateParsingStates[(int)dps][8] == DateTimeParse.DS.ERROR && DateTimeParse.dateParsingStates[(int)dps][7] > DateTimeParse.DS.ERROR) { str.Index = index; str.m_current = current; dtok.dtt = DateTimeParse.DTT.MonthSpace; goto IL_786; } dtok.dtt = DateTimeParse.DTT.MonthDatesep; goto IL_786; } } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; IL_786: raw.month = num; break; } case TokenType.EndOfString: { dtok.dtt = DateTimeParse.DTT.End; break; } case TokenType.DayOfWeekToken: { if (raw.dayOfWeek != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.dayOfWeek = num; dtok.dtt = DateTimeParse.DTT.DayOfWeek; break; } case TokenType.TimeZoneToken: { if ((result.flags & ParseFlags.TimeZoneUsed) != (ParseFlags)0) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } dtok.dtt = DateTimeParse.DTT.TimeZone; result.flags |= ParseFlags.TimeZoneUsed; result.timeZoneOffset = new TimeSpan(0L); result.flags |= ParseFlags.TimeZoneUtc; break; } case TokenType.EraToken: { if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DateTimeParse.DTT.Era; break; } case TokenType.UnknownToken: { if (char.IsLetter(str.m_current)) { result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index); return false; } if (Environment.GetCompatibilityFlag(CompatibilityFlag.DateTimeParseIgnorePunctuation) && (result.flags & ParseFlags.CaptureOffset) == (ParseFlags)0) { str.GetNext(); return true; } if ((str.m_current == '-' || str.m_current == '+') && (result.flags & ParseFlags.TimeZoneUsed) == (ParseFlags)0) { int index2 = str.Index; if (DateTimeParse.ParseTimeZone(ref str, ref result.timeZoneOffset)) { result.flags |= ParseFlags.TimeZoneUsed; return true; } str.Index = index2; } if (DateTimeParse.VerifyValidPunctuation(ref str)) { return true; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } case TokenType.HebrewNumber: { int index; char current; if (num < 100) { dtok.num = num; raw.AddNumber(dtok.num); TokenType separatorToken4 = str.GetSeparatorToken(dtfi, out index, out current); if (separatorToken4 <= TokenType.SEP_Space) { if (separatorToken4 == TokenType.SEP_End) { dtok.dtt = DateTimeParse.DTT.NumEnd; break; } if (separatorToken4 != TokenType.SEP_Space) { goto IL_695; } } else { if (separatorToken4 != TokenType.SEP_Date) { if (separatorToken4 != TokenType.SEP_DateOrOffset) { goto IL_695; } if (DateTimeParse.dateParsingStates[(int)dps][4] == DateTimeParse.DS.ERROR && DateTimeParse.dateParsingStates[(int)dps][3] > DateTimeParse.DS.ERROR) { str.Index = index; str.m_current = current; dtok.dtt = DateTimeParse.DTT.NumSpace; break; } dtok.dtt = DateTimeParse.DTT.NumDatesep; break; } } dtok.dtt = DateTimeParse.DTT.NumDatesep; break; IL_695: result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (raw.year != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.year = num; TokenType separatorToken5 = str.GetSeparatorToken(dtfi, out index, out current); if (separatorToken5 != TokenType.SEP_End) { if (separatorToken5 != TokenType.SEP_Space) { if (separatorToken5 == TokenType.SEP_DateOrOffset) { if (DateTimeParse.dateParsingStates[(int)dps][12] > DateTimeParse.DS.ERROR) { str.Index = index; str.m_current = current; dtok.dtt = DateTimeParse.DTT.YearSpace; break; } } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } dtok.dtt = DateTimeParse.DTT.YearSpace; } else { dtok.dtt = DateTimeParse.DTT.YearEnd; } break; } case TokenType.JapaneseEraToken: { result.calendar = JapaneseCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI(); if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DateTimeParse.DTT.Era; break; } case TokenType.TEraToken: { result.calendar = TaiwanCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI(); if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DateTimeParse.DTT.Era; break; } } return true; }
[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 bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok) { TokenType suffix = dtok.suffix; if (suffix <= TokenType.SEP_DaySuff) { if (suffix != TokenType.SEP_YearSuff) { if (suffix != TokenType.SEP_MonthSuff) { if (suffix == TokenType.SEP_DaySuff) { if ((result.flags & ParseFlags.HaveDay) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveDay; result.Day = dtok.num; } } else { if ((result.flags & ParseFlags.HaveMonth) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveMonth; result.Month = (raw.month = dtok.num); } } else { if ((result.flags & ParseFlags.HaveYear) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveYear; result.Year = (raw.year = dtok.num); } } else { if (suffix != TokenType.SEP_HourSuff) { if (suffix != TokenType.SEP_MinuteSuff) { if (suffix == TokenType.SEP_SecondSuff) { if ((result.flags & ParseFlags.HaveSecond) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveSecond; result.Second = dtok.num; } } else { if ((result.flags & ParseFlags.HaveMinute) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveMinute; result.Minute = dtok.num; } } else { if ((result.flags & ParseFlags.HaveHour) != (ParseFlags)0) { return false; } result.flags |= ParseFlags.HaveHour; result.Hour = dtok.num; } } return true; }
// // 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; }
[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; }
// // This is the lexer. Check the character at the current index, and put the found token in dtok and // some raw date/time information in raw. // private static Boolean Lex( DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles) { TokenType tokenType; int tokenValue; int indexBeforeSeparator; char charBeforeSeparator; TokenType sep; dtok.dtt = DTT.Unk; // Assume the token is unkown. str.GetRegularToken(out tokenType, out tokenValue, dtfi); // Look at the regular token. switch (tokenType) { case TokenType.NumberToken: case TokenType.YearNumberToken: if (raw.numCount == 3 || tokenValue == -1) { result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); 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)) { 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, SR.Format_BadDateTime, null); } 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, SR.Format_BadDateTime, null); 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(dps, DTT.YearDateSep) == DS.ERROR) && (DateParsingStates(dps, 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, SR.Format_BadDateTime, null); return false; } return true; } result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); return false; } switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) { // // Note here we check if the numCount is less than three. // When we have more than three numbers, it will be caught as error in the state machine. // case TokenType.SEP_End: dtok.dtt = DTT.NumEnd; raw.AddNumber(dtok.num); break; case TokenType.SEP_Am: case TokenType.SEP_Pm: if (raw.timeMark == TM.NotSet) { raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM); dtok.dtt = DTT.NumAmpm; // Fix AM/PM parsing case, e.g. "1/10 5 AM" if (dps == DS.D_NN) { if (!ProcessTerminaltState(DS.DX_NN, ref result, ref styles, ref raw, dtfi)) { return false; } } raw.AddNumber(dtok.num); } else { result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); break; } if (dps == DS.T_NNt || dps == DS.T_Nt) { if (false == HandleTimeZone(ref str, ref result)) { 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(dps, DTT.NumDatesep) == DS.ERROR) && (DateParsingStates(dps, 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: dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue); 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, SR.Format_BadDateTime, null); 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(dps, 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, SR.Format_BadDateTime, null); return false; } } else { // Invalid separator after number number. result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); 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(dps, DTT.NumDatesep) == DS.ERROR) && (DateParsingStates(dps, 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, SR.Format_BadDateTime, null); 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, SR.Format_BadDateTime, null); 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, SR.Format_BadDateTime, null); 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(dps, DTT.MonthDatesep) == DS.ERROR) && (DateParsingStates(dps, 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, SR.Format_BadDateTime, null); return false; } raw.month = tokenValue; } else { result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); return false; } break; case TokenType.EraToken: if (result.era != -1) { result.era = tokenValue; dtok.dtt = DTT.Era; } else { result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); return false; } break; case TokenType.JapaneseEraToken: throw new NotSupportedException(); case TokenType.TEraToken: throw new NotSupportedException(); 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, SR.Format_BadDateTime, null); 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, SR.Format_BadDateTime, null); return false; } break; case TokenType.UnknownToken: if (Char.IsLetter(str.m_current)) { result.SetFailure(ParseFailureKind.FormatWithParameter, SR.Format_UnknowDateTimeWord, str.Index); return (false); } if ((str.m_current == '-' || str.m_current == '+') && ((result.flags & ParseFlags.TimeZoneUsed) == 0)) { Int32 originalIndex = str.Index; if (ParseTimeZone(ref str, ref result.timeZoneOffset)) { result.flags |= ParseFlags.TimeZoneUsed; 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)) { return true; } result.SetFailure(ParseFailureKind.Format, SR.Format_BadDateTime, null); return false; } return true; }
[System.Security.SecuritySafeCritical] // auto-generated private static Boolean Lex( #if !FEATURE_CORECLR DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles)
private static bool Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi) { TokenType type; int num; int num2; char ch; TokenType type2; dtok.dtt = DTT.Unk; str.GetRegularToken(out type, out num, dtfi); switch (type) { case TokenType.NumberToken: case TokenType.YearNumberToken: if ((raw.numCount != 3) && (num != -1)) { if ((dps == DS.T_NNt) && (str.Index < (str.len - 1))) { char ch2 = str.Value[str.Index]; if (ch2 == '.') { ParseFraction(ref str, out raw.fraction); } } if (((dps == DS.T_NNt) || (dps == DS.T_Nt)) && (str.Index < (str.len - 1))) { char c = str.Value[str.Index]; int num3 = 0; while (char.IsWhiteSpace(c) && ((str.Index + num3) < (str.len - 1))) { num3++; c = str.Value[str.Index + num3]; } switch (c) { case '+': case '-': str.Index += num3; if ((result.flags & ParseFlags.TimeZoneUsed) != 0) { 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; } break; } } dtok.num = num; if (type != TokenType.YearNumberToken) { switch ((type2 = str.GetSeparatorToken(dtfi, out num2, out ch))) { case TokenType.SEP_End: dtok.dtt = DTT.NumEnd; raw.AddNumber(dtok.num); goto Label_0A1E; case TokenType.SEP_Space: dtok.dtt = DTT.NumSpace; raw.AddNumber(dtok.num); goto Label_0A1E; case TokenType.SEP_Am: case TokenType.SEP_Pm: if (raw.timeMark == TM.NotSet) { raw.timeMark = (type2 == TokenType.SEP_Am) ? TM.AM : TM.PM; dtok.dtt = DTT.NumAmpm; raw.AddNumber(dtok.num); } else { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); } goto Label_0A1E; case TokenType.SEP_Time: dtok.dtt = DTT.NumTimesep; raw.AddNumber(dtok.num); goto Label_0A1E; case TokenType.SEP_YearSuff: dtok.num = dtfi.Calendar.ToFourDigitYear(num); dtok.dtt = DTT.NumDatesuff; dtok.suffix = type2; goto Label_0A1E; case TokenType.SEP_Date: dtok.dtt = DTT.NumDatesep; raw.AddNumber(dtok.num); goto Label_0A1E; case TokenType.SEP_MonthSuff: case TokenType.SEP_DaySuff: dtok.dtt = DTT.NumDatesuff; dtok.suffix = type2; goto Label_0A1E; case TokenType.SEP_HourSuff: case TokenType.SEP_MinuteSuff: case TokenType.SEP_SecondSuff: dtok.dtt = DTT.NumTimesuff; dtok.suffix = type2; goto Label_0A1E; case TokenType.SEP_LocalTimeMark: dtok.dtt = DTT.NumLocalTimeMark; raw.AddNumber(dtok.num); goto Label_0A1E; case TokenType.SEP_DateOrOffset: if ((dateParsingStates[(int) dps][4] == DS.ERROR) && (dateParsingStates[(int) dps][3] > DS.ERROR)) { str.Index = num2; str.m_current = ch; dtok.dtt = DTT.NumSpace; } else { dtok.dtt = DTT.NumDatesep; } raw.AddNumber(dtok.num); goto Label_0A1E; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (raw.year != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.year = num; switch ((type2 = str.GetSeparatorToken(dtfi, out num2, out ch))) { case TokenType.SEP_Pm: case TokenType.SEP_Am: if (raw.timeMark == TM.NotSet) { raw.timeMark = (type2 == TokenType.SEP_Am) ? TM.AM : TM.PM; dtok.dtt = DTT.YearSpace; } else { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); } goto Label_0354; case TokenType.SEP_Date: dtok.dtt = DTT.YearDateSep; goto Label_0354; case TokenType.SEP_YearSuff: case TokenType.SEP_MonthSuff: case TokenType.SEP_DaySuff: dtok.dtt = DTT.NumDatesuff; dtok.suffix = type2; goto Label_0354; case TokenType.SEP_End: dtok.dtt = DTT.YearEnd; goto Label_0354; case TokenType.SEP_Space: dtok.dtt = DTT.YearSpace; goto Label_0354; case TokenType.SEP_HourSuff: case TokenType.SEP_MinuteSuff: case TokenType.SEP_SecondSuff: dtok.dtt = DTT.NumTimesuff; dtok.suffix = type2; goto Label_0354; case TokenType.SEP_DateOrOffset: if ((dateParsingStates[(int) dps][13] == DS.ERROR) && (dateParsingStates[(int) dps][12] > DS.ERROR)) { str.Index = num2; str.m_current = ch; dtok.dtt = DTT.YearSpace; } else { dtok.dtt = DTT.YearDateSep; } goto Label_0354; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; case TokenType.Am: case TokenType.Pm: if (raw.timeMark != TM.NotSet) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.timeMark = (TM) num; break; case TokenType.MonthToken: { if (raw.month != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } TokenType type8 = type2 = str.GetSeparatorToken(dtfi, out num2, out ch); if (type8 > TokenType.SEP_Space) { switch (type8) { case TokenType.SEP_Date: dtok.dtt = DTT.MonthDatesep; goto Label_0823; case TokenType.SEP_DateOrOffset: if ((dateParsingStates[(int) dps][8] == DS.ERROR) && (dateParsingStates[(int) dps][7] > DS.ERROR)) { str.Index = num2; str.m_current = ch; dtok.dtt = DTT.MonthSpace; } else { dtok.dtt = DTT.MonthDatesep; } goto Label_0823; } } else { switch (type8) { case TokenType.SEP_End: dtok.dtt = DTT.MonthEnd; goto Label_0823; case TokenType.SEP_Space: dtok.dtt = DTT.MonthSpace; goto Label_0823; } } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } case TokenType.EndOfString: dtok.dtt = DTT.End; break; case TokenType.DayOfWeekToken: if (raw.dayOfWeek != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.dayOfWeek = num; dtok.dtt = DTT.DayOfWeek; break; case TokenType.TimeZoneToken: dtok.dtt = DTT.TimeZone; result.flags |= ParseFlags.TimeZoneUsed; result.timeZoneOffset = new TimeSpan(0L); result.flags |= ParseFlags.TimeZoneUtc; break; case TokenType.EraToken: if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DTT.Era; break; case TokenType.UnknownToken: if (!char.IsLetter(str.m_current)) { if (Environment.GetCompatibilityFlag(CompatibilityFlag.DateTimeParseIgnorePunctuation) && ((result.flags & ParseFlags.CaptureOffset) == 0)) { str.GetNext(); return true; } if (((str.m_current == '-') || (str.m_current == '+')) && ((result.flags & ParseFlags.TimeZoneUsed) == 0)) { int index = str.Index; if (ParseTimeZone(ref str, ref result.timeZoneOffset)) { result.flags |= ParseFlags.TimeZoneUsed; return true; } str.Index = index; } if (VerifyValidPunctuation(ref str)) { return true; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index); return false; case TokenType.HebrewNumber: if (num < 100) { dtok.num = num; raw.AddNumber(dtok.num); switch ((type2 = str.GetSeparatorToken(dtfi, out num2, out ch))) { case TokenType.SEP_Date: case TokenType.SEP_Space: dtok.dtt = DTT.NumDatesep; goto Label_0A1E; case TokenType.SEP_DateOrOffset: if ((dateParsingStates[(int) dps][4] == DS.ERROR) && (dateParsingStates[(int) dps][3] > DS.ERROR)) { str.Index = num2; str.m_current = ch; dtok.dtt = DTT.NumSpace; } else { dtok.dtt = DTT.NumDatesep; } goto Label_0A1E; case TokenType.SEP_End: dtok.dtt = DTT.NumEnd; goto Label_0A1E; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (raw.year != -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.year = num; switch ((type2 = str.GetSeparatorToken(dtfi, out num2, out ch))) { case TokenType.SEP_End: dtok.dtt = DTT.YearEnd; goto Label_0A1E; case TokenType.SEP_Space: dtok.dtt = DTT.YearSpace; goto Label_0A1E; case TokenType.SEP_DateOrOffset: if (dateParsingStates[(int) dps][12] > DS.ERROR) { str.Index = num2; str.m_current = ch; dtok.dtt = DTT.YearSpace; goto Label_0A1E; } break; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; case TokenType.JapaneseEraToken: result.calendar = JapaneseCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI(); if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DTT.Era; break; case TokenType.TEraToken: result.calendar = TaiwanCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI(); if (result.era == -1) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } result.era = num; dtok.dtt = DTT.Era; break; } goto Label_0A1E; Label_0354: return true; Label_0823: raw.month = num; Label_0A1E: return true; }
internal static unsafe bool TryParse(string s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) { DateTime time; 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; } DS bEGIN = DS.BEGIN; bool flag = false; DateTimeToken dtok = new DateTimeToken { suffix = TokenType.SEP_Unk }; DateTimeRawInfo raw = new DateTimeRawInfo(); int* numberBuffer = stackalloc int[3]; raw.Init(numberBuffer); result.calendar = dtfi.Calendar; result.era = 0; __DTString str = new __DTString(s, dtfi); str.GetNext(); do { if (!Lex(bEGIN, ref str, ref dtok, ref raw, ref result, ref dtfi)) { return false; } if (dtok.dtt != DTT.Unk) { if (dtok.suffix != TokenType.SEP_Unk) { if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok)) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } dtok.suffix = TokenType.SEP_Unk; } if (dtok.dtt == DTT.NumLocalTimeMark) { switch (bEGIN) { case DS.D_YNd: case DS.D_YN: return ParseISO8601(ref raw, ref str, styles, ref result); } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } bEGIN = dateParsingStates[(int) bEGIN][(int) dtok.dtt]; if (bEGIN == DS.ERROR) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } if (bEGIN > DS.ERROR) { if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != DateTimeFormatFlags.None) { if (!ProcessHebrewTerminalState(bEGIN, ref result, ref styles, ref raw, dtfi)) { return false; } } else if (!ProcessTerminaltState(bEGIN, ref result, ref styles, ref raw, dtfi)) { return false; } flag = true; bEGIN = DS.BEGIN; } } } while (((dtok.dtt != DTT.End) && (dtok.dtt != DTT.NumEnd)) && (dtok.dtt != DTT.MonthEnd)); if (!flag) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } AdjustTimeMark(dtfi, ref raw); if (!AdjustHour(ref result.Hour, raw.timeMark)) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } bool bTimeOnly = ((result.Year == -1) && (result.Month == -1)) && (result.Day == -1); if (!CheckDefaultDateTime(ref result, ref result.calendar, styles)) { 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); return false; } if (raw.fraction > 0.0) { time = time.AddTicks((long) Math.Round((double) (raw.fraction * 10000000.0))); } if ((raw.dayOfWeek != -1) && (raw.dayOfWeek != result.calendar.GetDayOfWeek(time))) { result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null); return false; } result.parsedDate = time; if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) { return false; } return true; }
// // This is the lexer. Check the character at the current index, and put the found token in dtok and // some raw date/time information in raw. // private static Boolean Lex( DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi) { TokenType tokenType; int tokenValue; TokenType sep; dtok.dtt = DTT.Unk; // Assume the token is unkown. str.GetRegularToken(out tokenType, out tokenValue, dtfi); // 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); 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 swallows 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)) { 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; } } } } 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)) { 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); } break; case TokenType.SEP_Space: dtok.dtt = DTT.YearSpace; break; case TokenType.SEP_Date: 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); return false; } // // Found the token already. Return now. // return true; } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } switch (sep = str.GetSeparatorToken(dtfi)) { // // 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; raw.AddNumber(dtok.num); } else { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); } 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_Time: if ((result.flags & ParseFlags.TimeZoneUsed) == 0) { dtok.dtt = DTT.NumTimesep; raw.AddNumber(dtok.num); } else { // If we already got timezone, there should be no // time separator again. result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } break; case TokenType.SEP_YearSuff: dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue); 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); 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)) { case TokenType.SEP_End: dtok.dtt = DTT.YearEnd; break; case TokenType.SEP_Space: dtok.dtt = DTT.YearSpace; break; default: // Invalid separator after number number. result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } } else { // Invalid separator after number number. result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } } else { // This is a day number dtok.num = tokenValue; raw.AddNumber(dtok.num); switch (sep = str.GetSeparatorToken(dtfi)) { // // 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; default: // Invalid separator after number number. result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); 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); return false; } break; case TokenType.MonthToken: if (raw.month == -1) { // // This is a month name // switch(sep=str.GetSeparatorToken(dtfi)) { 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; default: //Invalid separator after month name result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } raw.month = tokenValue; } else { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); 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); 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); return false; } break; case TokenType.TEraToken: // Special case for Taiwan. 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); return false; } break; case TokenType.TimeZoneToken: // 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); return false; } break; case TokenType.UnknownToken: if (Char.IsLetter(str.m_current)) { result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index); return (false); } else { // 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)) { str.GetNext(); return true; } else { if (VerifyValidPunctuation(ref str)) { return true; } } result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } } return true; }
// // This is the real method to do the parsing work. // internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) { if (s == null) { result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s"); return false; } if (s.Length == 0) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } BCLDebug.Assert(dtfi != null, "dtfi == null"); 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); } 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)) { 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); 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) { return (ParseISO8601(ref raw, ref str, styles, ref result)); } else { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return false; } } // // 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); return false; } else if (dps > DS.ERROR) { if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) { if (!ProcessHebrewTerminalState(dps, ref result, ref raw, dtfi)) { return false; } } else { if (!ProcessTerminaltState(dps, ref result, ref raw, dtfi)) { 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); return false; } AdjustTimeMark(dtfi, ref raw); if (!AdjustHour(ref result.Hour, raw.timeMark)) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); 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. // CheckDefaultDateTime(ref result, ref result.calendar, styles); 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); 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); return false; } } result.parsedDate = time; if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) { return false; } return true; }
// // This is the lexer. Check the character at the current index, and put the found token in dtok and // some raw date/time information in raw. // private static void Lex( int dps, __DTString str, DateTimeToken dtok, DateTimeRawInfo raw, DateTimeResult result, ref DateTimeFormatInfo dtfi) { int sep; dtok.dtt = DTT_Unk; // Assume the token is unkown. // // Skip any white spaces. // if (!str.SkipWhiteSpaceComma()) { // // SkipWhiteSpaceComma() will return true when end of string is reached. // dtok.dtt = DTT_End; return; } char ch = str.GetChar(); if (Char.IsLetter(ch)) { // // This is a letter. // int month, dayOfWeek, era, timeMark; // // Check if this is a beginning of a month name. // And check if this is a day of week name. // if (raw.month == -1 && (month = GetMonthNumber(str, dtfi)) >= 1) { // // This is a month name // switch(sep=GetSeparator(str, raw, dtfi)) { case SEP_End: dtok.dtt = DTT_MonthEnd; break; case SEP_Space: dtok.dtt = DTT_MonthSpace; break; case SEP_Date: dtok.dtt = DTT_MonthDatesep; break; default: //Invalid separator after month name throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } raw.month = month; } else if (raw.dayOfWeek == -1 && (dayOfWeek = GetDayOfWeekNumber(str, dtfi)) >= 0) { // // This is a day of week name. // raw.dayOfWeek = dayOfWeek; dtok.dtt = DTT_DayOfWeek; // // Discard the separator. // GetSeparator(str, raw, dtfi); } else if (GetTimeZoneName(str)) { // // This is a timezone designator // // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time). // dtok.dtt = DTT_TimeZone; result.timeZoneUsed = true; result.timeZoneOffset = new TimeSpan(0); } else if ((raw.era == -1) && ((era = GetEra(str, result, ref dtfi)) != -1)) { raw.era = era; dtok.dtt = DTT_Era; } else if (raw.timeMark == -1 && (timeMark = GetTimeMark(str, dtfi)) != -1) { raw.timeMark = timeMark; GetSeparator(str, raw, dtfi); } else { // // Not a month name, not a day of week name. Check if this is one of the // known date words. This is used to deal case like Spanish cultures, which // uses 'de' in their Date string. // // if (!str.MatchWords(dtfi.DateWords)) { throw new FormatException( String.Format(Environment.GetResourceString("Format_UnknowDateTimeWord"), str.Index)); } GetSeparator(str, raw, dtfi); } } else if (Char.IsDigit(ch)) { if (raw.numCount == 3) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // This is a digit. // int number = ch - '0'; int digitCount = 1; // // Collect other digits. // while (str.GetNextDigit()) { number = number * 10 + str.GetDigit(); digitCount++; } // 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:02). // // 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 || dps == DS_T_Nt) { char nextCh; if ((str.Index < str.len - 1)) { nextCh = str.Value[str.Index]; switch (nextCh) { case '.': if (dps == DS_T_NNt) { // Yes, advance to the next character. str.Index++; // Collect the second fraction. raw.fraction = ParseFraction(str); } break; case '+': case '-': if (result.timeZoneUsed) { // Should not have two timezone offsets. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } result.timeZoneUsed = true; result.timeZoneOffset = ParseTimeZone(str, nextCh); break; } } } if (number >= 0) { dtok.num = number; if (digitCount >= 3) { if (raw.year == -1) { raw.year = number; // // 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 = GetSeparator(str, raw, dtfi)) { case SEP_End: dtok.dtt = DTT_YearEnd; break; case SEP_Am: case SEP_Pm: case SEP_Space: dtok.dtt = DTT_YearSpace; break; case SEP_Date: dtok.dtt = DTT_YearDateSep; break; case SEP_YearSuff: case SEP_MonthSuff: case SEP_DaySuff: dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_HourSuff: case SEP_MinuteSuff: case SEP_SecondSuff: dtok.dtt = DTT_NumTimesuff; dtok.suffix = sep; break; default: // Invalid separator after number number. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // Found the token already. Let's bail. // return; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else { // // number is overflowed. // throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } switch (sep = GetSeparator(str, raw, dtfi)) { // // 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 SEP_End: dtok.dtt = DTT_NumEnd; raw.num[raw.numCount++] = dtok.num; break; case SEP_Am: case SEP_Pm: dtok.dtt = DTT_NumAmpm; raw.num[raw.numCount++] = dtok.num; break; case SEP_Space: dtok.dtt = DTT_NumSpace; raw.num[raw.numCount++] = dtok.num; break; case SEP_Date: dtok.dtt = DTT_NumDatesep; raw.num[raw.numCount++] = dtok.num; break; case SEP_Time: if (!result.timeZoneUsed) { dtok.dtt = DTT_NumTimesep; raw.num[raw.numCount++] = dtok.num; } else { // If we already got timezone, there should be no // time separator again. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } break; case SEP_YearSuff: dtok.num = dtfi.Calendar.ToFourDigitYear(number); dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_MonthSuff: case SEP_DaySuff: dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_HourSuff: case SEP_MinuteSuff: case SEP_SecondSuff: dtok.dtt = DTT_NumTimesuff; dtok.suffix = sep; break; case SEP_LocalTimeMark: dtok.dtt = DTT_NumLocalTimeMark; raw.num[raw.numCount++] = dtok.num; break; default: // Invalid separator after number number. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else { // // Not a letter, not a digit. Just ignore it. // str.Index++; } return; }
// // This is the real method to do the parsing work. // internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) { if (s == null) { throw new ArgumentNullException("s", Environment.GetResourceString("ArgumentNull_String")); } if (s.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (dtfi == null) { dtfi = DateTimeFormatInfo.CurrentInfo; } DateTime time; // // First try the predefined format. // int dps = DS_BEGIN; // Date Parsing State. bool reachTerminalState = false; DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. DateTimeToken dtok = new DateTimeToken(); // The buffer to store the parsing token. DateTimeRawInfo raw = new DateTimeRawInfo(); // The buffer to store temporary parsing information. result.calendar = dtfi.Calendar; // // 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); 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. Lex(dps, str, dtok, raw, result, ref dtfi); // // 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 != SEP_Unk) { ProcessDateTimeSuffix(result, raw, dtok); dtok.suffix = SEP_Unk; // Reset suffix to SEP_Unk; } if (dps == DS_D_YN && dtok.dtt == DTT_NumLocalTimeMark) { // Consider this as ISO 8601 format: // "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00 return (ParseISO8601(raw, str, styles)); } // // Advance to the next state, and continue // dps = dateParsingStates[dps][dtok.dtt]; if (dps == DS_ERROR) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): dps is DS_ERROR"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } else if (dps > DS_ERROR) { ProcessTerminaltState(dps, result, raw, dtfi); 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) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): terminal state is not reached"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // 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. // CheckDefaultDateTime(result, ref result.calendar, styles); try { if (raw.era == -1) { raw.era = Calendar.CurrentEra; } time = result.calendar.ToDateTime(result.Year, result.Month, result.Day, result.Hour, result.Minute, result.Second, 0, raw.era); if (raw.fraction > 0) { time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond)); } } catch (Exception) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): time is bad"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // NOTENOTE : // 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)) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): day of week is not correct"); throw new FormatException(Environment.GetResourceString("Format_BadDayOfWeek")); } } if (result.timeZoneUsed) { time = AdjustTimeZone(time, result.timeZoneOffset, styles, bTimeOnly); } return (time); }
// // A date suffix is found, use this method to put the number into the result. // private static void ProcessDateTimeSuffix(DateTimeResult result, DateTimeRawInfo raw, DateTimeToken dtok) { switch (dtok.suffix) { case SEP_YearSuff: result.Year = raw.year = dtok.num; break; case SEP_MonthSuff: result.Month= raw.month = dtok.num; break; case SEP_DaySuff: result.Day = dtok.num; break; case SEP_HourSuff: result.Hour = dtok.num; break; case SEP_MinuteSuff: result.Minute = dtok.num; break; case SEP_SecondSuff: result.Second = dtok.num; break; } }