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