【问题标题】:Parsing DateTime in a "Myy" format以“Myy”格式解析 DateTime
【发布时间】:2015-05-21 20:01:03
【问题描述】:

我需要以“Myy”格式解析 DateTime,所以:

  • 第一个数字是没有前导零的月份(1 到 12),并且
  • 第二个数字是两位数的年份。

例子:

115 -> January 2015
1016 -> October 2016

当使用DateTime.ParseExact 和“Myy”作为格式时,DateTime 会在月份不带前导零时引发异常。

此代码引发异常:

var date = DateTime.ParseExact("115", 
   "Myy", 
   CultureInfo.InvariantCulture); // throws FormatException

虽然这很好用:

var date = DateTime.ParseExact("1016", 
    "Myy", 
    CultureInfo.InvariantCulture); // works fine

MSDN Documentation 明确定义了格式说明符:

  • “M”- 月份,从 1 到 12。
  • "MM" – 月份,从 01 到 12。
  • "yy" – 年份,从 00 到 99。

是否有任何格式可以解决上述情况,即“Myy”日期时间格式,其中月份没有前导零?

编辑

确切地说:问题是关于在 ParseExact 中具体使用格式,而不是关于如何通过使用字符串操作来解析它本身。

【问题讨论】:

  • 在这么简单的场景下,何不干脆将字符串拆分,通过int.parse运行?
  • @Amit:我知道它很容易分解和解决,但问题是关于使用正确的 DateTime 格式。
  • @DariuszWoźniak 目标是解析字符串,还是通过 DateTime.ParseExact 传递字符串?
  • @SonerGönül:非常有趣,谢谢。

标签: c# parsing datetime


【解决方案1】:

这是因为 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。所以基本上,对于传递的digitLen1,该函数还将接受最大长度为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 个月匹配,并在稍后失败。

【讨论】:

  • the DateTime parser reads from left to right without backtracking. - 你有那个来源吗?另外 - 如果第一个数字不是 1 怎么办 - 它不应该再读了。
  • @ispiro 你要的,我加的!
  • 感谢您的快速修复!顺便说一句 - 干得好!
  • 谢谢,我真的很喜欢深入研究它;)(顺便说一句,我并不是说“要求”消极。;)期待经过充分研究的答案总是受欢迎的)
  • @poke:非常感谢,这就是答案! :)
【解决方案2】:

来自MSDN

如果 format 是不包含日期的自定义格式模式或 时间分隔符(例如“yyyyMMdd HHmm”),使用不变的文化 用于提供者参数和每种自定义格式的最宽形式 说明符。例如,如果您想以格式指定小时数 模式,指定较宽的形式,“HH”,而不是较窄的形式, “H”。

也就是说,很可能无法按照你想要的方式解决。

【讨论】:

    【解决方案3】:

    太简单了??

          string date = "115";
          if (date.Count()==3)
          {
             date = "0" + date;
          }
    

    【讨论】:

    • 我需要没有字符串操作的方法。还是谢谢你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 2012-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    相关资源
    最近更新 更多