DateTime.TryParseExact not working as expected

2019-04-20 08:09发布

问题:

Can anyone explain why the following snippet returns true?

According to the docs for The "d" custom format specifier, "A single-digit day is formatted without a leading zero." So why doesn't TryParseExact fail when I give it a single-digit day with a leading zero?

DateTime x;
return DateTime.TryParseExact
(
    "01/01/2001",
    @"d\/MM\/yyyy",
    null,
    System.Globalization.DateTimeStyles.None,
    out x
);

UPDATE

I think maybe I was unclear originally. What I am really trying to get at is: Why does TryParseExact accept some values that don't match exactly? from all of the documentation I have seen, 'd' matching '01' and '1' is just as much a bug as if 'MM' matched 'March' as well as '03'. The issue here isn't that the values are equivalent, its that they don't match the format.

The relevant snippets of documentation are:

  • From TryParseExact: The format of the string representation must match a specified format exactly.

  • From The 'd' Specifier: A single-digit day is formatted without a leading zero.

It seems abundantly clear to me that '01' has a leading 0, and therefore doesn't exactly match 'd'.

回答1:

From the .NET 4 source in DateTimeParse.ParseByFormat():

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
    {...}

The parser lumps "d" and "dd" together.



回答2:

It appears that behavior is by design, and I think it works that way to be consistent with other string formatting options.

Take the following example:

//Convert DateTime to string
string dateFormat = "d/MM/yyyy";
string date1 = new DateTime(2008, 10, 5).ToString(dateFormat);
string date2 = new DateTime(2008, 10, 12).ToString(dateFormat);

//Convert back to DateTime
DateTime x1, x2;
DateTime.TryParseExact(date1, dateFormat, null, System.Globalization.DateTimeStyles.None, out x1);
DateTime.TryParseExact(date2, dateFormat, null, System.Globalization.DateTimeStyles.None, out x2);

Console.WriteLine(x1);
Console.WriteLine(x2);

In the first part, ToString() outputs a two digit day for October 12th, because it wouldn't make much sense to just write out a single digit day (and which digit would it pick, the 1 or the 2?). So since the "d" represents one OR two digit days when converting to a string, it would have to work the same way when converting back to DateTime. If it didn't, the conversion back to DateTime in TryParseExact in my example would fail, and that would definitely not be an expected behavior.

I would say that if you really need to match a d/MM/yyyy format exactly, you could probably use a regex to validate the string and then pass it through Parse, TryParse or TryParseExact (depending on how good your regex is, since it would have to handle leap years, 30/31 days, etc if you wanted to use Parse).



回答3:

I'd say it doesn't fail because TryParseExact is smart enough to know that '01' == '1'.



回答4:

TryParseExact is just trying to be flexible in this case I guess. But the "d" vs "dd" should and would work as advertised when you are converting date to string using a format specifier.



回答5:

Because a single 'd' means that your DateTime value will be converted to as short value as possible, i.e. without leading zero if there's no necessity for it. I suppose it shouldn't fail when you're converting from string to DateTime because the main purpose of TryParseExact's format string is to help to convert to DateTime, i.e. it serves like a hint, it's not intended to validate string format.

You can use RegEx if you still need hardcore string format validation.