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 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; }
// // 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; }
// // 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; }