我们如何开发旨在防止闰年错误的编码实践?

微软刚刚宣布,计算date(超过闰年)的软件错误导致Windows Azure上周出现重大中断 。

在闰年的DateTime.Now.AddYears(1)周围的判断是否真的是一个简单的错误?

什么编码实践可以阻止这个?

编辑由于dcstraw指出闰年DateTime.Now.AddYears(1)事实上在.NET中返回正确的date。 所以这不是一个框架错误,但显然是date计算中的一个错误。

无耻的插件:

使用更好的date和时间API

内置的.NETdate和时间库非常难以正确使用。 他们确实让你做你所需要的一切,但你不能通过types系统清楚地expression自己。 DateTime是一团糟 , DateTimeOffset可能会让你觉得你实际上是在保留时区信息,而TimeZoneInfo并不强迫你考虑你应该考虑的一切。

这些都没有提供“只是一个时间”或“只是一个约会”的好方法,也没有明确区分“当地时间”和“特定时间的时间”。 如果您想使用除阳历之外的日历,则需要一直检查Calendar类。

所有这一切都是为什么我要build立Noda Time–一个替代date和时间的图书馆,它build立在Joda Time “引擎”的一个端口上,但在顶部有一个新的(更精简的)API。

您可能想要考虑的一些问题,如果您不知道它们,那么这些问题很容易被忽略:

  • 将当地date/时间映射到特定时区中的某个date/时间并不像您想象的那么简单。 由于夏令时转换,特定的本地date/时间可能会发生一次,两次(不明确)或零次(它被跳过)
  • 时区在历史上有所不同 – 比TimeZoneInfo通常愿意坦白地揭示的更多。 (它不支持“标准时间”的概念随时间变化的时区,或者持续夏令时的时区)。
  • 即使使用zoneinfo数据库,时区ID也不一定是稳定的。 (CLDR解决了这个问题,我最终希望在野田时间能够支持这一点。)
  • date和时间的文本表示是一场噩梦,不仅仅是在sorting方面,而且还包括date分隔符,时间分隔符以及诸如所有格的月份名称
  • 一天的开始并不总是午夜 – 例如在巴西,spring夏令时的过渡将挂钟从11:59:59 pm移到凌晨1am
  • 在某些情况下(也就是我所知道的),一个时区可能会迫使整个一天被忽略 – 2011年12月30日在萨摩亚没有发生! 我怀疑大多数开发者可能会忽略这个,但是…
  • 如果您要使用公历以外的其他日历,请小心并确保您确实知道您希望如何行事。

就具体的开发实践而言:

  • 想想你真正想要代表什么。 我期望Noda Time的核心优势是迫使开发人员在各种不同types之间进行select来表示他们的数据。 得到这个权利,其他一切都更简单。
  • unit testing你所能想到的一切。 这当然取决于你的系统的function,但是特别考虑不同的时区,夏令时过渡会发生什么,当然还有闰年。
  • 我build议注入一个“时钟类接口” – 一个告诉当前时间的服务 – 而不是显式调用DateTime.NowDateTime.UtcNow ; 它使得unit testing更容易(可行!)
  • 如果你用“现在”执行多个操作,那么获取一次这个date/时间并记住它,而不是反复地请求“现在” – 否则这个值可能在调用之间以不幸的方式改变。
  • 如果我想知道在我的本地时区出现两周后到底什么时候发生,那么“以UTC做所有事情”并不总是答案。 那么我需要存储当地的date/时间以及时区。

值得注意的是,这个bug可能不是由于你所发布的一行:

 DateTime.Now.AddYears(1) 

这不会创build一个无效的date。 如果你运行:

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

您将获得2013年2月28日。我不知道Azure的访客代理是写入了什么,但它必须是一个不同的调用失败。 在.NET中这样做的一个坏方法是:

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

如果today是闰日,则会引发exception。 但是,有关Azure问题的Microsoft博客表示,他们创build了2013年2月29日的无效date,我不确定是否可以在.NET中使用DateTime

我并不是说DateTimeDateTimeOffset不容易出错,只是我不认为他们会导致这个问题。

我们如何开发旨在防止闰年错误的编码实践? 什么编码实践可以阻止这个?

unit testing约翰提到的具体date是一个代码实践,将有助于但没有什么比我所定义的“手动集成testing”

更改开发/testing平台服务器上的时钟,并观察时间结束后会发生什么。

不要拘泥于具体情况,不pipe这是否是“编码练习” – 显然,在日历上的每一个date都不能这样做 – select你所关心的date,是2月29日,月末date或夏时制转换date。