这是因为 DateTime 解析器从左到右读取,没有回溯。
由于它尝试读取月份,它开始获取前两位数字并使用它来解析月份。然后它尝试解析年份,但只剩下一位数字,所以它失败了。如果不引入分隔符,根本没有办法解决这个问题:
DateTime.ParseExact("1 15", "M yy", CultureInfo.InvariantCulture)
如果您不能这样做,请先从右侧读取并分别拆分年份(使用字符串操作)。或者只是在开头添加一个零并将其解析为MMyy:
string s = "115";
if (s.Length < 4)
s = "0" + s;
Console.WriteLine(DateTime.ParseExact(s, "MMyy", CultureInfo.InvariantCulture));
研究!
由于 ispiro 要求来源:解析由 DateTimeParse 类型完成。与我们相关的是ParseDigits 方法:
internal static bool ParseDigits(ref __DTString str, int digitLen, out int result) {
if (digitLen == 1) {
// 1 really means 1 or 2 for this call
return ParseDigits(ref str, 1, 2, out result);
}
else {
return ParseDigits(ref str, digitLen, digitLen, out result);
}
}
请注意,digitLen 等于 1 的情况下的注释。知道另一个ParseDigits 重载中的第一个数字是minDigitLen,另一个是maxDigitLen。所以基本上,对于传递的digitLen 或1,该函数还将接受最大长度为2(这使得可以使用单个M 来匹配两位数的月份)。
现在,实际工作的other overload 包含这个循环:
while (tokenLength < maxDigitLen) {
if (!str.GetNextDigit()) {
str.Index--;
break;
}
result = result * 10 + str.GetDigit();
tokenLength++;
}
如您所见,该方法不断从字符串中获取更多数字,直到超过最大数字长度。该方法的其余部分只是错误检查和其他东西。
最后,我们来看看DoStrictParse中的实际解析。在那里,我们有following loop:
// Scan every character in format and match the pattern in str.
while (format.GetNext()) {
// We trim inner spaces here, so that we will not eat trailing spaces when
// AllowTrailingWhite is not used.
if (parseInfo.fAllowInnerWhite) {
str.SkipWhiteSpaces();
}
if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
return (false);
}
}
所以基本上,这会遍历格式字符串中的字符,然后尝试使用该格式从左到右匹配字符串。 ParseByFormat 执行额外的逻辑来捕获重复的格式(例如 yy 而不仅仅是 y)并使用该信息分支到不同的格式。在我们的几个月里,这是the relevant part:
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);
}
}
}
因此,我们在这里关闭ParseDigits 的圆圈,它以1 的令牌长度传递给单个M。但正如我们在上面看到的,如果可以的话,它仍然会匹配两位数;所有这一切都没有验证它匹配的两位数是否在一个月内有意义。所以130 也不匹配 2030 年 1 月。它将与第 13 个月匹配,并在稍后失败。