【问题标题】:DateTime vs DateTimeOffset日期时间与日期时间偏移
【发布时间】:2011-05-18 21:17:52
【问题描述】:

目前,我们有一种处理 .NET DateTime 时区感知的标准方法:每当我们生成 DateTime 时,我们都会在 UTC 中执行(例如使用 DateTime.UtcNow),并且每当我们显示一,我们从UTC转换回用户的本地时间。

这很好用,但我一直在阅读有关 DateTimeOffset 以及它如何在对象本身中捕获本地和 UTC 时间的信息。所以问题是,与我们已经在做的事情相比,使用DateTimeOffset 的优势是什么?

【问题讨论】:

标签: c# .net datetime timezone datetimeoffset


【解决方案1】:

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

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

那么,为什么是DateTimeOffset 而不是UTC DateTime这都是关于视角的。让我们打个比方 - 我们会假装自己是摄影师。

想象一下,您站在日历时间轴上,将相机对准摆在您面前的瞬时时间轴上的一个人。您根据您的时区规则排列您的相机 - 由于夏令时或您的时区法律定义的其他更改,这些规则会定期更改。 (你的手不稳,所以你的相机会摇晃。)

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

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

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

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

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

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

  • 如果您需要跟踪瞬时时间,但您还想知道“用户认为这是他们当地日历上的什么时间?” - 那么你必须使用DateTimeOffset。这对于计时系统非常重要,例如技术和法律问题。

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

    还应该指出,Noda Time 对此有一个称为ZonedDateTime 的表示,而.Net 基类库没有类似的东西。您需要同时存储 DateTimeOffsetTimeZoneInfo.Id 值。

  • 有时,您会想要表示“查看它的人”的本地日历时间。例如,在定义 today 的含义时。今天总是午夜到午夜,但这些代表了瞬时时间线上几乎无限数量的重叠范围。 (实际上,我们有有限数量的时区,但您可以将偏移量表达到滴答声)因此,在这些情况下,请确保您了解如何限制“谁在问?”质疑到单个时区,或酌情将它们转换回瞬时时间。

这里有一些关于DateTimeOffset 的其他小细节可以支持这个类比,以及一些保持直截了当的提示:

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

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

  • .Net 框架中内置了一种单向隐式转换,可让您将DateTime 传递给任何DateTimeOffset 参数或变量。这样做时,.Kind 很重要。如果您传递 UTC 类型,它将以零偏移量传入,但如果您传递 .Local.Unspecified,它将假定为 local。该框架基本上是在说,“好吧,你让我将日历时间转换为瞬时时间,但我不知道这是从哪里来的,所以我打算使用本地日历。”如果您在具有不同时区的计算机上加载未指定的DateTime,这是一个巨大的问题。 (恕我直言 - 这应该引发异常 - 但它不会。)

无耻的插头:

许多人与我分享他们发现这个类比非常有价值,所以我将它包含在我的 Pluralsight 课程中,Date and Time Fundamentals。您将在标题为“日历时间与瞬时时间”的剪辑的第二个模块“上下文很重要”中找到相机类比的分步演练。

【讨论】:

  • @ZackJannsen 如果你在 C# 中有一个 DateTimeOffset,那么你应该将它持久化到 SQL Server 中的一个 DATETIMEOFFSETDATETIME2 或只是 DATETIME(取决于所需的范围)适用于常规 DateTime 值。是的 - 您可以从任何一对时区 + dto 或 utc 解析本地时间。不同之处在于——你总是想为每个解析计算规则,还是想预先计算它们?在许多情况下(有时出于法律考虑),DTO 是更好的选择。
  • @ZackJannsen 对于您问题的第二部分,我建议尽可能多地在服务器端进行。 Javascript 对于时区计算并不是那么好。如果必须这样做,请使用these libraries 之一。但是服务器端是最好的。如果您还有其他更详细的问题,请开始新的 S.O.向他们提出问题,如果可以,我会回答。谢谢。
  • @JoaoLeme - 这取决于你从哪里获得它。你是对的,如果你在服务器上说DateTimeOffset.Now,你确实会得到服务器的偏移量。关键是 DateTimeOffset 类型可以保留该偏移量。您可以在客户端上轻松执行此操作,将其发送到服务器,然后您的服务器将知道客户端的偏移量。
  • 是的,没错。除了 DTO 存储为 (local time, offset) 对,而不是 (utc time, offset) 对。换句话说,与 UTC 的偏移量已经反映在本地时间中。要转换回 UTC,请反转偏移量的符号并将其应用于本地时间。
  • 这是一个非常棒的答案,对令人困惑的 DateTime 库构造函数和方法确实有意义。
