3

I have a time and a date value with no field delimiters which I'm attempting to parse into a DateTime using TryParseExact. The time component has a single digit hour, and two digit minutes and seconds.

The following expression:

DateTime.ParseExact("20170101 84457", "yyyyMMdd Hmmss", 
    System.Globalization.CultureInfo.InvariantCulture)

results in FormatException with the message "String was not recognized as a valid DateTime.". I am assuming that this is because the time can't be unambiguously resolved, however since mm and ss are always going to be two digits each, I don't understand why this would be an issue.

The following results in successful parsing:

  • Hacking the input time to include delimiters (e.g., '8:44:57' and 'H:mm:ss')
  • Kludging the input time to have a leading zero if < 6 digits

Both of these seem a bit of a hack.

nullPainter
  • 2,676
  • 3
  • 22
  • 42
  • 2
    It might be a hack, but forcing a 0 prefix may just be what you have to do. – GreatAndPowerfulOz Jul 10 '17 at 22:12
  • It appears that the cause is due to how ParseExact works under the hood, attempting to greedily retrieve two characters when it can. There's a breakdown of the behaviour here: https://stackoverflow.com/questions/2016206/net-why-is-tryparseexact-failing-on-hmm-and-hmmss/2016451#2016451 – nullPainter Jul 10 '17 at 22:35
  • 1
    Yeah, that seems to be the problem. `DateTime.ParseExact` ends up calling `DateTimeParse.DoStrictParse`, and even though you specify `H`, it still grabs two digits to evaluate for the hour. The source code is here if you really want to see how it works: http://referencesource.microsoft.com/mscorlib/R/f1835935628e4a2f.html – Rufus L Jul 10 '17 at 22:46
  • Format H means value can be between 0 to 23. It is obvious that even though format has been specified as H still value can be of 2 digit. Perhaps that;s the reason why to read 2 digit to form H. – MKR Jul 10 '17 at 22:55
  • 1
    @nullPainter You can see what .NET Team suggest for this situation on my answer as well. https://stackoverflow.com/a/26778076/447156 – Soner Gönül Jul 11 '17 at 05:58

2 Answers2

3

From what I can understand from other people's research, parsing attempts to retrieve two digits if it can, and parses from left to right.

Using my failing example of the raw time value 84857 and format Hmmss, because the hour is followed by a digit, it will be parsed as 84 - hence throwing a format exception.

nullPainter
  • 2,676
  • 3
  • 22
  • 42
0

According to the documentation:

If format is a custom format pattern that does not include date or time separators (such as "yyyyMMddHHmm"), 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 format pattern, specify the wider form, "HH", instead of the narrower form, "H".

So it appears that if you don't have any delimiters, you are required to use HH and not H.

Personally I would pad the time component to 6 digits and use HH. The following works fine for me:

DateTime.ParseExact("20170101 084457", "yyyyMMdd hhmmss", System.Globalization.CultureInfo.InvariantCulture);

If you want to wrap this in a custom function, you can use something like this:

    static DateTime ParseDateTime(string input)
    {
        int dateInteger, timeInteger;

        var s = input.Split(' ');
        bool dateOK = int.TryParse(s[0], out dateInteger);
        bool timeOK = int.TryParse(s[1], out timeInteger);

        if (!dateOK || !timeOK) throw new FormatException("Invalid date/time string.");

        var newInput = String.Format("{0:00000000} {1:000000}", dateInteger, timeInteger);
        return DateTime.ParseExact(newInput, "yyyyMMdd hhmmss", System.Globalization.CultureInfo.InvariantCulture);
    }
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • 1
    I read and attempted that too. Unfortunately that manifests the same behaviour. – nullPainter Jul 10 '17 at 22:00
  • Works fine for me. You sure you got it right? – John Wu Jul 10 '17 at 22:01
  • My response was to your answer before you edited it to add a leading zero to the time. I am also currently adding a leading zero, but it seems a bit of a kludge. – nullPainter Jul 10 '17 at 22:03
  • 1
    Using just `H` works fine if the input is padded with a leading `0` (also in your example you are switching to a 12-hour clock instead of the expected 24-hour clock) – Rufus L Jul 10 '17 at 22:06
  • 1
    I didn't edit my answer to add a digit... I cut and pasted working code from my Visual Studio. Maybe you were looking at a different answer. FWIW, storing a time as a 5-digit integer also seems like a kludge, no offense. – John Wu Jul 10 '17 at 22:10
  • Oh, I agree. In this case, the time is an integer being returned from a mainframe. – nullPainter Jul 10 '17 at 22:11
  • You added the note "...I would pad the time component to 6 digits...". `DateTime.ParseExact("20170101 84457", "yyyyMMdd HHmmss", System.Globalization.CultureInfo.InvariantCulture);` fails, as I noted. – nullPainter Jul 10 '17 at 22:23
  • For me it worked after changing 8 to 08. – MKR Jul 10 '17 at 22:24