Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 225
Steve Howard Avatar asked Mar 15 '23 12:03

Steve Howard


1 Answers

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.

like image 194
Steve Howard Avatar answered Apr 06 '23 06:04

Steve Howard