【解决方案2】:

来自微软:

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

来源:"Choosing Between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo"MSDN

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

我认为DateTime 在您只想处理日期、时间或一般意义上的处理时很有用。例如,如果您有一个想要在每天早上 7 点响起的闹钟,您可以使用 DateTimeKindUnspecified 将其存储在 DateTime 中,因为您希望它在早上 7 点响起而不管 DST .但是如果你想表示警报发生的历史,你会使用DateTimeOffset

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

【讨论】:

  • 我只想说我也喜欢这个答案,并投了赞成票。尽管在最后一部分 - 即使确保 Kind 相同,但比较可能会出错。如果双方都有DateTimeKind.Unspecified,你真的不知道他们来自同一个时区。如果双方都是DateTimeKind.Local大多数比较会很好,但你仍然可能有错误,因为一方在本地时区不明确。真的只有DateTimeKind.Utc 比较是万无一失的,是的,DateTimeOffset 通常是首选。 (干杯!)
  • +1 我要补充一点:您选择的 DataType 应该反映您的意图。不要在任何地方使用 DateTimeOffset,只是因为。如果偏移量对您的计算和从/持久到数据库的读取很重要,则使用 DateTimeOffset。如果没关系,请使用 DateTime,这样您就可以了解(仅通过查看 DataType)Offset 不应该有任何影响,并且 Times 应该保持相对于您的 C# 代码正在运行的服务器/机器的位置。
【解决方案3】:

DateTime 只能存储两个不同的时间,即本地时间和 UTC。 Kind 属性指明是哪一种。

DateTimeOffset 对此进行了扩展,能够存储来自世界任何地方的当地时间。它还存储本地时间和 UTC 之间的偏移量。请注意 DateTime 无法做到这一点,除非您在类中添加一个额外的成员来存储该 UTC 偏移量。或者只使用 UTC。顺便说一句,这本身就是一个好主意。

