DateTime.ParseExact returns today if date string and format are set to "General"
Asked Answered
E

3

10

During troubleshooting, I came across this weird behaviour which occurs when parsing text to create a DateTime object in C# (in .Net Core 8.0).

Can anybody explain why the following statements return today's date when I would have expected them to fail?

if (DateTime.TryParseExact("General", "General", null, DateTimeStyles.None, out var dateTime))
    Debug.WriteLine(dateTime);

var dateTime2 = DateTime.ParseExact("General", "General", null);
Debug.WriteLine(dateTime2);

Is this behaviour expected? And if it is, can anyone explain why or how it makes sense?

Exoteric answered 6/9 at 11:33 Comment(5)
Well any text repeated twice as s and format will be accepted and return current date, try DateTime.ParseExact("123", "123", null).Synopsis
Hmm, what Sinatr said would be true if ParseExact / TryParseExact were only looking for custom date and time formatting strings. The characters in "General" matches none of them and would be matching against the literal characters instead.Wheelbarrow
That would be bad though as the documentation not only doesn't mention ignoring the standard date and time formatting strings, but even uses the standard ones in its code examples.Wheelbarrow
Documentation often just tells you what they did, not why they did it that way. Often we're left with an unsatisfying answer to "why or how it makes sense" (esp. with design choices you don't agree with) being just "because that's how they designed it."Bedesman
@Powerlord: "General" is not a standard formatting string. "A standard date and time format string uses a single character as the format specifier to define the text representation of a DateTime or a DateTimeOffset value. Any date and time format string that contains more than one character, including white space, is interpreted as a custom date and time format string." (from your own link)Tericaterina
K
17

From the documentation remarks:

If format defines a date with no time element and the parse operation succeeds, the resulting DateTime value has a time of midnight (00:00:00).

If format defines a time with no date element and the parse operation succeeds, the resulting DateTime value by default has a date of DateTime.Now.Date, or it has a date of DateTime.MinValue.Date if styles includes the DateTimeStyles.NoCurrentDateDefault flag.

The value "General" matches the format you are using ("General"), but provides neither date or time, so in both cases the default value is used.

Komi answered 6/9 at 11:55 Comment(2)
Awesome. Looking forward to Assert.IsTrue(DateTime.ParseExact("General", "General", null) == DateTime.ParseExact("General", "General", null))failing during the midnight unit test run.Bedesman
@Wyck, fix your unit test with DateTimeStyles.NoCurrentDateDefault ^^Synopsis
D
3

It is an intended behavior of the CheckDefaultDateTime method, which dates back to the very first commit of Microsoft's library itself (2014). This method is called by DateTime.TryParseExact/DateTime.ParseExact, explaining why it defaults to today's date in certain cases.

https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/globalization/datetimeparse.cs#L2517

        // Check if the parsed string only contains hour/minute/second values.
        bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);

        //
        // Check if any year/month/day is missing in the parsing string.
        // If yes, get the default value from today's date.
        //
        if (!CheckDefaultDateTime(ref result, ref result.calendar, styles)) {
            TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps);
            return false;
        }

In particular the line: https://github.com/microsoft/referencesource/blob/master/mscorlib/system/globalization/datetimeparse.cs#L3381

        if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1)) {
            /*
            The following table describes the behaviors of getting the default value
            when a certain year/month/day values are missing.

            An "X" means that the value exists.  And "--" means that value is missing.

            Year    Month   Day =>  ResultYear  ResultMonth     ResultDay       Note

            X       X       X       Parsed year Parsed month    Parsed day
            X       X       --      Parsed Year Parsed month    First day       If we have year and month, assume the first day of that month.
            X       --      X       Parsed year First month     Parsed day      If the month is missing, assume first month of that year.
            X       --      --      Parsed year First month     First day       If we have only the year, assume the first day of that year.

            --      X       X       CurrentYear Parsed month    Parsed day      If the year is missing, assume the current year.
            --      X       --      CurrentYear Parsed month    First day       If we have only a month value, assume the current year and current day.
            --      --      X       CurrentYear First month     Parsed day      If we have only a day value, assume current year and first month.
            --      --      --      CurrentYear Current month   Current day     So this means that if the date string only contains time, you will get current date.

            */

The method also considers the DateTimeStyles.NoCurrentDateDefault flag, which influences whether the current date is used as a default when no date is provided.

Diagenesis answered 6/9 at 12:54 Comment(0)
B
2

Because G, e, n, r, a, l happen to not be any date format specifiers, they will be interpreted as character literals.

This documentation explains the way how literals are parsed:

In a parsing operation, they must match the characters in the input string exactly; the comparison is case-sensitive.

Bluh answered 6/9 at 12:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.