回归基础:使用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: https://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分钟的时间。(嗯,我知道我得修复它。)
“我的代码没有缺陷,它是根据编写内容运行的。”——某位著名程序员
明年再见吧。