DateTime与DateTimeOffset

目前,我们有一种以TimeZone感知方式处理.net DateTimes的标准方法:每当我们生成一个 DateTime 时,我们用UTC(例如使用 DateTime.UtcNow )来做,每当我们显示一个时,我们就会从UTC转换回用户的本地时间 .

这工作正常,但我一直在阅读 DateTimeOffset 以及它如何捕获对象本身的本地和UTC时间 . 所以问题是,使用 DateTimeOffset 与我们已经做过的事情有什么好处?

回答(9)

3 years ago

DateTimeOffset 表示瞬时时间(也称为绝对时间) . 通过这个,我的意思是每个人都普遍的时刻(不考虑leap seconds,或time dilation的相对论效应) . 另一种表示瞬时时间的方法是 DateTime ,其中 .KindDateTimeKind.Utc .

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

那么,为什么 DateTimeOffset 而不是UTC DateTimeIt's all about perspective. 让's use an analogy - we'假装成为摄影师 .

想象一下,您正站在日历时间轴上,将摄像机对准在您面前的瞬时时间线上的人 . 您根据时区规则排列相机 - 由于夏令时,或由于您所在时区的法律定义的其他更改而定期更改 . (你没有稳定的手,所以你的相机不稳定 . )

站在照片中的人会看到相机来自的角度 . 如果其他人正在拍照,他们可能是从不同的角度来看 . 这就是 DateTimeOffsetOffset 部分代表的内容 .

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

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

Instantaneous Time vs Calendar Time Visualization

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

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

  • 如果您必须始终确定时刻,请确保您正在表示瞬时时间 . 使用 DateTimeOffset 强制执行,或按惯例使用UTC DateTime .

  • 如果你需要追踪瞬间的瞬间,但你想知道"What time did the user think it was on their local calendar?" - 那么你必须使用 DateTimeOffset . 这对于计时系统非常重要,例如 - 无论是技术问题还是法律问题 .

  • 如果您需要修改以前记录的 DateTimeOffset - 仅在偏移中没有足够的信息以确保新偏移仍然与用户相关 . 您还必须存储时区标识符(想想 - 我需要该摄像机的名称,这样即使位置发生变化,我也可以拍摄新照片) .

还应该指出Noda Time有一个名为 ZonedDateTime 的表示,而.Net基类库没有任何类似的东西 . 您需要存储 DateTimeOffsetTimeZoneInfo.Id 值 .

  • 有时,您需要表示"whomever is looking at it"本地的日历时间 . 例如,在定义今天的意义时 . 今天总是午夜到午夜,但这些代表了瞬时时间轴上几乎无限数量的重叠范围 . (实际上我们有一个有限数量的时区,但是你可以将偏移量表示为刻度线)所以在这些情况下,请确保你理解如何将"who's asking?"问题限制到一个时区,或者处理将它们翻译回来到适当的瞬间时间 .

这里有一些关于 DateTimeOffset 的其他一点点备份这个类比,以及一些保持它的提示:

  • 如果比较两个 DateTimeOffset 值,它们首先被标准化为零偏移量比较 . 换句话说, 2012-01-01T00:00:00+00:002012-01-01T02:00:00+02:00 指的是相同的瞬时时刻,因此是等价的 .

  • 如果您正在进行任何单元测试并且需要确定偏移量,请分别测试 DateTimeOffset 值和 .Offset 属性 .

  • .Net框架内置了一个单向隐式转换,允许您将 DateTime 传递给任何 DateTimeOffset 参数或变量 . 这样做的时候, the .Kind matters . 如果您传递UTC类型,它将携带零偏移,但如果您传递 .Local.Unspecified ,它将假定为 local . 该框架基本上是在说,"Well, you asked me to convert calendar time to instantaneous time, but I have no idea where this came from, so I'm just going to use the local calendar."如果您在具有不同时区的计算机上加载未指定的 DateTime ,这是一个巨大的问题 . (恕我直言 - 这应该抛出异常 - 但事实并非如此 . )

