DateTime
a和 a 有什么区别,什么DateTimeOffset
时候应该使用?
目前,我们有一种以 TimeZone-aware 方式处理 .NETDateTime
的标准方法:每当我们生成 a时,DateTime
我们都会使用 UTC(例如使用DateTime.UtcNow
),并且每当我们显示时,我们都会从 UTC 转换回用户的本地时间.
这很好用,但我一直在阅读DateTimeOffset
以及它如何在对象本身中捕获本地和 UTC 时间。
DateTime
a和 a 有什么区别,什么DateTimeOffset
时候应该使用?
目前,我们有一种以 TimeZone-aware 方式处理 .NETDateTime
的标准方法:每当我们生成 a时,DateTime
我们都会使用 UTC(例如使用DateTime.UtcNow
),并且每当我们显示时,我们都会从 UTC 转换回用户的本地时间.
这很好用,但我一直在阅读DateTimeOffset
以及它如何在对象本身中捕获本地和 UTC 时间。
DateTimeOffset
是瞬时时间(也称为绝对时间)的表示。我的意思是每个人都通用的时间点(不考虑闰秒,或时间膨胀的相对论效应)。另一种表示瞬时时间的方法是使用DateTime
where .Kind
is DateTimeKind.Utc
。
这与日历时间(也称为民用时间)不同,后者是某人日历上的一个位置,全球有许多不同的日历。我们称这些日历为时区。日历时间由DateTime
where .Kind
isDateTimeKind.Unspecified
或表示DateTimeKind.Local
。并且.Local
仅在您对使用结果的计算机的位置有隐含理解的情况下才有意义。(例如,用户的工作站)
那么,为什么DateTimeOffset
不是 UTCDateTime
呢? 这都是关于视角的。 让我们打个比方——我们会假装自己是摄影师。
想象一下,您站在日历时间线上,将相机对准摆在您面前的瞬时时间线上的一个人。您根据您的时区规则排列您的相机 - 由于夏令时或您的时区法律定义的其他更改,这些规则会定期更改。(你的手不稳,所以你的相机会摇晃。)
站在照片中的人会看到你的相机来自的角度。如果其他人在拍照,他们可能从不同的角度。这就是代表的Offset
部分DateTimeOffset
。
因此,如果您将相机标记为“东部时间”,则有时您从-5 指向,有时您从-4 指向。世界各地都有摄像机,它们都标记了不同的事物,并且都从不同的角度指向同一个瞬时时间线。其中一些彼此相邻(或重叠),因此仅知道偏移量不足以确定时间与哪个时区相关。
那么UTC呢?好吧,这是保证手部稳定的唯一相机。它在三脚架上,牢固地固定在地面上。它不会去任何地方。我们将其视角称为零偏移。
那么 - 这个类比告诉我们什么?它提供了一些直观的指导方针——
如果您特别表示相对于某个地方的时间,请用日历时间表示它DateTime
。请确保您永远不会将一个日历与另一个日历混淆。 Unspecified
应该是你的假设。 Local
只对来自 有用DateTime.Now
。例如,我可能会获取DateTime.Now
它并将其保存在数据库中 - 但是当我检索它时,我必须假设它是Unspecified
. 我不能相信我的本地日历与最初的日历相同。
如果您必须始终确定时刻,请确保您代表的是瞬时时间。用于DateTimeOffset
强制执行,或DateTime
按惯例使用 UTC。
如果您需要跟踪瞬时时间,但您还想知道“用户认为这是他们当地日历上的什么时间?” - 那么你必须使用DateTimeOffset
. 这对于计时系统非常重要,例如,对于技术和法律问题。
如果您需要修改以前记录的DateTimeOffset
- 您在偏移量中没有足够的信息来确保新的偏移量仍然与用户相关。您还必须存储一个时区标识符(想想 - 我需要那个相机的名称,这样即使位置发生了变化,我也可以拍一张新照片)。
还应该指出的是,Noda Time有这样的表示ZonedDateTime
,而 .Net 基类库没有类似的东西。您需要同时存储 aDateTimeOffset
和 aTimeZoneInfo.Id
值。
有时,您会想要表示“查看它的人”的本地日历时间。例如,在定义今天的含义时。今天总是午夜到午夜,但这些代表了瞬时时间线上几乎无限数量的重叠范围。(实际上,我们有有限数量的时区,但您可以将偏移量表达到刻度线)因此,在这些情况下,请确保您了解如何限制“谁在问?” 质疑到单个时区,或酌情将它们转换回瞬时时间。
这里有一些关于DateTimeOffset
支持这个类比的其他一些小细节,以及一些保持它直截了当的技巧:
如果您比较两个DateTimeOffset
值,在比较之前它们首先被归一化为零偏移。换句话说,2012-01-01T00:00:00+00:00
和2012-01-01T02:00:00+02:00
指的是同一瞬间,因此是等价的。
如果您正在进行任何单元测试并且需要确定偏移量,请分别测试值和DateTimeOffset
属性.Offset
。
.Net 框架中内置了一种单向隐式转换,可让您将 a 传递给DateTime
任何DateTimeOffset
参数或变量。这样做时,.Kind
事情。如果您传递 UTC 类型,它将以零偏移量传入,但如果您传递.Local
or .Unspecified
,它将假定为local。该框架基本上是在说,“好吧,你让我将日历时间转换为瞬时时间,但我不知道这是从哪里来的,所以我打算使用本地日历。” DateTime
如果您在具有不同时区的计算机上加载未指定的文件,这将是一个巨大的问题。(恕我直言 - 这应该抛出一个异常 - 但它没有。)
无耻的插头:
许多人与我分享他们发现这个类比非常有价值,因此我将它包含在我的 Pluralsight 课程“日期和时间基础”中。您将在标题为“日历时间与瞬时时间”的剪辑的第二个模块“上下文很重要”中找到相机类比的逐步演练。
来自微软:
DateTimeOffset 值的这些用途比 DateTime 值的用途更常见。因此,应将 DateTimeOffset 视为应用程序开发的默认日期和时间类型。
来源:“在 DateTime、DateTimeOffset、TimeSpan 和 TimeZoneInfo 之间进行选择”,MSDN
DateTimeOffset
当我们的应用程序处理特定的时间点(例如创建/更新记录时)时,我们几乎可以使用所有内容。附带说明一下,我们DATETIMEOFFSET
也在 SQL Server 2008 中使用。
DateTime
当您只想处理日期、时间或一般意义上的处理时,我认为这很有用。例如,如果您有一个闹钟希望每天早上 7 点响起,那么您可以将其存储在DateTime
using a DateTimeKind
of 中,Unspecified
因为您希望它在早上 7 点响起而不管 DST。但是,如果您想表示警报发生的历史记录,您可以使用DateTimeOffset
.
混合使用时要小心DateTimeOffset
,DateTime
尤其是在类型之间分配和比较时。此外,仅比较DateTime
相同的实例,DateTimeKind
因为DateTime
在比较时忽略时区偏移。
DateTime 只能存储两个不同的时间,本地时间和 UTC。Kind属性指示哪个。
DateTimeOffset 通过能够存储来自世界任何地方的本地时间对此进行了扩展。它还存储本地时间和 UTC 之间的偏移量。请注意 DateTime 无法做到这一点,除非您向您的班级添加一个额外的成员来存储该 UTC 偏移量。或者只使用 UTC。顺便说一句,这本身就是一个好主意。
There's a few places where DateTimeOffset
makes sense. One is when you're dealing with recurring events and daylight savings time. Let's say I want to set an alarm to go off at 9am every day. If I use the "store as UTC, display as local time" rule, then the alarm will be going off at a different time when daylight savings time is in effect.
There are probably others, but the above example is actually one that I've run into in the past (this was before the addition of DateTimeOffset
to the BCL - my solution at the time was to explicitly store the time in the local timezone, and save the timezone information along side it: basically what DateTimeOffset
does internally).
最重要的区别是 DateTime 不存储时区信息,而 DateTimeOffset 则存储。
尽管 DateTime 区分 UTC 和 Local,但绝对没有与之关联的明确时区偏移量。如果您进行任何类型的序列化或转换,将使用服务器的时区。即使您通过添加分钟来偏移 UTC 时间来手动创建本地时间,您仍然可以在序列化步骤中获得一些信息,因为(由于 DateTime 中没有任何明确的偏移量)它将使用服务器的时区偏移量。
例如,如果您使用 Json.Net 和 ISO 日期格式序列化具有 Kind=Local 的 DateTime 值,您将获得类似2015-08-05T07:00:00-04
. 请注意,最后一部分(-04)与您的 DateTime 或您用来计算它的任何偏移量无关......它纯粹是服务器的时区偏移量。
同时, DateTimeOffset 明确包含偏移量。它可能不包含时区的名称,但至少包含偏移量,如果您对其进行序列化,您将在值中获得显式包含的偏移量,而不是服务器的本地时间。
Microsoft的这段代码解释了一切:
// Find difference between Date.Now and Date.UtcNow
date1 = DateTime.Now;
date2 = DateTime.UtcNow;
difference = date1 - date2;
Console.WriteLine("{0} - {1} = {2}", date1, date2, difference);
// Find difference between Now and UtcNow using DateTimeOffset
dateOffset1 = DateTimeOffset.Now;
dateOffset2 = DateTimeOffset.UtcNow;
difference = dateOffset1 - dateOffset2;
Console.WriteLine("{0} - {1} = {2}",
dateOffset1, dateOffset2, difference);
// If run in the Pacific Standard time zone on 4/2/2007, the example
// displays the following output to the console:
// 4/2/2007 7:23:57 PM - 4/3/2007 2:23:57 AM = -07:00:00
// 4/2/2007 7:23:57 PM -07:00 - 4/3/2007 2:23:57 AM +00:00 = 00:00:00
TLDR,如果您不想阅读所有这些出色的答案 :-)
显式:
使用DateTimeOffset
是因为时区被强制为 UTC+0。
隐式:
使用DateTime
您希望每个人都遵守时区始终为 UTC+0 的不成文规则的地方。
(开发人员的旁注:显式总是比隐式好!)
(Java 开发人员的旁注,C# DateTimeOffset
== Java OffsetDateTime
,请阅读:https ://www.baeldung.com/java-zoneddatetime-offsetdatetime )
一个主要区别是DateTimeOffset
可以与 结合使用TimeZoneInfo
以转换为当前时区以外的本地时间。
这在由不同时区的用户访问的服务器应用程序(例如 ASP.NET)上很有用。
我看到的 DateTimeOffset 唯一不利的一面是微软“忘记”(按设计)在他们的 XmlSerializer 类中支持它。但它已被添加到 XmlConvert 实用程序类中。
我说继续使用 DateTimeOffset 和 TimeZoneInfo 是因为有所有好处,只是在创建将或可能序列化到 XML 或从 XML 序列化的实体时要小心(然后是所有业务对象)。
DateTime.Now
12 月 21 日星期五 18:40:11
DateTimeOffset.Now
12 月 21 日星期五 18:40:11 +02:00
因此,DateTimeOffset
存储有关时间如何与 UTC 相关的信息,基本上是时区。