当前,我们有一种以 TimeZone 感知的方式DateTime
DateTime
我们都在 UTC 中进行处理(例如,使用DateTime.UtcNow
),并且每当我们显示一个 DateTime 时,便从 UTC 转换回去到用户的本地时间。
效果很好,但是我一直在阅读有关DateTimeOffset
,以及它如何捕获对象本身中的本地时间和 UTC 时间。所以问题是,与我们已经在做的事情相比DateTimeOffset
DateTimeOffset
是瞬时时间(也称为绝对时间)的表示。所谓时间,是指每个人都普遍拥有的时间(不考虑 accounting秒或时间膨胀的相对论效应)。表示瞬时时间的另一种方法是使用DateTime
,其中.Kind
是DateTimeKind.Utc
。
这与日历时间(也称为民事时间)不同,后者是某人的日历上的一个位置,并且全球有许多不同的日历。我们称这些日历为时区。日历时间由DateTime
表示,其中.Kind
为DateTimeKind.Unspecified
或DateTimeKind.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 基类库没有类似的表示形式。您将需要存储DateTimeOffset
和TimeZoneInfo.Id
值。
有时,您将希望代表 “whover 在看着它” 本地的日历时间。例如,在定义今天意味着什么时。今天始终是午夜至午夜,但它们代表了瞬时时间轴上几乎无限数量的重叠范围。 (实际上,时区是有限的,但是您可以将偏移量表示为滴答声。)因此,在这些情况下,请确保您了解如何限制 “谁在问谁”?问题降到单个时区,或酌情将其转换回瞬时时间。
以下是有关DateTimeOffset
其他一些内容,可以支持该类比,并提供一些保持其正确性的技巧:
如果比较两个DateTimeOffset
值,则在比较之前首先将它们归一化为零偏移量。换句话说, 2012-01-01T00:00:00+00:00
和2012-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
使用DateTimeKind
为Unspecified
因为您希望它在凌晨 7 点发出而不考虑 DST。但是,如果要表示警报发生的历史记录,则可以使用DateTimeOffset
。
混合使用DateTimeOffset
和DateTime
特别小心,尤其是在类型之间进行分配和比较时。另外,仅比较具有相同DateTimeKind
DateTime
实例,因为DateTime
在比较时会忽略时区偏移量。
DateTime 只能存储两个不同的时间,本地时间和 UTC。种类属性指示哪个。
DateTimeOffset 可以在世界任何地方存储本地时间,从而对此进行了扩展。它还存储该本地时间和 UTC 之间的时间偏移。请注意,除非您将一个额外的成员添加到您的类中以存储该 UTC 偏移量,否则 DateTime 不能做到这一点。或仅与 UTC 一起使用。顺便说一句,这本身就是个好主意。