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?

Community
  • 1
  • 1
Steve Howard
  • 6,737
  • 1
  • 26
  • 37

1 Answers1

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.

Steve Howard
  • 6,737
  • 1
  • 26
  • 37
  • 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. – Matt Johnson-Pint Oct 10 '15 at 00:17
  • Of course - it would be preferred to use ISO8601 instead of this horrendous format, but still - good catch! – Matt Johnson-Pint Oct 10 '15 at 00:18
  • Also, you might want to log this at https://github.com/dotnet/coreclr/issues - seems silly not to fix it. – Matt Johnson-Pint Oct 10 '15 at 00:19
  • @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. ;) – Steve Howard Oct 10 '15 at 05:17
  • Thanks, but I disagree with keeping this bug around. I logged it here: https://github.com/dotnet/coreclr/issues/1757 – Matt Johnson-Pint Oct 13 '15 at 18:06
  • I guess there's (hopefully) not a lot of code out there that "needs" this to be broken. Thanks for filing! – Steve Howard Oct 13 '15 at 18:28