Description
Description
System.Net.Cookie
has multiple usages of DateTime.Now
, which should generally be avoided in server-side code because they would be impacted by any DST transitions of the local time zone.
runtime/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs
Lines 198 to 211 in 0c7c10e
runtime/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs
Lines 802 to 812 in 0c7c10e
All of these should be using DateTime.UtcNow
, and should then use ToUniversalTime
, not ToLocalTime
.
This probably has gone unnoticed because it's a best practice for production servers to have their local time zone set to UTC.
Also, cookies are often set for arbitrarily long timeframes - so an hour off in the expiration might also go unnoticed. Still, it's a bug that should be resolved (IMHO).
Reproduction Steps
The following will usually fail on a computer whose local time zone alternates between two offsets, as is common for daylight saving time. For example, Pacific Standard Time (America/Los_Angeles
). It should be good enough to demonstrate. A more robust test would examine the output of TimeZoneInfo.GetAdjustmentRules
to choose a target date that ensures crossing a DST transition.
using System.Diagnostics;
using System.Net;
using System.Reflection;
var sec = 6 * 30 * 24 * 60 * 60; // 15552000 (approx 6 months)
var dt = DateTime.UtcNow.AddSeconds(sec);
var c = new Cookie
{
Name = "foo",
Value = "bar",
Expires = dt
};
// Max-Age is only output in ToServerString, which is non-public.
var str = typeof(Cookie)
.GetMethod("ToServerString", BindingFlags.Instance | BindingFlags.NonPublic)?
.Invoke(c, null)
as string;
// The result could be exact, or could be 1 second off due to delay in code execution.
var expected1 = $"{c}; Max-Age={sec-1}";
var expected2 = $"{c}; Max-Age={sec}";
Debug.Assert(str == expected1 || str == expected2,
"Cookie Max-Age has been affected by a local DST transition.",
"Expected \"{0}\" or \"{1}\", got \"{2}\"",
expected1, expected2, str);
Expected behavior
The assertion should pass, because the Max-Age
value should approximate the amount of time originally added to DateTime.UtcNow
.
Actual behavior
The assertion fails when there's a DST transition in the local time zone between the time of creation and the expiration date. The Max-Age
value is one hour off, because it was generated based on local time, and subtraction of two DateTime
values does not consider time zone transitions.
Regression?
Pretty sure it's been here all along. .NET Framework reference sources show the same code.
Known Workarounds
If the server's local time zone is UTC, or another time zone that does not undergo any transitions, then the problem does not manifest.
Configuration
.NET 8 latest. Not platform-specific.
Other information
No response