Shameless Plug:

许多人与我分享他们发现这个类比非常有 Value ,所以我把它包含在我的Pluralsight课程中,Date and Time Fundamentals . 您将在 Headers 为"Calendar Time vs. Instantaneous Time"的剪辑中找到第二个模块"Context Matters"中相机类比的逐步演练 .

3 years ago

来自微软:

DateTimeOffset值的这些用法比DateTime值的用法更常见 . 因此,应将DateTimeOffset视为应用程序开发的默认日期和时间类型 .

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

我们使用 DateTimeOffset 几乎所有内容,因为我们的应用程序处理特定时间点(例如,创建/更新记录时) . 另外,我们在SQL Server 2008中也使用 DATETIMEOFFSET .

我认为 DateTime 在您只想处理日期,仅处理时间或处理一般意义上的处理时非常有用 . 例如,如果你有一个警报要在每天早上7点起飞,你可以使用 DateTimeKind Unspecified 将其保存在 DateTime 中,因为你希望它在早上7点关闭,而不管DST . 但是如果要表示报警发生的历史记录,可以使用 DateTimeOffset .

使用 DateTimeOffsetDateTime 的混合时要特别小心,尤其是在分配和比较类型时 . 此外,仅比较 DateTimeKind 相同的 DateTime 实例,因为 DateTime 在比较时忽略时区偏移 .

3 years ago

DateTime只能存储两个不同的时间,即本地时间和UTC . Kind属性表示哪个 .

DateTimeOffset通过能够从世界上任何地方存储本地时间来扩展此功能 . 它还存储本地时间和UTC之间的偏移量 . 请注意DateTime不能这样做,除非您在类中添加一个额外的成员来存储该UTC偏移量 . 或者只使用UTC . 这本身就是一个好主意btw .

3 years ago

有几个地方 DateTimeOffset 有意义 . 一个是当你说我想设置闹钟每天早上9点起飞 . 如果我使用"store as UTC, display as local time"规则,那么当夏令时生效时,闹钟将在不同时间关闭 .

可能还有其他的,但上面的例子实际上是我过去遇到的一个(这是在向BCL添加 DateTimeOffset 之前 - 我当时的解决方案是明确地将时间存储在本地时区,以及保存时区信息:基本上 DateTimeOffset 在内部做什么 .

3 years ago

最重要的区别是DateTime不存储时区信息,而DateTimeOffset则存储时区信息 .

尽管DateTime区分UTC和Local,但绝对没有与之关联的显式时区偏移 . 如果进行任何类型的序列化或转换,将使用服务器的时区 . 即使您通过添加分钟来手动创建本地时间来抵消UTC时间,您仍然可以在序列化步骤中获得位,因为(由于DateTime中没有任何显式偏移),它将使用服务器的时区偏移量 .

例如,如果使用Json.Net和ISO日期格式序列化Kind = Local的DateTime值,您将获得类似 2015-08-05T07:00:00-04 的字符串 . 请注意,最后一部分(-04)与您的DateTime或您用于计算它的任何偏移无关......它是's just purely the server'的时区偏移 .

同时,DateTimeOffset显式包含偏移量 . 它可能不包含时区的名称,但至少它包含偏移量,如果序列化它,您将获得值中显式包含的偏移量,而不是服务器的本地时间 .

3 years ago

大多数答案都很好,但我想添加一些MSDN链接以获取更多信息

3 years ago

一个主要区别是 DateTimeOffset 可以与 TimeZoneInfo 一起使用,以转换为当前时区以外的时区 .

这在服务器上很有用用户在不同时区访问的应用程序(例如ASP.NET) .

3 years ago

我看到DateTimeOffset的唯一不利方面是微软“忘记”(按设计)在XmlSerializer类中支持它 . 但它已被添加到XmlConvert实用程序类中 .

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

我说继续使用DateTimeOffset和TimeZoneInfo因为所有的好处,只要注意创建将要或可能序列化为XML的实体(然后是所有业务对象) .

3 years ago

这段来自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