views:

369

answers:

6

I'm trying out the DateTime.TryParseExact method, and I have come over a case that I just don't get. I have some formats and some subjects to parse that each should match one of the formats perfectly:

var formats = new[]
     {
         "%H",
         "HH",
         "Hmm",
         "HHmm",
         "Hmmss",
         "HHmmss",
     };

var subjects = new[]
     {
         "1",
         "12",
         "123",
         "1234",
         "12345",
         "123456",
     };

I then try to parse them all and print out the results:

foreach(var subject in subjects)
{
    DateTime result;
    DateTime.TryParseExact(subject, formats, 
        CultureInfo.InvariantCulture, 
        DateTimeStyles.NoCurrentDateDefault,
        out result);

    Console.WriteLine("{0,-6} : {1}", 
        subject,
        result.ToString("T", CultureInfo.InvariantCulture));
}

I get the following:

1      : 01:00:00
12     : 12:00:00
123    : 00:00:00
1234   : 12:34:00
12345  : 00:00:00
123456 : 12:34:56

And to my question... why is it failing on 123 and 12345? Shouldn't those have become 01:23:00 and 01:23:45? What am I missing here? And how can I get it to work as I would expect it to?


Update: So, seems like we might have figured out why this is failing sort of. Seems like the H is actually grabbing two digits and then leaving just one for the mm, which would then fail. But, does anyone have a good idea on how I can change this code so that I would get the result I am looking for?

Another update: Think I've found a reasonable solution now. Added it as an answer. Will accept it in 2 days unless someone else come up with an even better one. Thanks for the help!

+3  A: 

0123 012345

I'm guessing it looks for a length of 2/4/6 when it finds a string of numbers like that. Is 123 supposed to be AM or PM? 0123 isn't ambiguous like that.

Jimbo
well, the H is supposed to be 24-hour format (h is for 12-hour). And in my head, when the format is Hmm, and the string is 123, it shouldn't really be much room for ambiguity. Although it could be that it takes the 12 for H and then fails to match 3 into m... but yeah... how would I sove that then?
Svish
In that case, I would expect a 24 hour symbol to require 2 digits. I don't know how a single H would or should be interpreted.
Jimbo
According to msdn it is just the same, except it is not using leading zeros: *"H" -- The hour, using a 24-hour clock from 0 to 23.*
Svish
I thought starting with a digit that couldn't possibly be a two digit hour might work, but "300" didn't parse either.
Jimbo
A: 

"123" and "12345" seem to be ambiguous with respect to the TryParseExact method. "12345" could be either 12:34:50 or 01:23:45 for instance. Just a guess though.

Rodrick Chapman
A: 

I could be wrong, but I suspect it may have to do with the ambiguity inherent in the "H" part of your format string -- i.e., given the string "123", you could be dealing with hour "1" (01:00) or hour "12" (12:00); and since TryParseExact doesn't know which is correct, it returns false.

As for why the method does not supply a "best guess": the documentation is not on your side on this one, I'm afraid. From the MSDN documentation on DateTime.TryParse (emphasis mine):

When this method returns, contains the DateTime value equivalent to the date and time contained in s, if the conversion succeeded, or DateTime.MinValue if the conversion failed. The conversion fails if either the s or format parameter is null, is an empty string, or does not contain a date and time that correspond to the pattern specified in format. This parameter is passed uninitialized.

Dan Tao
Yeah, I thought about that too, but in still shouldn't be failing though, should it?
Svish
Well, I think it's generally accepted that if a method of this sort (accepts an `out` parameter, returns a bool) returns false, you shouldn't use what was assigned to the `out` parameter. So once the method realized it would fail, it just quit without bothering to venture a "best guess." (Of course, this is just me thinking aloud...)
Dan Tao
Well, obviously. But the point here isn't whether I should use the result or not, but why it is failing to give me one in the first place ;-)
Svish
Think of it this way: with a `DateTime` this might seem trivial, but in general it takes some amount of work to parse a string to a given data type. Rather than *do* that work when the result is not going to be useful anyway, why not just quit and inform your caller you couldn't do it?
Dan Tao
Because the specifications say that I can :p It's a special feature in our software to allow users to type in dates and times more quickly. I have a similar function for dates which in addition for example uses the current month and/or year if not enough digits are there to extract one.
Svish
When you say "specifications," I'm assuming you mean your *own* software's specs? The MSDN documentation does in fact state that the output will be set to `DateTime.MinValue` in the event of failure, as I've indicated in my edit. If your spec requires the inputs you've supplied in the question to be parsable, it seems you're going to have to write your own method (most likely utilizing `DateTime.TryParseExact` where it works, and using custom logic where it doesn't).
Dan Tao
A: 

To quote from MSDN's Using Single Custom Format Specifiers:

A custom date and time format string consists of two or more characters. For example, if the format string consists only of the specifier h, the format string is interpreted as a standard date and time format specifier. However, in this particular case, an exception is thrown because there is no h standard date and time format specifier.

To use a single custom date and time format specifier, include a space before or after the date and time specifier, or include a percent (%) format specifier before the single custom date and time specifier. For example, the format strings "h " and "%h" are interpreted as custom date and time format strings that display the hour represented by the current date and time value. Note that, if a space is used, it appears as a literal character in the result string.

So, should that have been % H in the first element in the formats array?

Hope this helps, Best regards, Tom.

tommieb75
+1  A: 

If you do not use date or time separators in a custom format pattern, use the invariant culture for the provider parameter and the widest form of each custom format specifier. For example, if you want to specify hours in the pattern, specify the wider form, "HH", instead of the narrower form, "H"

cite: http://msdn.microsoft.com/en-us/library/ms131044.aspx

As others have pointed out H is ambiguous because it implies a 10 hour day, where as HH is 12

tzenes
10 hour day? Not sure if I am following you...
Svish
H implies a single human readable digit for the hour. Thus the possible options are 0-9 or 10 different hours. Since we have a twelve hour day and human readable digits are base ten, the input requires HH at minimum for hour
tzenes
+1  A: 

Ok, so I think I have figured this all out now thanks to more reading, experimenting and the other helpful answers here. What's happening is that H, m and s actually grabs two digits when they can, even if there won't be enough digits for the rest of the format. So the for example with the format Hmm and the digits 123, H would grab 12 and there would only be a 3 left. And mm requires two digits, so it fails. Tadaa.

So, my solution is currently to instead use just the following three formats:

var formats = new[]
    {
        "%H",
        "Hm",
        "Hms",
    };

With the rest of the code from my question staying the same, I will then get this as a result:

1      : 01:00:00
12     : 12:00:00
123    : 12:03:00
1234   : 12:34:00
12345  : 12:34:05
123456 : 12:34:56

Which I think should be both reasonable and acceptable :)

Svish
Well, it's a solution. I don't know if I would call 12:03 the time I would expect from an input of "123"!
Jimbo
I kind of agree, but at the same time it does make *some* sense at least :p Also I am just not sure how I can parse it as 1:23 without a bunch more code. Please enlighten me if you have some good ideas!
Svish