我们如何开发旨在防止leap年错误的编码实践?[关闭]


80

微软刚刚宣布,计算日期(超过leap年)的软件错误导致上周Windows Azure发生了严重故障

在judgment年期间进行判断时,这真的是一个简单的错误DateTime.Now.AddYears(1)吗?

哪些编码惯例可以阻止这种情况?

编辑 dcstraw指出,DateTime.Now.AddYears(1)on年实际上确实会在.NET中返回正确的日期。因此,这不是框架错误,但显然是日期计算中的错误。


7
由于没有“一个”答案,而这更多的是讨论中的问题,我认为应该将其迁移给程序员。另外:始终对您的代码进行单元测试。总是。
乔治·斯托克2012年

2
DateTime问题是一个棘手且复杂的问题。可能有人会说类似UTC的所有操作,但是总会有一些翻译点……当发生这种情况时,您必须考虑到的排列数会避免所有错误。我们大多数人永远都不会在需要维护的系统上工作。
2012年

9
我认为这个问题太有趣了
史蒂文(Steven)

11
要关闭四张选票?我希望这个脾气暴躁,引发快乐的亲密接触只是SO演进的一个阶段。一切都比你还神圣。
伊恩·霍尔德

7
@IainMH必须同意您的意见,尤其是因为几乎所有关闭它的人都从未以超过几票的票数问过一个问题。
劳埃德

Answers:


95

无耻的插头:

使用更好的日期和时间API

内置的.NET日期和时间库很难正确使用。它们确实可以让您做所需的一切,但是您无法通过类型系统清楚地表达自己。真是DateTime一团糟DateTimeOffset可能会让您误以为自己实际上是在保留时区信息,并且TimeZoneInfo不会强迫您考虑所有应考虑的事项。

这些都不提供说“一天中的某个时间”或“只是一个日期”的好方法,也没有在“本地时间”和“特定时区的时间”之间做出明确区分。而且,如果您要使用公历以外的日历,则需要一直进行该Calendar课程。

所有这些都是为什么我要构建Noda Time的原因-替代的日期和时间库建立在Joda Time “引擎”的端口上,但顶部具有新的(更精简的)API。

您可能要考虑的一些要点,如果您不了解它们,就很容易错过:

  • 将本地日期/时间映射到特定时区中的日期/时间并不像您想的那么简单。由于夏令时转换,特定的本地日期/时间可能会发生一次,两次(歧义)或零次(被跳过)。
  • 时区在历史上有所不同-TimeZoneInfo坦率地说,时区比一般人更愿意透露。(它不支持“标准时间”的概念随时间而变化的时区,或者不属于永久性的夏令时。)
  • 即使使用zoneinfo数据库,时区ID也不一定稳定。(CLDR解决了这个问题;我最终希望在Noda Time中提供支持。)
  • 日期和时间的文本表示是一场噩梦,不仅是顺序上的问题,而且还包括日期分隔符,时间分隔符以及诸如通用月份名称之类的奇怪内容
  • 一天的开始并不总是午夜-例如在巴西,春季的夏时制转换将挂钟从晚上11:59:59移至凌晨1点
  • 在某些情况下(嗯,据我所知),时区可能会导致整天被跳过-萨摩亚没有发生2011年12月30日的事件!我怀疑大多数开发人员可能会忽略这一点,但是...
  • 如果要使用公历以外的日历,请小心并确保您真正了解日历的行为。

就具体的开发实践而言:

  • 考虑一下您真正要代表的内容。我希望Noda Time的核心优势是迫使开发人员在各种不同类型之间进行选择以表示其数据。做到这一点,其他一切都变得更加简单。
  • 单元测试您能想到的一切。当然,这完全取决于系统的工作,但尤其要考虑不同的时区,在夏时制转换中发生的事情以及course年。
  • 我建议注入“类似于时钟的接口”(一种用于告知当前时间的服务),而不要显式调用DateTime.Nowor DateTime.UtcNow;它使单元测试更加容易(可行!)
  • 如果您要使用“现在”执行多个操作,请一次获取该日期/时间并记住该日期/时间,而不是重复请求“现在”-否则,值可能会在调用之间以不幸的方式更改。
  • “我想知道“ UTC的所有时间”何时确切在本地时区发生吗?那么我需要存储本地日期/时间以及时区。

2
@flq:同样,您需要准确定义“ le年安全”的含义。我怀疑是导致Azure出现问题的框架错误-我希望框架使用率不高。
乔恩·斯基特

10
关于leap年的主题(即主题),没有广告的十个要点。然后在Twitter上吹气。我想你过得更好,乔恩。
Will Dean

8
@WillDean:还要注意,这个问题是由乔治·斯托克(George Stocker)编辑的。原标题是“对的DateTime错误防御性编程” -在这种情况下,我想你会同意我的职务完全相关的。(我只是注意到标题已更改。我开始回答时说的是DateTime ...)
Jon Skeet 2012年

2
琼恩(Jon),对不起,我没有看过先前的标题!也许有人也应该改变标题来编辑所有答案...
Dean Will Dean

3
@WillDean:是的...我很想回退标题更改或以“ DateTime错误(例如leap年)”结尾
Jon Skeet 2012年

25

值得注意的是,该错误可能不是由于您发布的一行引起的:

DateTime.Now.AddYears(1)

那不会创建一个无效的日期。如果您运行:

(new DateTime(2012, 2, 29)).AddYears(1)

您会在2013年2月28日得到通知。我不知道Azure的来宾代理编写的内容,但一定是另一个失败的呼叫。在.NET中执行此操作的不良方法是:

new DateTime(today.Year + 1, today.Month, today.Day)

如果today是leap日,那将引发例外。但是,有关Azure问题的Microsoft博客说,他们创建了一个无效的日期2013年2月29日,我不确定DateTime在.NET中是否可以这样做。

我不是说DateTimeDateTimeOffset不容易出错,只是我不认为他们会造成这一具体问题。


我猜想它是用C ++完成的
PhilPursglove 2012年

2
@PhilPursglove:是什么让你觉得呢?Windows操作系统主要是用C和C ++编写的,可以正确处理leap日。可能与转移证书创建过程中与日期相关的失败有关。
电脑

可以从字符串中解析日期吗?
Fixer 2012年

是的,正如您所指出的,您对AddYears(1)是正确的。我不确定内部发生了什么。只是想让问题有一个新的视角,而不必太深入探讨该博客文章。
修复者2012年

这很有道理。老实说,我不知道是否是这种情况,但这是可信的。我看过包括MS在内的大公司的讨厌代码。但是,再次,我们真的不能做很多事情,只是在这一点上进行推测。
Alpha

2

我们如何开发旨在防止leap年错误的编码实践?哪些编码惯例可以阻止这种情况?

正如John所提到的,对特定日期进行单元测试是一种代码实践,可以为您提供帮助,但是没有什么比我定义的“手动集成测试”更好

更改开发/测试平台服务器上的时钟,并观察时间流逝时会发生什么。

不要为这是否是“编码实践”而陷入困境-显然,您不能为日历上的每个日期执行此操作-选择您要关注的日期,例如月底2月29日日期或夏令时转换日期。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.