40

Cache.Add的示例用于计算DateTime.Now.Add过期时间,即通过:

 DateTime.Now.AddSeconds(60)

作为absoluteExpiration参数的值。

我曾认为相对于它的计算DateTime.UtcNow会更正确[因为如果夏令时开始于现在和到期点之间的中间时间,则没有歧义]。

在引入DateTimeKind.

在 .NET 2.0 及更高版本中,我猜测它应该正确处理DateTime计算,DateTime.UtcNow.AddSeconds(60)因为它必须DateTime.Kind在其推理中用作输入。

多年来,我一直自信地使用DateTime.UtcNow它作为基础,但在没有任何东西指出文档在 4 年多来一直高度误导的情况下,这绝对是正确的做法。

问题?

  1. 尽管有很多狂欢和谷歌搜索,但我无法从 MS 找到任何关于此的权威讨论 - 任何人都可以找到有关此的内容吗?
  2. 为什么使用 UtcNow 不会更正确和/或更安全?

(是的,我可以仔细阅读源和/或 Reflector 的源,但我正在寻找一个完整的逐个打击的内幕!)

4

3 回答 3

25

我前段时间在 Microsoft Connect 上报告了这个错误,但由于无法修复,它已被关闭。

如果您在本地时间指定绝对到期时间,您在 .NET 2.0 中仍然存在问题。

在夏令时结束的一小时内,您的当地时间是不明确的,因此您可能会得到意想不到的结果,即绝对到期时间可能比预期的时间长一小时。

在欧洲,夏令时于 2009 年 10 月 25 日 02:00 结束。下面的示例说明,如果您在 01:59 将一个项目放入缓存中并过期 2 分钟,它将在缓存中保留一小时,然后2分钟。

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());

如 Ruben 所指出的,.NET 2.0 或更高版本的解决方法是在 UTC 中指定绝对过期时间。

Microsoft 可能应该建议在示例中使用 UTC 来表示绝对过期,但我想这可能会造成混淆,因为此建议仅对 .NET 2.0 及更高版本有效。

编辑

从评论:

但是只有在重叠期间发生转换时才会发生曝光。实际发生的单一转换是当您使用 Cache.Add 提交项目时

仅当您在夏令时结束时的一个模棱两可的小时内将具有本地时间的 AbsoluteExpiration 时间的项目插入缓存时,才会发生此问题。

例如,如果您的本地时区是中欧(冬季 GMT+1,夏季 GMT+2),并且您在 2009 年 10 月 25 日 01:59:00 执行以下代码:

DateTime absoluteExpiration = DateTime.Now.AddMinutes(2);
Cache.Add(... absoluteExpiration ...)

那么该项目将在缓存中保留一小时两分钟,而不是您通常期望的两分钟。这对于一些对时间要求非常严格的应用程序(例如股票行情、航空公司离港板)来说可能是个问题。

这里发生的事情是(假设欧洲时间,但任何时区的原则都是相同的):

  • DateTime.Now = 2009-10-25 01:59:00 本地。本地=GMT+2,所以 UTC = 2009-10-24 23:59:00

  • .AddMinutes(2) = 2009-10-25 02:01:00 本地。本地 = GMT+1,所以 UTC = 2009-11-25 01:01:00

  • Cache.Add 在内部将过期时间转换为 UTC (2009-11-25 01:01:00),因此过期时间比当前 UTC 时间 (23:59:00) 提前一小时两分钟。

如果您使用 DateTime.UtcNow 代替 DateTime.Now,则缓存到期时间为两分钟(.NET 2.0 或更高版本):

DateTime absoluteExpiration = DateTime.UtcNow.AddMinutes(2);
Cache.Add(... absoluteExpiration ...)

从评论:

还是我错过了什么?

不你不是。您的分析是正确的,如果您的应用程序对时间要求很高,并且在 DST 结束时的这段时间内运行,那么您使用 DateTime.UtcNow 是正确的。

鲁本回答中的陈述是:

只要设置了您提供的时间的种类,您就可以安全使用

是不正确的。

于 2009-11-06T18:06:50.590 回答
7

Cache.Add在存储到期日期之前将其转换为 UTC。

来自 Reflector(我省略了大部分参数以使其更易于阅读):

public object Add(... DateTime absoluteExpiration ...)
{
    DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
    return this._cacheInternal.DoInsert(... utcAbsoluteExpiration ...);
}

CacheExpires.FlushExpiredItems中,utcAbsoluteExpiration与 相比DateTime.UtcNow。正如乔在他的回答中指出的那样,当缓存项的添加和到期跨越夏令时结束时,这会导致意外行为。

于 2009-11-06T16:41:26.310 回答
3

[从 Jeff Sternal 的回答中高度衍生的见解,我在不完整的付款中 +1 了:D]

似乎在 1.1 中(没有看 1.0,但我认为它相似),在没有 aDateTime.Kind并且发布了具有DateTime.Now-relative 时间的示例的情况下,他们立即感到很舒服立即调用ToUniversalTime()

因此...

  1. DateTime.UtcNow在 1.x 中,如果您 [API 用户] 使用(并且在调用期间开始对 DST 敏感Cache.Add),您最终会一团糟

  2. 在 2.0 [和 3.x] 中,只要Kind设置了您提供的时间 [如果您从DateTime.Now或获得时间,通常是这样],您就可以安全地使用UtcNow。[有关完整理由,请参阅 Joe 的评论和答案] 绝对更喜欢UtcNow1 小时 DST 切换的歧义。

  3. 该示例保持原样,因此反对 1.x 的人不会被误导 [人们可以继续假装 DST 是一个古怪的边缘案例:P]。[同上,正如 Joe 指出的那样] 但这是一个非常值得商榷的立场,因为这可能会导致内容在缓存中多停留一个小时。

我仍然很想听听更多细节,包括来自任何小马的任何细节。

编辑:我从 Joe 的评论中意识到,我没有明确指出如果使用 2.0 或更高版本,使用它肯定更正确,UtcNow因为在 DST '土拨鼠期间,项目可能会被缓存一小时。小时'。我还认为 MS 文档应该指出这个事实(附带条件是他们需要提到这不适用于 1.1 [无论页面是否标记为 2.0+ 特定]。谢谢乔。

编辑:Noda Time 将有一个整洁的包装,使这个万无一失:D

于 2009-11-06T17:00:42.170 回答