回归基础:使用SetLastModified时,夏令时缺陷再现


原文发表地址: Back to Basics: Daylight Savings Time bugs strike again with SetLastModified

原文发表时间: 2011-11-06 07:14 AM

无论你对某个话题或者代码库有多熟悉,迟早会被某个最新的但可能是存在了至少五年的缺陷所困扰。

DasBlog,驱动这篇博客的ASP.NET2博客引擎已经完了。不是说它不行了,而是它精疲力尽了,而且非常稳定。我们去年有过承诺,我承诺在二月前完成缺陷修复,不过尽管如此我们还是得到了很多的理解。我的博客几年来都没有受到障碍影响,因为DasBlog在单一机器上表现还是不错的。

那是太平洋夏令时的下午10:51,我正在写一篇关于我家的时钟的博客,介于太平洋标准时间很快就要切换过去。我在Windows Live Writer中编写,把它放到博客上,然后点击Hanselman.com来查看。

跳出404.

什么?404?烦人。刷新。

404

*内心动态*我被黑客攻击了?发生什么了?看看日志!

   1: l2 time 
   2: 2011-11-06T05:36:31 code 1 
   3: message Error:System.ArgumentOutOfRangeException: Specified 
   4: argument was out of the range of valid values.
   5: Parameter name: utcDate
   6: at 
   7: System.Web.HttpCacnhePolicy.UtcSetLastModified(DateTime utcDate)
   8: at 
   9: System.Web.HttpCachePolicy.SetLastModified(DateTime date)
  10: at 
  11: newtelligence.DasBlog.Web.Core.SiteUtilities.GetStatusNotModified(DateTime 
  12: latest) in 
  13: C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SiteUtilities.cs:line 
  14: 1253
  15: at 
  16: newtelligence.DasBlog.Web.Core.SharedBasePage.NotModified(EntryCollection 
  17: entryCollection) in 
  18: C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SharedBasePage.cs:line 
  19: 1182
  20: at 
  21: newtelligence.DasBlog.Web.Core.SharedBasePage.Page_Load(Object sender, EventArgs 
  22: e) in 
  23: C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SharedBasePage.cs:line 
  24: 1213
  25: at System.EventHandler.Invoke(Object sender, 
  26: EventArgs e)
  27: at System.Web.UI.Control.OnLoad(EventArgs e)
  28: at System.Web.UI.Control.LoadRecursive()
  29: at System.Web.UI.Page.ProcessRequestMain(Boolean 
  30: includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) 
  31: while processing 
  32: http://www.hanselman.com/blog/default.aspx.

发生什么了?超出范围了?什么超出范围了。好吧,我的站点第一次发生崩溃。看来我不得不搞砸那篇时钟的博文了,好吧,我删除它,好了,删除了。

刷新。

404

什么?!?

查看日志,显示的是同样的错误,现在文件变成了meg格式,而且内容更加多了,因为这个信息每分钟都会重复几百次。好吧,看看代码!

UtcSetLastModified是用来设置特定缓存的HTTP标头,及控制ASP.NET页面输出缓存的。通过它我可以让HTTP知道从某一时刻起什么东西没做过修改。我有一个应用程序可以了解哪篇博文被最近修改过,或者哪篇博文的评论最近被修改过,然后我告诉主页和浏览器,这样谁都可以知道有没有更新内容了。

   1: public DateTime 
   2: GetLatestModifedEntryDateTime(IBlogDataService dataService, EntryCollection 
   3: entries)
   4: {
   5: //figure out if send a 304 Not Modified or 
   6: not...
   7: return latest //the lastTime anything 
   8: interesting happened.
   9: }

在基础页我们会自问,我们在工作时能避免获取304错误信息吗?

   1: //Can we get away with an "if-not-modified" header?
   2: if 
   3: (SiteUtilities.GetStatusNotModified(SiteUtilities.GetLatestModifedEntryDateTime(dataService, 
   4: entryCollection)))
   5: {
   6: //snip
   7: }

不过,注意我还是得调用SetLastModified。看来UtcSetLastModified是私有的。(为什么?)当我调用SetLastModified的时候,就会看到这个:

   1: public void SetLastModified(DateTime 
   2: date)
   3: {
   4: DateTime utcDate = 
   5: DateTimeUtil.ConvertToUniversalTime(date);
   6: this.UtcSetLastModified(utcDate);
   7: }

好吧。那就意味着我得按照本地时间来工作。我检索日期,转换成ToLocalTime()。

看到这里,你可能会说,哦,我知道了,它调用了ToLocalTime()很多次,转换了两次时间。那就是我所想的。不过,在.NET 2 之后这就有可能了。转换返回的值是DateTime,它的Kind属性总是本地的。所以,即使ToLocalTime在同一个DateTime中反复应用,都会返回有效的结果了。

但是,我们最初是在.NET 1.1 中写的DasBlog,又 在几年后把它移到了.NET2上。我怀疑我在使用我们(Clemens Vasters和我的)时区里的错误行为和存在潜在错误行为的数据访问代码,(过度转换DateTimes到本地时间),现在这些都不会再发生了。已经4年没发生这样的状况了。

希望你们可以看到结果。

格林威治时间上午5:36或者夏令时下午10:36 的时候,即东部时间上午1:36,好像有个评论。那是最新修改时间。我们在转换中添加了一个小时,因为夏令时与太平洋标准时不一致,但是东部夏令时与东部时间是一致的。

你的大脑已经快爆炸了?讨厌夏令时?没错,我也很讨厌它。

不管怎样,DateTime变成了东部时间上午2:36,而不是上午1:36。问题是,东部时间上午2:36,也就是格林威治时间6:46,是未来时间,而这个时刻还没有到来。

松散了5年的缺陷每年都会发生一小时,它一直在那里,就好像长达10年的framework代码7年前才修复。有夏令时的单元测试了么?我还没有。

我的服务器正处于未来的时间,但实际上并没有未来那么远。我的服务器在东海岸,显示为上午1:51。不过,有时候我的博文看上去像是来自未来时间,是因为我把任何东西都储存在UTC/GMT区中,所以在我的文件系统中显示的是下一天的上午5:51。

这个故事的主旨?

我要确认我的服务器是在GMT时间,我所储存的代码不受夏令时影响。

你也可以有不同的解释,不要使用DateTime.Now来进行任何日期计算或储存,请用DateTime.UTCNow,要记得如果你把它们设为未来时间,有些方法会错乱。不要再本地时间上做任何事情,直到你把DateTime显示给用户。

在我的例子中,在调试的9分钟里,它自己解决了。未来变成了现在,未来最新修改DateTime变得有效了。这里存在缺陷吗?当然有,至少,每年有一个小时是有缺陷的,。现在实实在在的问题就是,我需要修复它或者打破一年里其他8759个小时的东西吗?那样就是4个9分钟的时间。(嗯,我知道我得修复它。)

“我的代码没有缺陷,它是根据编写内容运行的。”——某位著名程序员

明年再见吧。


Comments (0)

Skip to main content