【讨论】:

    【解决方案4】:

    DateTimeOffset 在某些地方是有意义的。一种是当您处理重复事件和夏令时。假设我想设置一个每天早上 9 点响起的闹钟。如果我使用“存储为 UTC,显示为本地时间”规则,那么当夏令时生效时,闹钟将在 不同的时间响起。

    可能还有其他的,但上面的例子实际上是我过去遇到的一个(这是在将DateTimeOffset 添加到 BCL 之前 - 我当时的解决方案是明确地将时间存储在本地时区,并将时区信息保存在旁边:基本上是 DateTimeOffset 在内部执行的操作)。

    【讨论】:

    • DateTimeOffset 不能解决 DST 问题
    • 使用 TimeZoneInfo 类确实带有 DST 规则。如果您使用的是 .net 3.5 或更高版本,则使用 TimeZone 或 TimeZoneInfo 类来处理必须结合时区偏移处理夏令时的日期。
    • 是一个很好的例外示例(警报应用程序),但是当时间比日期更重要时,您应该将其真正存储在应用程序的计划数据结构中,即发生类型 = 每天和时间 = 09:00。这里的重点是开发人员需要了解他们正在记录、计算或呈现给用户的日期类型。尤其是应用程序往往更加全球化,现在我们将互联网作为标准和大型应用程序商店来编写软件。作为一个侧节点,我还希望看到微软添加一个单独的日期和时间结构。
    • Jarrett 和 Zack 的 cmets 总结:听起来 DateTimeOffset alone 不会处理 DST 问题,但结合使用 DateTimeOffset 和 TimeZoneInfo 会处理它。这与类型为 Utc 的 DateTime 没有什么不同。在这两种情况下,我都必须知道我将时间投影到的日历的时区(不仅仅是偏移量)。 (如果可能,我可能会将其存储在用户的配置文件中或从客户端(例如 Windows)获取)。听起来对吗?
    • "在某些地方 DateTimeOffset 是有意义的。" --- 可以说,这通常是有意义的。
    【解决方案5】:

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

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

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

    同时,DateTimeOffset 明确包含偏移量。它可能不包含时区的名称,但至少包含偏移量,如果您对其进行序列化,您将在值中获得明确包含的偏移量,而不是服务器的本地时间。

    【讨论】:

    • 有了以上所有答案,我想知道为什么没有人费心写你的一句话来总结一切The most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
    • DateTimeOffset 不存储时区信息。题为“在 DateTime、DateTimeOffset、TimeSpan 和 TimeZoneInfo 之间进行选择”的 MS 文档指定了以下说明:“DateTimeOffset 值与特定时区无关,但可以源自各种时区中的任何一个”。也就是说,DateTimeOffset 是 AWARE 时区,包含与 UTC 的偏移量,这一切都不同,这就是为什么在处理处理日期信息的应用程序开发时它是 MS 推荐的默认类。如果您真的关心数据来自哪个特定时区,则必须单独保存
    • 是的,但正如在许多地方所显示的那样,+ 或 - 小时与您所处的时区无关,并且最终无用。根据您需要做什么,您也可以将日期时间存储为 Kind.Unspecified ,然后存储其时区的 id,我认为您实际上会更好。
    【解决方案6】:

    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
    

    【讨论】:

    • 所以可以说当用户创建某些东西时我需要存储一个 CreatedDate 属性。我是否将 DatetimeOffset.Now 或 UtcNow 传递给服务器?
    • @Morten_564834 我会说DateTimeOffset.Now,因为您可以比较CreatedDate,而不考虑他们的时区。
    【解决方案7】:

    TLDR 如果您不想阅读所有这些精彩的答案 :-)

    显式

    使用DateTimeOffset,因为时区被强制为UTC+0。

    隐式

    使用DateTime,您希望每个人都遵守时区始终为 UTC+0 的不成文规则。


    (开发者的旁注:显式总是比隐式好!)

    (Java 开发者的旁注,C# DateTimeOffset == Java OffsetDateTime,请阅读:https://www.baeldung.com/java-zoneddatetime-offsetdatetime

    【讨论】:

    • 如果您在 Azure 中运行,则不必担心每个人都会遵守不成文的规则。 DateTime.Now、DateTimeOffset.Now、DateTime.UtcNow 和 DateTimeOffset.UtcNow 都返回完全相同的 UTC 时间点。
    【解决方案8】:

    主要区别在于DateTimeOffset 可以与TimeZoneInfo 结合使用,以转换为当前时区以外的本地时间。

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

    【讨论】:

    • @Bugeo Bugeo 是真的,但存在风险。您可以通过首先在每个日期上调用“ToUniversalTime”来比较两个日期时间。如果您在比较中只有一个值 DateTimeKind = Unspecified 您的策略将失败。当需要转换为本地时间时,这种失败的可能性是考虑 DateTimeOffset 而不是 DateTime 的原因。
    • 如上所述,我认为在这种情况下,存储 TimeZoneId 比使用 DateTimeOffset 更好,这最终没有任何意义。
    • 或者你可以存储一个 DateTimeOffset 加上 TimeZoneId。那么你不仅有偏移量,还有导致偏移量的时区。请记住,多个时区可以共享相同的偏移量。
    【解决方案9】:

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

    XmlConvert.ToDateTimeOffset

    XmlConvert.ToString

    我说继续使用 DateTimeOffset 和 TimeZoneInfo 是因为它的所有好处,只是在创建将或可能序列化到 XML 或从 XML 序列化的实体时要小心(然后是所有业务对象)。

    【讨论】:

      【解决方案10】:

      DateTime.Now
      21 年 12 月 3 日星期五 18:40:11

      DateTimeOffset.Now
      12 月 21 日星期五 18:40:11 +02:00

      所以,DateTimeOffset 存储有关时间与 UTC(基本上是时区)之间关系的信息。

      【讨论】:

        猜你喜欢
        • 2013-09-13
        • 1970-01-01
        • 2021-12-19
        • 1970-01-01
        • 2018-03-10
        • 2016-10-29
        • 1970-01-01
        • 2015-08-26
        • 2018-02-20
        相关资源
        最近更新 更多