DateTime.TryParse cannot parse DateTime.MinValue

2019-06-20 06:46发布

问题:

I'm using a library called Json.NET that uses the following code internally to parse a JSON string into a DateTime:

if (DateTime.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt))
{
     dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling);
     SetToken(JsonToken.Date, dt);
     return dt;
}

I thought Json.NET was screwing up the conversion, but it looks like it's DateTime.TryParse itself that's botching the value.

When I parse the following valid Iso date (which corresponds to UTC DateTime.MinValue):

string json = "0001-01-01T00:00:00+00:00";
DateTime dt;
DateTime.TryParse(json, invariantCulture, DateTimeStyles.RoundtripKind, out dt);

The result is a localized DateTime: {0001-01-01 8:00:00 PM}, which when converted back to Utc time gives {0001-01-02 0:00:00 PM}. Essentially, the date underflowed, which is exactly the kind of problem you would expect DateTimeStyles.RoundtripKind to avoid.

How do I avoid this scenario?

回答1:

Why use DateTimeStyles.RoundtripKind? The documentation for RoundtripKind says:

The DateTimeKind field of a date is preserved when a DateTime object is converted to a string using the "o" or "r" standard format specifier, and the string is then converted back to a DateTime object.

The string output from the "o" or "r" standard format specifiers are not like the ISO 8601 string you are trying to parse. It doesn't sound to me like RoundtripKind is really supposed to work with any date time string format. It sounds like the round trip is for the DateTime.Kind property when the string is in a particular format.

Since you know the format of the string you are trying to parse, then I would suggest using DateTime.TryParseExact.

I have had to support a couple different versions of the ISO 8601 string - either of these formats are valid date-time values in ISO 8601 (and there are even more options for dates, times and fractional seconds, but I didn't those):

0001-01-01T00:00:00+00:00

0001-01-01T00:00:00Z

Here's a method that will handle either of these formats:

private bool TryParseIso8601(string s, out DateTime result)
{
    if (!string.IsNullOrEmpty(s))
    {
        string format = s.EndsWith("Z") ? "yyyy-MM-ddTHH:mm:ssZ" : "yyyy-MM-ddTHH:mm:sszzz";
        return DateTime.TryParseExact(s, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result);
    }

    result = new DateTime(0L, DateTimeKind.Utc);
    return false;
}