runtime: `DateTime.Now.ToString("r")` incorrectly attributes GMT

Description

DateTime.Now.ToString("r") stamps the LOCAL time but appends GMT on the string, resulting in an INCORRECT output value.

Console.WriteLine(DateTime.Now.ToString("r"));
Console.WriteLine(DateTime.UtcNow.ToString("r"));
Console.WriteLine(DateTimeOffset.Now.ToString("r"));
Console.WriteLine(DateTimeOffset.UtcNow.ToString("r"));

produces:

Thu, 18 Aug 2022 14:33:58 GMT
Thu, 18 Aug 2022 21:33:59 GMT
Thu, 18 Aug 2022 21:33:59 GMT
Thu, 18 Aug 2022 21:33:59 GMT

The first line is factually incorrect, yet what the code produces.

Documentation points to the following note:

Although the RFC 1123 standard expresses a time as Coordinated Universal Time (UTC), the formatting operation does not modify the value of the DateTime object that is being formatted. Therefore, you must convert the DateTime value to UTC by calling the DateTime.ToUniversalTime method before you perform the formatting operation. In contrast, DateTimeOffset values perform this conversion automatically; there is no need to call the DateTimeOffset.ToUniversalTime method before the formatting operation. DateTime Struct (System) Represents an instant in time, typically expressed as a date and time of day.

The documentation effectively reads like we documented an unfixed bug.

Reproduction Steps

Console.WriteLine(DateTime.Now.ToString("r"));

Expected behavior

Would produce either a) the actual GMT correct value or b) the correct timezone noting – but not a mix of both resulting in an incorrect value.

Actual behavior

Local time with a GMT timezone stamp which is wrong

Regression?

No response

Known Workarounds

Don’t use DateTime.Now, use DateTime.UtcNow or DateTimeOffset

Configuration

.NET 7

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 27 (26 by maintainers)

Most upvoted comments

going back to the original issue, I already discussed this a few days ago with @timheuer. My take here is not to change anything as this is the behavior since day one and we are clearly documenting the behavior as indicated. It is not worth the app-compat breaks especially since we have some easier alternatives. I suggest closing this issue.

If that’s the case, probably best to stick to ISO 8601 (“o”) format, with the invariant culture.

That sounds about right. @timheuer any reason you’re not using DateTimeOffset? Is it size, or legacy API? (I find @mattjohnsonpint answer very useful: stackoverflow.com/questions/4331189/datetime-vs-datetimeoffset)

Excellent question @danmoseley – the real answer is indeed legacy/muscle memory and IntelliSense offering…oh and the fact the API seems clear to me – I want to work with Dates and Times…hmmm, DateTime seems to be a great API and it has everything I need a Now and all sorts of things. It isn’t intuitive to me that DateTimeOffset is a ‘better’ thing than DateTime – that’s my real answer.

As far as the "r" format specifier goes - if we were wanting to make a breaking change for .NET 7, then we could do the following:

DateTime.Now.ToString("r")                           == "Thu, 18 Aug 2022 14:33:59 -0700"
DateTime.UtcNow.ToString("r")                        == "Thu, 18 Aug 2022 21:33:59 GMT"
new DateTime(2022, 8, 18, 21, 33, 59).ToString("r")  == "Thu, 18 Aug 2022 21:33:59 GMT"   // Unspecified == Utc
DateTimeOffset.Now.ToString("r")                     == "Thu, 18 Aug 2022 14:33:59 -0700"
DateTimeOffset.UtcNow.ToString("r")                  == "Thu, 18 Aug 2022 21:33:59 GMT"   // Use GMT for +0000

These would all still be compliant with RFC 1123/822/2822/5322 We’d then have to document that we were making a breaking change to provide better compliance with the spec, but grandfathering in that DateTimeKind.Unspecified is treated as DateTimeKind.Utc.

Note on the last one, I think it’s important to also stay compliant with the HTTP Date Header in RFC 7231 for DateTimeOffset.UtcNow, so we would emit GMT instead of +0000. The trade-off is that there are local times that are also UTC+0 (such as England in the winter, or Iceland year-round) that would see “GMT” instead of their local offset.