// Given a specified format character, parse and update the parsing result.
//
private static bool ParseByFormat(
ref __DTString str,
ref __DTString format,
ref ParsingInfo parseInfo,
DateTimeFormatInfo dtfi,
ref DateTimeResult result) {
int tokenLen = 0;
int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0;
double tempFraction = 0;
TM tempTimeMark = 0;
char ch = format.GetChar();
switch (ch) {
case 'y':
tokenLen = format.GetRepeatCount();
bool parseResult;
if (dtfi.HasForceTwoDigitYears) {
parseResult = ParseDigits(ref str, 1, 4, out tempYear);
}
else {
if (tokenLen <= 2) {
parseInfo.fUseTwoDigitYear = true;
}
parseResult = ParseDigits(ref str, tokenLen, out tempYear);
}
if (!parseResult && parseInfo.fCustomNumberParser) {
parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear);
}
if (!parseResult) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if (!CheckNewValue(ref result.Year, tempYear, ch, ref result)) {
return (false);
}
break;
case 'M':
tokenLen = format.GetRepeatCount();
if (tokenLen <= 2) {
if (!ParseDigits(ref str, tokenLen, out tempMonth)) {
if (!parseInfo.fCustomNumberParser ||
!parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
} else {
if (tokenLen == 3) {
if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
} else {
if (!MatchMonthName(ref str, dtfi, ref tempMonth)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
result.flags |= ParseFlags.ParsedMonthName;
}
if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result)) {
return (false);
}
break;
case 'd':
// Day & Day of week
tokenLen = format.GetRepeatCount();
if (tokenLen <= 2) {
// "d" & "dd"
if (!ParseDigits(ref str, tokenLen, out tempDay)) {
if (!parseInfo.fCustomNumberParser ||
!parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
if (!CheckNewValue(ref result.Day, tempDay, ch, ref result)) {
return (false);
}
} else {
if (tokenLen == 3) {
// "ddd"
if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
} else {
// "dddd*"
if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result)) {
return (false);
}
}
break;
case 'g':
tokenLen = format.GetRepeatCount();
// Put the era value in result.era.
if (!MatchEraName(ref str, dtfi, ref result.era)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
break;
case 'h':
parseInfo.fUseHour12 = true;
tokenLen = format.GetRepeatCount();
if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempHour)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
return (false);
}
break;
case 'H':
tokenLen = format.GetRepeatCount();
if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempHour)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
return (false);
}
break;
case 'm':
tokenLen = format.GetRepeatCount();
if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempMinute)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result)) {
return (false);
}
break;
case 's':
tokenLen = format.GetRepeatCount();
if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempSecond)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result)) {
return (false);
}
break;
case 'f':
case 'F':
tokenLen = format.GetRepeatCount();
if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits) {
if (!ParseFractionExact(ref str, tokenLen, ref tempFraction)) {
if (ch == 'f') {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
if (result.fraction < 0) {
result.fraction = tempFraction;
} else {
if (tempFraction != result.fraction) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
return (false);
}
}
} else {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
break;
case 't':
// AM/PM designator
tokenLen = format.GetRepeatCount();
if (tokenLen == 1) {
if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
} else {
if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
if (parseInfo.timeMark == TM.NotSet) {
parseInfo.timeMark = tempTimeMark;
}
else {
if (parseInfo.timeMark != tempTimeMark) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
return (false);
}
}
break;
case 'z':
// timezone offset
tokenLen = format.GetRepeatCount();
{
TimeSpan tempTimeZoneOffset = new TimeSpan(0);
if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'z');
return (false);
}
result.timeZoneOffset = tempTimeZoneOffset;
result.flags |= ParseFlags.TimeZoneUsed;
}
break;
case 'Z':
if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'Z');
return (false);
}
result.flags |= ParseFlags.TimeZoneUsed;
result.timeZoneOffset = new TimeSpan(0);
result.flags |= ParseFlags.TimeZoneUtc;
// The updating of the indexes is to reflect that ParseExact MatchXXX methods assume that
// they need to increment the index and Parse GetXXX do not. Since we are calling a Parse
// method from inside ParseExact we need to adjust this. Long term, we should try to
// eliminate this discrepancy.
str.Index++;
if (!GetTimeZoneName(ref str)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
str.Index--;
break;
case 'K':
// This should parse either as a blank, the 'Z' character or a local offset like "-07:00"
if (str.Match('Z')) {
if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K');
return (false);
}
result.flags |= ParseFlags.TimeZoneUsed;
result.timeZoneOffset = new TimeSpan(0);
result.flags |= ParseFlags.TimeZoneUtc;
}
else if (str.Match('+') || str.Match('-')) {
str.Index--; // Put the character back for the parser
TimeSpan tempTimeZoneOffset = new TimeSpan(0);
if (!ParseTimeZoneOffset(ref str, 3, ref tempTimeZoneOffset)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K');
return (false);
}
result.timeZoneOffset = tempTimeZoneOffset;
result.flags |= ParseFlags.TimeZoneUsed;
}
// Otherwise it is unspecified and we consume no characters
break;
case ':':
// We match the separator in time pattern with the character in the time string if both equal to ':' or the date separator is matching the characters in the date string
// We have to exclude the case when the time separator is more than one character and starts with ':' something like "::" for instance.
if (((dtfi.TimeSeparator.Length > 1 && dtfi.TimeSeparator[0] == ':') || !str.Match(':')) &&
!str.Match(dtfi.TimeSeparator)) {
// A time separator is expected.
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
break;
case '/':
// We match the separator in date pattern with the character in the date string if both equal to '/' or the date separator is matching the characters in the date string
// We have to exclude the case when the date separator is more than one character and starts with '/' something like "//" for instance.
if (((dtfi.DateSeparator.Length > 1 && dtfi.DateSeparator[0] == '/') || !str.Match('/')) &&
!str.Match(dtfi.DateSeparator))
{
// A date separator is expected.
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
break;
case '\"':
case '\'':
StringBuilder enquotedString = new StringBuilder();
// Use ParseQuoteString so that we can handle escape characters within the quoted string.
if (!TryParseQuoteString(format.Value, format.Index, enquotedString, out tokenLen)) {
result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch);
return (false);
}
format.Index += tokenLen - 1;
// Some cultures uses space in the quoted string. E.g. Spanish has long date format as:
// "dddd, dd' de 'MMMM' de 'yyyy". When inner spaces flag is set, we should skip whitespaces if there is space
// in the quoted string.
String quotedStr = enquotedString.ToString();
for (int i = 0; i < quotedStr.Length; i++) {
if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite) {
str.SkipWhiteSpaces();
} else if (!str.Match(quotedStr[i])) {
// Can not find the matching quoted string.
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
}
// The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively. We cannot
// correct this mistake for DateTime.ParseExact for compatibility reasons, but we can
// fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released
// with this issue.
if ((result.flags & ParseFlags.CaptureOffset) != 0) {
if ((result.flags & ParseFlags.Rfc1123Pattern) != 0 && quotedStr == GMTName) {
result.flags |= ParseFlags.TimeZoneUsed;
result.timeZoneOffset = TimeSpan.Zero;
}
else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName) {
result.flags |= ParseFlags.TimeZoneUsed;
result.timeZoneOffset = TimeSpan.Zero;
}
}
break;
case '%':
// Skip this so we can get to the next pattern character.
// Used in case like "%d", "%y"
// Make sure the next character is not a '%' again.
if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%') {
result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
return false;
}
break;
case '\\':
// Escape character. For example, "\d".
// Get the next character in format, and see if we can
// find a match in str.
if (format.GetNext()) {
if (!str.Match(format.GetChar())) {
// Can not find a match for the escaped character.
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
} else {
result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
return false;
}
break;
case '.':
if (!str.Match(ch)) {
if (format.GetNext()) {
// If we encounter the pattern ".F", and the dot is not present, it is an optional
// second fraction and we can skip this format.
if (format.Match('F')) {
format.GetRepeatCount();
break;
}
}
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
break;
default:
if (ch == ' ') {
if (parseInfo.fAllowInnerWhite) {
// Skip whitespaces if AllowInnerWhite.
// Do nothing here.
} else {
if (!str.Match(ch)) {
// If the space does not match, and trailing space is allowed, we do
// one more step to see if the next format character can lead to
// successful parsing.
// This is used to deal with special case that a empty string can match
// a specific pattern.
// The example here is af-ZA, which has a time format like "hh:mm:ss tt". However,
// its AM symbol is "" (empty string). If fAllowTrailingWhite is used, and time is in
// the AM, we will trim the whitespaces at the end, which will lead to a failure
// when we are trying to match the space before "tt".
if (parseInfo.fAllowTrailingWhite) {
if (format.GetNext()) {
if (ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
return (true);
}
}
}
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
// Found a macth.
}
} else {
if (format.MatchSpecifiedWord(GMTName)) {
format.Index += (GMTName.Length - 1);
// Found GMT string in format. This means the DateTime string
// is in GMT timezone.
result.flags |= ParseFlags.TimeZoneUsed;
result.timeZoneOffset = TimeSpan.Zero;
if (!str.Match(GMTName)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
} else if (!str.Match(ch)) {
// ch is expected.
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return false;
}
}
break;
} // switch
return (true);
}