我需要以"Myy"格式解析DateTime,所以:
- 第一个数字是没有前导零的月份(1到12(,并且
- 第二个数字是有两位数字的年份
示例:
115 -> January 2015
1016 -> October 2016
当使用带有"Myy"的DateTime.ParseExact
作为格式时,当月份没有前导零时,DateTime
抛出异常。
此代码引发一个异常:
var date = DateTime.ParseExact("115",
"Myy",
CultureInfo.InvariantCulture); // throws FormatException
虽然这很好:
var date = DateTime.ParseExact("1016",
"Myy",
CultureInfo.InvariantCulture); // works fine
MSDN文档明确定义了格式说明符:
- "M"–月份,从1到12
- "MM"–月份,从01到12
- "yy"–年份,从00到99
是否有任何格式可以解决上述情况,即"Myy"日期-时间格式,其中月份没有前导零?
编辑
确切地说:问题是关于在ParseExact中具体使用格式,而不是关于如何使用字符串操作来解析它本身。
这是因为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
。因此,基本上,对于传递的1
的digitLen
,函数也将接受最大长度2(这使得可以使用单个M
来匹配2位数的月份(。
现在,另一个实际完成工作的重载包含这个循环:
while (tokenLength < maxDigitLen) {
if (!str.GetNextDigit()) {
str.Index--;
break;
}
result = result * 10 + str.GetDigit();
tokenLength++;
}
正如您所看到的,该方法不断从字符串中提取更多的数字,直到它超过最大数字长度。该方法的其余部分只是错误检查之类的。
最后,让我们看看DoStrictParse
中的实际解析。在那里,我们有以下循环:
// 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
(,并使用该信息分支到不同的格式。在我们的几个月里,这是相关的部分:
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
,对于单个M
,它以1
的令牌长度传递。但正如我们在上面看到的,如果可以的话,它仍然会匹配两位数;所有这些都没有验证它匹配的两位数在一个月内是否有意义。因此130
也无法与2030年1月相匹配。它将匹配为第13个月,然后在那里失败。
来自MSDN:
如果格式是不包括日期或时间分隔符(如"yyyyMMdd HHmm"(,使用不变区域性用于提供程序参数和每个自定义格式的最宽形式说明符。例如,如果要在格式中指定小时模式,指定较宽的形式"HH",而不是较窄的形式,"H"。
换句话说,它很可能无法按照你想要的方式解决。
太简单了??
string date = "115";
if (date.Count()==3)
{
date = "0" + date;
}