协慌网

登录 贡献 社区

DateTime vs DateTimeOffset

当前,我们有一种以 TimeZone 感知的方式DateTime DateTime我们都在 UTC 中进行处理(例如,使用DateTime.UtcNow ),并且每当我们显示一个 DateTime 时,便从 UTC 转换回去到用户的本地时间。

效果很好,但是我一直在阅读有关DateTimeOffset ,以及它如何捕获对象本身中的本地时间和 UTC 时间。所以问题是,与我们已经在做的事情相比DateTimeOffset

答案

DateTimeOffset瞬时时间(也称为绝对时间)的表示。所谓时间,是指每个人都普遍拥有的时间(不考虑 accounting或时间膨胀的相对论效应)。表示瞬时时间的另一种方法是使用DateTime ,其中.KindDateTimeKind.Utc

这与日历时间(也称为民事时间)不同,后者是某人的日历上的一个位置,并且全球有许多不同的日历。我们称这些日历为时区。日历时间由DateTime表示,其中.KindDateTimeKind.UnspecifiedDateTimeKind.Local.Local仅在您对使用结果的计算机的位置有隐含了解的情况下才有意义。 (例如,用户的工作站)

那么,为什么要用DateTimeOffset而不是 UTC DateTime呢?一切都与视角有关。让我们用一个比喻 - 我们假装是摄影师。

假设您站在日历时间轴上,将相机对准摆在您面前的即时时间轴上的某个人。您可以根据时区的规则为相机排好队 - 夏令时或其他时区的法律定义会定期更改这些规则。 (您没有稳定的手,因此您的相机不稳定。)

站在照片中的人会看到您的相机所成的角度。如果其他人正在拍照,那么他们可能来自不同的角度。这就是DateTimeOffset Offset部分所代表的内容。

因此,如果将相机标记为 “东部时间”,则有时指向 - 5,有时指向 - 4。世界各地都有摄像机,所有摄像机都标有不同的东西,并且都从不同的角度指向同一瞬时时间轴。它们中的一些彼此相邻(或位于彼此顶部),因此仅知道偏移量不足以确定时间与哪个时区相关。

那么 UTC 呢?好吧,这是一台保证手稳固的相机。它在三脚架上,牢固地固定在地面上。它不会去任何地方。我们称其视角为零偏移。

瞬时时间与日历时间的可视化

那么 - 这个类比告诉我们什么?它提供了一些直观的准则 -

  • 如果要特别表示相对于某个地方的时间,请使用DateTime在日历时间中表示它。只要确保您从未将一个日历与另一个日历混淆即可。 Unspecified应该是您的假设。 Local仅对DateTime.Now有用。例如,我可能会获取DateTime.Now并将其保存在数据库中 - 但是当我检索它时,我必须假定它是Unspecified 。我不能相信我的本地日历与最初使用的日历相同。

  • 如果必须始终确定当前时刻,请确保您代表的是瞬时时间。使用DateTimeOffset强制执行它,或按照约定DateTime

  • 如果您需要跟踪瞬时时间,但又想知道 “用户认为该时间在他们的本地日历上是什么时间?” - 那么您必须使用DateTimeOffset 。例如,这对于计时系统非常重要 - 出于技术和法律方面的考虑。

  • 如果您需要修改以前记录的DateTimeOffset仅在偏移量中没有足够的信息来确保新的偏移量仍与用户相关。您必须存储时区标识符(请考虑 - 我需要该摄像机的名称,以便即使位置发生了更改也可以拍摄新照片)。

    还应该指出的是, Noda Time为此具有一个名为ZonedDateTime的表示形式,而. Net 基类库没有类似的表示形式。您将需要存储DateTimeOffsetTimeZoneInfo.Id值。

  • 有时,您将希望代表 “whover 在看着它” 本地的日历时间。例如,在定义今天意味着什么时。今天始终是午夜至午夜,但它们代表了瞬时时间轴上几乎无限数量的重叠范围。 (实际上,时区是有限的,但是您可以将偏移量表示为滴答声。)因此,在这些情况下,请确保您了解如何限制 “谁在问谁”?问题降到单个时区,或酌情将其转换回瞬时时间。

以下是有关DateTimeOffset其他一些内容,可以支持该类比,并提供一些保持其正确性的技巧:

  • 如果比较两个DateTimeOffset值,则在比较之前首先将它们归一化为零偏移量。换句话说, 2012-01-01T00:00:00+00:002012-01-01T02:00:00+02:00指的是相同的瞬时时刻,因此是等效的。

  • 如果你正在做的任何单元测试,需要一定的偏移量,同时测试的DateTimeOffset值, .Offset分别财产。

  • .Net 框架内置了一种单向隐式转换,使您可以将DateTime传递到任何DateTimeOffset参数或变量中。这样做时, .Kind重要。如果你传递一个 UTC 一种,它将搭载与零偏移,但如果你通过其中.Local.Unspecified ,就会认为是本地的。该框架基本上是在说:“好吧,您让我将日历时间转换为瞬时时间,但是我不知道这是从哪里来的,所以我将使用本地日历。” 如果在具有不同时区的计算机上DateTime ,这将是一个巨大的难题。 (恕我直言 - 这应该引发异常 - 但事实并非如此。)

无耻的插头:

许多人与我分享了他们发现这种类比非常有价值的信息,因此我将其包含在我的 Pluralsight 课程 “日期和时间基础知识” 中。您可以在标题为 “日历时间与瞬时时间” 的剪辑的第二个模块 “上下文问题” 中找到有关摄像机类比的逐步介绍。

从 Microsoft:

这些用于 DateTimeOffset 值的用途比用于 DateTime 值的用途要普遍得多。因此,应将 DateTimeOffset 视为应用程序开发的默认日期和时间类型。

来源: “在 DateTime,DateTimeOffset,TimeSpan 和 TimeZoneInfo 之间选择”MSDN

由于应用程序处理特定的时间点(例如,创建 / 更新记录的时间),因此几乎所有内容都使用DateTimeOffset附带说明一下,我们也在 SQL Server 2008 中DATETIMEOFFSET

我认为DateTime在您只想处理日期,只处理时间或处理一般意义上的一种时很有用。例如,如果您有一个警报希望每天早上 7 点发出,则可以将它存储在DateTime使用DateTimeKindUnspecified因为您希望它在凌晨 7 点发出而不考虑 DST。但是,如果要表示警报发生的历史记录,则可以使用DateTimeOffset

混合使用DateTimeOffsetDateTime特别小心,尤其是在类型之间进行分配和比较时。另外,仅比较具有相同DateTimeKind DateTime实例,因为DateTime在比较时会忽略时区偏移量。

DateTime 只能存储两个不同的时间,本地时间和 UTC。种类属性指示哪个。

DateTimeOffset 可以在世界任何地方存储本地时间,从而对此进行了扩展。它还存储该本地时间和 UTC 之间的时间偏移。请注意,除非您将一个额外的成员添加到您的类中以存储该 UTC 偏移量,否则 DateTime 不能做到这一点。或仅与 UTC 一起使用。顺便说一句,这本身就是个好主意。