The examples for Cache.Add uses DateTime.Now.Add
to compute the expiration, i.e. it passes:
DateTime.Now.AddSeconds(60)
as the value of the absoluteExpiration
parameter.
I'd have thought that computing it relative to DateTime.UtcNow
would be more correct [as there is no ambiguity if Daylight Savings Time starts in the intervening time between now and the expiration point].
Before the introduction of DateTimeKind
, I'd have guessed that there's some ugly hacks in the cache management to make it do something appropriate if the time was not a UTC time.
In .NET 2.0 and later, I'm guessing that it should handle a DateTime
calculated as DateTime.UtcNow.AddSeconds(60)
correctly given that it has DateTime.Kind
to use as an input in its inferences.
I've been confidently using DateTime.UtcNow
as the base for years, but wasnt able to come up with a rationale that this is definitely the correct thing to do in the absence of anything pointing out the documentation has been highly misleading for 4+ years.
The questions?
(Yes, I could peruse the source and/or the Reflector'd source, but am looking for a full blow-by-blow lowdown!)
I reported this bug on Microsoft Connect some time ago, but it's been closed as won't fix.
You still have a problem in .NET 2.0 if you specify your absolute expiration in local time.
During one hour at the end of daylight savings time, your local time is ambiguous, so you can get unexpected results, i.e. the absolute expiration can be one hour longer than expected.
In Europe, daylight savings time ended at 02:00 on 25 October 2009. The sample below illustrates that if you placed an item in the cache at 01:59 with an expiration of 2 minutes, it would remain in the cache for one hour and two minutes.
DateTime startTime = new DateTime(2009, 10, 25, 1, 59,0); DateTime endTime = startTime.AddMinutes(2); // end time is two minutes after start time DateTime startUtcTime = startTime.ToUniversalTime(); DateTime endUtcTime = endTime.ToUniversalTime(); // end UTC time is one hour and two minutes after start UTC time Console.WriteLine("Start UTC time = " + startUtcTime.ToString()); Console.WriteLine("End UTC time = " + endUtcTime.ToString());
The workaround for .NET 2.0 or later is to specify the absolute expiration time in UTC as pointed out by Ruben.
Microsoft should perhaps be recommending to use UTC in the examples for absolute expiration, but I guess there is the potential for confusion since this recommendation is only valid for .NET 2.0 and later.
EDIT
From the comments:
But the exposure only occurs if the conversion happens during the overlap. The single conversion actually taking place is when you lodge the item with Cache.Add
The problem will only happen if you insert an item in the cache with an AbsoluteExpiration time in local time during that one ambiguous hour at the end of daylight savings time.
So for example, if your local time zone is Central European (GMT+1 in winter, GMT+2 in summer), and you execute the following code at 01:59:00 on 25 October 2009:
DateTime absoluteExpiration = DateTime.Now.AddMinutes(2); Cache.Add(... absoluteExpiration ...)
then the item will remain in the cache for one hour and two minutes, rather than the two minutes you would normally expect. This can be a problem for some highly time-critical applications (e.g. stock ticker, airline departures board).
What's happening here is (assuming European time, but the principle is the same for any time zone):
DateTime.Now = 2009-10-25 01:59:00 local. local=GMT+2, so UTC = 2009-10-24 23:59:00
.AddMinutes(2) = 2009-10-25 02:01:00 local. local = GMT+1, so UTC = 2009-11-25 01:01:00
Cache.Add internally converts the expiration time to UTC (2009-11-25 01:01:00) so the expiration is one hour and two minutes ahead of the current UTC time (23:59:00).
If you use DateTime.UtcNow in place of DateTime.Now, the cache expiration will be two minutes (.NET 2.0 or later):
DateTime absoluteExpiration = DateTime.UtcNow.AddMinutes(2); Cache.Add(... absoluteExpiration ...)
From the comments:
Or am I missing something?
No you're not. Your analysis is spot on and if your application is time-critical and runs during that period at the end of DST, you're right to be using DateTime.UtcNow.
The statement in Ruben's answer that:
you're safe to use either as long as the Kind on the time you supply is set
is incorrect.
Cache.Add
converts the expiration date to UTC before storing it.
From Reflector (I've omitted most of the parameters to make it easier to read):
public object Add(... DateTime absoluteExpiration ...) { DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration); return this._cacheInternal.DoInsert(... utcAbsoluteExpiration ...); }
In CacheExpires.FlushExpiredItems
, utcAbsoluteExpiration
is compared to DateTime.UtcNow
. As Joe notes in his answer, this causes unexpected behavior when a cache item's addition and expiration span the end of daylight saving time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With