此文章由机器翻译。

孜孜不倦的程序员

野田佳彦时间

Ted Neward

 

Ted Neward曾经花了很多时间来思考的时间吗?

很早在我的职业生涯,我正在工作的系统上部署到几个不同的呼叫中心告终。 特别重要的是"何时"发生的事件跟踪的 (它是医疗-­相关护士的呼叫中心系统),所以,没有太多想这件事,我们和尽职尽责地写到数据库的行的事件的时间离开它当时。 除外,因为我们发现了以后,当系统被部署到四个不同的电话中心,每个不同的美国 时区,时间日志都有点"off",由于这一事实,我们本来没想到要包括时区偏移量。

在软件系统中的时间是这样 — — 这一切似乎非常简单明了,直到它突然再也不会。

根据我过去的两个列的主题 (可以在找到我的所有列 bit.ly/ghMsco),再一次.NET 社区受益于 Java 社区 ; 所做的工作 在这种情况下,它是一套称为"野田佳彦时间,"Microsoft.NET 框架端口 Java"焦达时间"的项目,其本身被设计为 Java"日期"类 (软件追溯到 Java 1.0 的天的可怕破片) 的更换。 Jon 飞碟,野田佳彦的时间,作者基于算法和中焦达时间的概念,但建立它从地面作为.NET 库。

足够的序言:做"安装软件包 NodaTime"(注意"野田佳彦"和"时间"之间没有空格),并让我们看看一些代码。

'我' 的时间

首先要知道的是直言对爱因斯坦的理论,你不必接近光的速度来实现该时间是相对的。 如果它是 7 下午 (这就是欧洲的朋友浏览 1900年) 在这里在西雅图,然后是 7 下午 对于我们所有人在西雅图,但它是 8 下午 为我的父母在盐湖城,9 下午 在达拉斯和 10 下午我旅行代理 为在波士顿我喝好友。 我们所有获得的 — — 这就是神奇的时区。 但我的电脑并不真的理解时区,本身 — — 它会报告时,它被设置为,在这种情况下即 7 下午,尽管它是确切的同一时间在时间中为我们所有人在世界各地。 换句话说,它不是时间的时间本身是时间的相对的它是时间的我们的表示形式是时间的相对的。 野田佳彦的时间,在此表示形式反映为"全球化"的时间,意味着每个人都同意的通用时间线上的时刻。 我们认为是 「 本地人 」 的时间 — — 也就是说,这次与相关联的时区 — — 野田佳彦次调用"划的时间,"而不是野田佳彦时间认为 「 本地人 」 的时间,这是没有任何附加的时间区域 (更多在后面介绍)。

所以,例如,野田佳彦时间"即时"指扬午夜原点与全球时间轴上上的一个点。 1、 1970 年,协调通用时间 (UTC)。 (没有什么神奇,大概是这一天,只是这是由公约 》"时代的开始"Unix 系统,从那时起,计数"滴答声",从而充当好参考原点作为任何其他。)所以,这样做使我们当前的即时 (假定的野田佳彦时间命名空间引用 — —"使用 NodaTime"— — 当然):

var now = SystemClock.Instance.Now;

若要获取相对于西雅图的时间,我们想要 ZonedDateTime。 这是本质上是即时的但在内的时区信息,这样,它将自身标识为相对于"西雅图,对扬。 9、 2013"(日期不重要,因为我们需要知道是否我们在夏令时 [DST] 或不)。 ZonedDateTime 通过构造函数,并传递即时和日期­时区的时间。 我们已即时,但我们需要 DateTime 区 DST 在西雅图。 若要获取日期时间区域,我们需要 IDateTime zoneProvider。 (这种间接方式的原因是微妙的但就是要与.NET 框架的时区不同于任何其他编程的平台 ; 使用一种表示形式这一事实 互联网分配名称管理局或 IANA,使用像"美国 Los_Angeles"的格式)。野田佳彦时间提供了两个内置提供程序,一种是 IANA 版本和其他标准的.NET 基础类 (BCL) 库版本,通过对 DateTime zoneProviders 类的静态属性:

var seattleTZ = dtzi["America/Vancouver"];
var dtzi = DateTime zoneProviders.Tzdb;

时区数据库 (TZDB) 版本是 IANA 的版本,并因此获得时区表示西雅图是选取它的问题,(,根据 IANA,是"美国/Los_Angeles,"如果你想要的东西更接近,"美国/温哥华"或):

