我们如何开发旨在防止闰年错误的编码实践
本文关键字:错误 闰年 编码 何开发 开发 我们 | 更新日期: 2023-09-27 18:27:32
微软刚刚宣布,上周在计算日期(跨闰年)时发生的软件错误导致了Windows Azure的重大停机。
在闰年围绕DateTime.Now.AddYears(1)
工作真的是一个简单的判断错误吗?
哪些编码实践可以防止这种情况发生?
编辑正如dcstraw所指出的,闰年的DateTime.Now.AddYears(1)
实际上在.NET中返回了正确的日期。因此,这不是一个框架错误,但显然是日期计算中的一个错误。
无耻插头:
使用更好的日期和时间API
内置的.NET日期和时间库很难正确使用。他们可以让你做你需要的一切,但你不能通过类型系统清楚地表达自己。DateTime
一团糟,DateTimeOffset
可能会让你误以为你实际上在保存时区信息,而事实并非如此,而TimeZoneInfo
不会强迫你思考你应该考虑的一切。
这些都没有提供一种很好的方式来表达"一天中的某个时间"或"某个日期",也没有明确区分"当地时间"answers"特定时区的时间"。如果你想使用公历以外的日历,你需要一直学习Calendar
课程。
所有这些都是我构建Noda Time的原因——一个替代日期和时间库,建立在Joda Time"引擎"的端口上,但上面有一个新的(更精简的)API。
你可能想思考的一些要点,如果你没有意识到的话,很容易错过:
- 将本地日期/时间映射到特定时区并不像你想象的那么简单。由于夏令时转换,特定的本地日期/时间可能出现一次、两次(不明确)或零次(跳过)
- 时区在历史上各不相同——坦率地说,这比
TimeZoneInfo
通常愿意透露的要多。(它不支持"标准时间"随时间变化的时区,也不支持永久夏令时。) - 即使使用zoneinfo数据库,时区ID也不一定稳定。(CLDR解决了这一问题;我希望最终能在《野田时间》中支持这一点。)
- 日期和时间的文本表示是一场噩梦,不仅在排序方面,而且在日期分隔符、时间分隔符和属格月名等奇怪的东西方面也是如此
- 一天的开始并不总是午夜——例如,在巴西,春季夏令时的转换将挂钟从晚上11:59:59移动到凌晨1点
- 在某些情况下(好吧,我知道的一个),时区可能会迫使跳过一整天——2011年12月30日在萨摩亚没有发生!我怀疑大多数开发人员可能会忽略这一点,但是
- 如果你打算使用公历以外的日历,要小心,并确保你真的知道它的表现
就具体的开发实践而言:
- 想想你真正想代表的是什么我预计Noda Time的核心好处是迫使开发人员在各种不同的类型之间进行选择,以表示他们的数据。做好这一点,其他一切都会更简单
- 单元测试你能想到的一切。当然,这将取决于您的系统到底做了什么,但特别是考虑不同的时区,夏令时转换期间会发生什么,当然还有闰年
- 我建议注入一个"类似时钟的接口"——一种告知当前时间的服务——而不是显式调用
DateTime.Now
或DateTime.UtcNow
;它使单元测试变得更容易(可行!) - 如果您使用"now"执行多个操作,请获取该日期/时间一次并记住它,而不是重复请求"now"-否则值可能会在调用之间以不幸的方式更改
- "用UTC做所有事情"也不总是答案——如果我想知道"‘两周后’在我的本地时区到底是什么时候?"那么我需要存储本地日期/时间以及时区
值得注意的是,这个错误可能不是因为你发布的一行:
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
是闰日,则抛出异常。然而,微软关于Azure问题的博客说,他们创建了一个无效的日期2013年2月29日,我不确定这是否可能与.NET.中的DateTime
有关
我并不是说DateTime
和DateTimeOffset
不容易出错,只是我认为它们不会导致这个特定的问题。
我们如何开发旨在防止闰年错误的编码实践?哪些编码实践可以防止这种情况发生?
John提到的单元测试特定日期是一种代码实践,它将有所帮助,但没有什么比我定义的"手动集成测试"更好的了
更改开发/测试台服务器上的时钟,并观察时间流逝时会发生什么
不要纠结于这是否是一种"编码实践"——显然,你不可能对日历上的每个日期都这样做——选择你关心的日期,无论是2月29日、月底还是夏令时转换日期。