ChatGPT解决这个技术问题 Extra ChatGPT

日期时间与日期时间偏移

DateTimeDateTimeOffset 有什么区别,什么时候应该使用?

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

这很好用,但我一直在阅读 DateTimeOffset 以及它如何在对象本身中捕获本地时间和 UTC 时间。

在存储方面,stackoverflow.com/questions/4715620/… 也很有趣。
好奇的人可能还想阅读storing utc is not a silver bullet

i
iliketocode

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

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

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

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

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

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

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

https://i.stack.imgur.com/QE5xq.png

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

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

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

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

如果您需要修改以前记录的 DateTimeOffset - 您在偏移量中没有足够的信息来确保新的偏移量仍然与用户相关。您还必须存储一个时区标识符(想想 - 我需要那个相机的名称,这样即使位置发生了变化,我也可以拍一张新照片)。还应该指出的是,Noda Time 对此有一个称为 ZonedDateTime 的表示,而 .Net 基类库没有类似的东西。您需要同时存储 DateTimeOffset 和 TimeZoneInfo.Id 值。

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

以下是关于 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 课程中,Date and Time Fundamentals。您将在标题为“日历时间与瞬时时间”的剪辑的第二个模块“上下文很重要”中找到相机类比的逐步演练。


@ZackJannsen 如果您在 C# 中有一个 DateTimeOffset,那么您应该将它保存到 SQL Server 中的 DATETIMEOFFSETDATETIME2 或仅 DATETIME(取决于所需的范围)适用于常规 DateTime 值。是的 - 您可以从任何一对时区 + dto 或 utc 解析本地时间。不同之处在于——你总是想为每个解析计算规则,还是想预先计算它们?在许多情况下(有时出于法律考虑),DTO 是更好的选择。
@ZackJannsen 对于您问题的第二部分,我建议您尽可能多地在服务器端进行操作。 Javascript 对于时区计算并不是那么好。如果必须这样做,请使用 these libraries 之一。但是服务器端是最好的。如果您还有其他更详细的问题,请为他们开始一个新的 SO 问题,如果可以,我会回答。谢谢。
@JoaoLeme - 这取决于你从哪里获得它。你是对的,如果你在服务器上说 DateTimeOffset.Now,你确实会得到服务器的偏移量。关键是 DateTimeOffset 类型可以保留该偏移量。您可以在客户端上轻松执行此操作,将其发送到服务器,然后您的服务器将知道客户端的偏移量。
对,那是正确的。除了 DTO 存储为 (local time, offset) 对,而不是 (utc time, offset) 对。换句话说,与 UTC 的偏移量已经反映在本地时间中。要转换回 UTC,请反转偏移量的符号并将其应用于本地时间。
我认为时区和UTC与相机和摄影师的角度关系不大。带您的孩子跨时区旅行,即使是 7 岁的孩子也能理解。
C
Cœur

来自微软:

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

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

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

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

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


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

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

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


D
Dean Harding

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

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


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

最重要的区别是 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,我认为您实际上会更好。
M
Mojtaba

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 而与他们的时区无关。
B
Blundell

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 时间点。
J
Joe

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

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


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

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

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

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


T
Tessaract

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

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

因此,DateTimeOffset 存储有关时间如何与 UTC(基本上是时区)相关的信息。