var seattleNow = new ZonedDateTime(now, seattleTZ);

如果我们打印出来,我们得到的一种表示"地方:2013/1/9 7:54:16 下午偏移量:-08 区:美国/温哥华"。注意到"偏移量"部分的表示形式中吗? 它是重要的因为记得哪年的一天 (和什么国家中,你和什么日历您正在操作下,和......) 的基础,从 UTC 的偏移量将会改变。 对于那些我们在西雅图,DST 意味着获得或失去一个小时关闭本地时钟,因此很重要的是要注意从 UTC 的偏移量是什么。 事实上,野田佳彦时间跟踪的另外,因为如分析日期时"2012年-06-26T20:41:00 + 1:00,"例如,我们知道这是早于 utc 时间,一小时,但我们不知道是否这是由于 DST 的特定时区中的时间或不。

仍思考时间是很容易吗?

'我们' 的时间

现在让我们假设我想知道多长时间才在我生命中一个重要的日子 — — 如我 25 的结婚纪念日,这将会扬。 16, 2018. (跟踪的这种事情是有点重要,如认为任何配偶就会告诉你,我需要知道我多久之前,我需要买一件非常昂贵的礼物。这是野田佳彦时间的真正去向出尽风头,因为它会跟踪您的所有隐隐的小细节。

首先,我需要构建检索到的 LocalDateTime (或者,如果我没有在意时间,LocalDate ; 或者,如果我不关心的日期,本地时间)。 检索到的 LocalDateTime (或 LocalDate 或本地时间) 是不知道它到底是什么相对于的时间线上的相对位置 — — 换句话说,它是不知道其时区的时间点。

(尽管不知道时区的时间,这是仍然有用的信息。 它认为,像这样:如果你和我在同一个办公室,在一起工作,并且我们想要满足今天晚些时候,我会说,"我们在 4 下午见面"因为我们俩都在同一时区中,没有附加信息有必要限定这次毫不含糊地向彼此。)

所以,因为我知道点时间,我关心已经,它易于构建:

var twentyFifth = new LocalDate(2018, 1, 16);

因为我只询问有关的两个日期,而不必关心的时区的差异,我只需要检索到的 LocalDateTime 部分的 ZonedDateTime 的 LocalDate 部分从我较早前的计算:

var today = seattleNow.LocalDateTime.Date;

但我们的要求是一种新的时间单位:"期"两个时间之间。 (在 BCL,这是持续时间。野田佳彦时间使用不同的构造来表示这 — — 一段时间 — — 像所有正确表示度量单位的它需要一个单位跟它去。 例如,"47"答案是徒劳无随附的单位,如"47 天,""47 小时"或"47 年"。期之间,提供一个方便的方法,来计算两个 LocalDates 之间某些特定时间单位的数目:

var period = Period.Between(today, twentyFifth, PeriodUnits.Days);
testContextInstance.WriteLine("Only {0} more days to shop!", period.Days);

这会告诉我到底是多少天,但我们通常不像这样计数天在大金额 (在我写这篇文章的时候,1,833)。 我们更喜欢时间,将更易于管理的数据块,如"年、 月、 几天,"我们可以再次询问野田佳彦管理时间。 我们可以问它给我们一段时间,其中包含 or 按年/月/天划分、 PeriodUnits 标志一起:

Period.Between(today, twentyFifth,
  PeriodUnits.Years | PeriodUnits.Months | PeriodUnits.Days)

或者,因为这是一个很普通的要求,我们可以问它给我们一段时间年/月/日击穿包含通过使用相同的名称的标志:

Period.Between(today, twentyFifth, PeriodUnits.YearMonthDay)

(显然,我仍有一点点时间,这很好,因为并不知道给她买什么。

测试时间

经常阅读此专栏的读者会知道我喜欢写勘探测试新的库,在调查时,此列也不例外。 然而,它不可能写测试基于时间,尤其是因为时间上继续这恼人的习惯。 关闭任何预期的结果是,每个经过的毫秒引发和使它硬,如果不是不可能的更要编写测试,将生成可预测的结果,我们可以断言,侵犯行为报告。

出于此原因,任何东西,提供时间 (如,你知道,一个时钟) 实现 IClock 接口,包括我较早前用于获取即时的"现在"SystemClock (现在静态属性)。 如果我们,例如,创建一个实现,实现 IClock 接口,并提供一个常量值回 (唯一成员必需由 IClock 接口,事实上) 现在属性,因为其余的野田佳彦时间库寻­我们效法使用瞬间识别时间在那一刻,我们基本上是创立了完全可测试的环境,使整件事要测试断言和验证。 因此,我可以稍有更改我较早前的代码,并创建一组探索测试,如中所示图 1

图 1 创建探索测试

[TestClass]
public class UnitTest1
{
  // SystemClock.Instance.Now was at 13578106905161124 when I
  // ran it, so let's mock up a clock that returns that moment
  // in time as "Now"
  public class MockClock : IClock
  {
    public Instant Now
    {
      get { return new Instant(13578106905161124); }
    }
  }
  [TestMethod]
  public void TestMethod1()
  {
    IClock clock = new MockClock(); // was SystemClock.Instance;
    var now = clock.Now;
    Assert.AreEqual(13578106905161124, now.Ticks);
    var dtzi = DateTime zoneProviders.Tzdb;
    var seattleTZ = dtzi["America/Vancouver"];
    Assert.AreEqual("America/Vancouver", seattleTZ.Id);
    var seattleNow = new ZonedDateTime(now, seattleTZ);
    Assert.AreEqual(1, seattleNow.Hour);
    Assert.AreEqual(38, seattleNow.Minute);
    var today = seattleNow.LocalDateTime.Date;
    var twentyFifth = new LocalDate(2018, 1, 16);
    var period = Period.Between(today, twentyFifth, PeriodUnits.Days);
    Assert.AreEqual(1832, period.Days);
  }
}

通过所有与时间相关代码并使用野田佳彦的时间,而不内置的.NET 时间类型,代码将成为更多可测试只需通过更换 IClock 用于获得即时的"现在"可控和已知的东西。

但等待......

有更多的野田佳彦时间比只是什么我在这里。 为例,它是相对容易地添加时间单位 (天、 月等) 到给定时间的工作都是通过使用"加号"和"减去"(为其有还运算符重载,如果那些使更多的意义上使用) 的方法,以及一个 FakeClock 类,专门用于测试与时间相关的代码,包括在一些离散的时尚时间以编程方式的"提前"的能力使它更易于测试已用时间敏感的代码 (如 Windows 工作流实例,例如,应该采取行动经过一段时间后没有活动)。

在更深入、 更概念层面上,野田佳彦时间还演示如何在编程语言中的类型系统可以帮助区分略有不同种类的问题域中的值:通过分离出的不同种类的时间 (瞬间、 当地时间、 本地日期、 本地日期和时间,和划的日期和时间,例如) 到离散的、 相互关联的类型,它可以帮助程序员需要清楚明确正是此代码是什么要做或与工作有关。 它可以是特别重要,例如,从某些代码"诞生日"区分"出生日期":一个反映了宇宙的时间轴中的时刻,当一个人出生时,另一种是反复出现的日期,依据我们庆祝的宇宙线中的那一刻。 (实际来说,之一已附加到它上面的一年,其他不会。

飞碟已清楚表明,他并不认为图书馆"完成"以任何方式,和他有计划,以增强和进一步扩展它。 幸运的是,今天,野田佳彦时间是可供使用和开发人员自己欠 NuGet 野田佳彦时间、 看一看,并开始找出如何以及在何处的问题域中使用它。 毕竟,时间很宝贵。

祝您工作愉快!

Ted Neward  是 Neward & Associates LLC 的负责人。他已写一百多篇和创作和合著了十几本书,其中包括"专业 F # 2.0"(Wrox,2010年)。他是 F# 领域最优秀的专家之一和著名的 Java 专家,在全球 Java 和 .NET 会议上演讲。他征求意见,并定期指导 — — 与他联系。 ted@tedneward.com 如果你感兴趣让他来与您的团队一起工作。他的博客网址是 blogs.tedneward.com,您也可以通过 Twitter 地址 twitter.com/tedneward 关注他。

感谢以下技术专家对本文的审阅: Jon 飞碟
Jon 飞碟是谷歌,在伦敦工作的高级软件工程师。 白天他代码在 Java 中,但他的激情是 C#。 你还可以在 Twitter 上的 Jon (@jonskeet) 或只是将问题张贴在堆栈溢出。