dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.06k stars 4.69k forks source link

DateTime.ParseExact with "r" specifier doesn't set Kind to Utc - why? #96789

Closed IanKemp closed 8 months ago

IanKemp commented 8 months ago

Documentation states:

The "R" or "r" standard format specifier represents a custom date and time format string that's defined by the DateTimeFormatInfo.RFC1123Pattern property. The pattern reflects a defined standard, and the property is read-only. Therefore, it is always the same, regardless of the culture used or the format provider supplied. The custom format string is "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'".

In other words, this is to be interpreted as an absolute date-time, not a relative one - identically to how formatting and parsing a date in the ISO 8601 format via o works. And yet:

> DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind).Kind
Unspecified

Arguably, the above snippet from the C# console should yield Utc. But because it does not, all sorts of weird and wonderful things happen if you're not aware of this idiosyncrasy. For example, since Unspecified takes timezone information into account when formatting, the below yields an incorrect value in my region (UK):

> DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind).ToUniversalTime()
[03/05/2018 15:00:00]

Of course, it's simple to fix things up via DateTime.SpecifyKind:

> DateTime.SpecifyKind(DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind), DateTimeKind.Utc)
[03/05/2018 16:00:00]

but again, you don't need to do this with o, so I'm puzzled why it's necessary with r. Bug, oversight, deliberate decision, or me missing the point by a large margin?

Also, why is it called RFC1123Pattern when RFC 1123 allows timezone offsets in the string? It's RFC 2616 that defines the pattern "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'" as one of only three date-time format strings accepted.

ghost commented 8 months ago

Tagging subscribers to this area: @dotnet/area-system-datetime See info in area-owners.md if you want to be subscribed.

Issue Details
[Documentation states:](https://learn.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings#RFC1123) > The "R" or "r" standard format specifier represents a custom date and time format string that's defined by the [DateTimeFormatInfo.RFC1123Pattern](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern) property. The pattern reflects a defined standard, and the property is read-only. Therefore, it is always the same, regardless of the culture used or the format provider supplied. The custom format string is "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'". In other words, this is to be interpreted as an absolute date-time, not a relative one - identically to how formatting and parsing a date in the ISO 8601 format via `o` works. And yet: ``` > DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind).Kind Unspecified ``` Arguably, the above snippet from the C# console should yield `Utc`. But because it does not, all sorts of weird and wonderful things happen if you're not aware of this idiosyncrasy. For example, since `Unspecified` takes timezone information into account when formatting, the below yields an incorrect value in my region (UK): ``` > DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind).ToUniversalTime() [03/05/2018 15:00:00] ``` Of course, it's simple to fix things up via `DateTime.SpecifyKind`: ``` > DateTime.SpecifyKind(DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, System.Globalization.DateTimeStyles.RoundtripKind), DateTimeKind.Utc) [03/05/2018 16:00:00] ``` but again, you don't need to do this with `o`, so I'm puzzled why it's necessary with `r`. Bug, oversight, deliberate decision, or me missing the point by a large margin? Also, why is it called `RFC1123Pattern` when RFC 1123 allows timezone offsets in the string? It's RFC 2616 that defines the pattern `"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"` as one of only three date-time format strings accepted.
Author: IanKemp
Assignees: -
Labels: `area-System.DateTime`
Milestone: -
Clockwork-Muse commented 8 months ago

The pattern applies to

Product Versions
.NET Framework 1.1, 2.0, 3.0, 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1

DateTimeKind applies to

Product Versions
.NET Framework 2.0, 3.0, 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1

DateTimeKind didn't exist when the pattern was created.
Welcome to backward compatibility concerns.

For a large number of DateTime(Offset)/DateTimeKind-related questions the answer is "it didn't exist yet" (and introducing it didn't necessarily solve those or related problems, Unspecified especially causes problems).

tarekgh commented 8 months ago

@Clockwork-Muse is correct. This was the behavior since day 1 and not changing it because of the compatibility reason. We can watch if more users run into problems, we may consider do the breaking change. But it is easy to work around the issue by just using DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal instead of DateTimeStyles.RoundtripKind something like:

DateTime.ParseExact("Thu, 03 May 2018 16:00:00 GMT", "r", null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal).Kind
IanKemp commented 8 months ago

Thanks for the expanations!