Why don't DateTime.ToString("R") and DateTime.TryParseExact round trip?
Asked Answered
L

1

5

I'm implementing conditional requests in a Web service. The backend can easily retrieve the last modified date of an entity, so I'm sending Last-Modified and getting back If-Modified-Since. The RFC for HTTP Dates specifies a format that is the same as the "R" format specifier in .NET.

The problem is that DateTime.ToString("R") formats the date correctly, but passing "R" to ParseExact doesn't read the time zone back (there is a "Round trip" specifier, "O", but it's not in the format I need). Here's an example in LinqPad:

DateTime lastModified = new DateTime(2015, 10, 01, 00, 00, 00, DateTimeKind.Utc);
string lastModifiedField = lastModified.ToString("R"); // Thu, 01 Oct 2015 00:00:00 GMT
DateTime ifModifiedSince = DateTime.ParseExact(
   lastModifiedField, "R", CultureInfo.InvariantCulture);

ifModifiedSince.Kind.Dump(); // Unspecified

I can certainly use methods on the parsed DateTime to force it into the format I want, but how can I get the framework to use the data that's already there?

Leong answered 9/10, 2015 at 20:2 Comment(0)
L
7

I stumbled across the reference source that explains this, hence asking and answering my own question.

The source to datetimeparse.cs indicates that this is a bug that can't be fixed for compatibility.

// The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively.  We cannot
// correct this mistake for DateTime.ParseExact for compatibility reasons, but we can 
// fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released
// with this issue.

So the code this comment precedes is called by both DateTime.ParseExact and DateTimeOffset.ParseExact, and suggests in effect that DateTimeOffset.ParseExact is more correct. Indeed, according to the documentation on choosing between DateTime and DateTimeOffset:

These uses for DateTimeOffset values are much more common than those for DateTime values. As a result, DateTimeOffset should be considered the default date and time type for application development.

Therefore the ideal solution is to switch to DateTimeOffset, but if you still need DateTime:

DateTime lastModified = new DateTime(2015, 10, 01, 00, 00, 00, DateTimeKind.Utc);
string lastModifiedField = lastModified.ToString("R");
DateTimeOffset ifModifiedSinceOffset = DateTimeOffset.ParseExact(
   lastModifiedField, "R", CultureInfo.InvariantCulture);
DateTime ifModifiedSince = ifModifiedSinceOffset.UtcDateTime;

ifModifiedSince.Kind.Dump(); // Utc

Which correctly identifies the time zone as GMT/UTC, and thus sets the correct property on the DateTime.

Leong answered 9/10, 2015 at 20:2 Comment(6)
Yes, this is good - except note that the final Kind will always be Utc, since you obtained it from .UtcDateTime. But at least the parsing is correct. You'll be interested to also observe that DateTime.Parse does work - at least it does when you pass DateTimeStyles.RoundtripKind. It's only DateTime.ParseExact that doesn't.Drona
Of course - it would be preferred to use ISO8601 instead of this horrendous format, but still - good catch!Drona
Also, you might want to log this at github.com/dotnet/coreclr/issues - seems silly not to fix it.Drona
@MattJohnson, the comment says they cannot fix it for compatibility purposes, and I see their point. And I agree that this format is silly (why include the day of the week in a field meant for computers to read) but I was trying to follow the IRC precisely because it's a luxury to have a spec every once in a while. ;)Leong
Thanks, but I disagree with keeping this bug around. I logged it here: github.com/dotnet/coreclr/issues/1757Drona
I guess there's (hopefully) not a lot of code out there that "needs" this to be broken. Thanks for filing!Leong

© 2022 - 2024 — McMap. All rights reserved.