DateTime vs DateTimeOffset


742

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

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


3
以下是一些很好的答案。但是我仍然想知道是什么,如果有的话,可以说服您开始使用DateTimeOffset。
HappyNomad13年


在存储方面,stackoverflow.com / questions / 4715620 /…也很有趣。
Dejan

Answers:


1169

DateTimeOffset瞬时时间(也称为绝对时间)的表示。所谓时间,是指每个人都普遍拥有的时刻(不考虑leap秒时间膨胀的相对论效应)。表示瞬时时间的另一种方法是使用DateTimewhere .KindDateTimeKind.Utc

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

那么,为什么要DateTimeOffset使用UTC DateTime这都是关于视角的。 让我们用一个比喻-我们假装是摄影师。

想象一下您正站在日历时间轴上,将相机对准摆在您面前的瞬时时间轴上的人。您根据时区的规则为相机排好队-夏令时或其他时区的法律定义会定期更改这些规则。(您的手没有稳固,因此相机抖动。)

站在照片中的人会看到您的相机所成的角度。如果其他人正在拍照,那么他们可能来自不同的角度。这就是代表的Offset部分DateTimeOffset

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

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

瞬时时间与日历时间的可视化

那么-这个比喻告诉我们什么?它提供了一些直观的准则-

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

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

  • 如果您需要跟踪瞬时时间,但又想知道“用户认为该时间是他们本地日历中的什么时间?” -那么您必须使用DateTimeOffset。例如,这对于计时系统非常重要-出于技术和法律方面的考虑。

  • 如果您需要修改以前记录的内容DateTimeOffset-仅偏移量中没有足够的信息来确保新偏移量仍与用户相关。您必须存储时区标识符(请考虑-我需要该摄像机的名称,以便即使位置发生了变化也可以拍摄新照片)。

    还应该指出的是,Noda Time具有ZonedDateTime为此的表示形式,而.Net基类库没有类似的表示形式。您将需要同时存储a DateTimeOffset和一个TimeZoneInfo.Id值。

  • 有时,您需要代表“ whover在看它”本地的日历时间。例如,在定义今天意味着什么时。今天始终是午夜至午夜,但它们表示瞬时时间轴上几乎无限数量的重叠范围。(实际上,时区是有限的,但是您可以将偏移量表示为滴答声。)因此,在这种情况下,请确保您了解如何限制“谁在问谁”?问题降到单个时区,或酌情将其转换回瞬时时间。

以下是支持该类比的其他一些信息DateTimeOffset,以及保持类比的一些技巧:

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

  • 如果你正在做的任何单元测试,需要一定的偏移量,测试的两个DateTimeOffset值,和.Offset分别财产。

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

无耻的插头:

许多人与我分享了他们发现这种类比的价值,因此我将其包含在我的Pluralsight课程“ 日期和时间基础知识”中。您可以在标题为“日历时间与瞬时时间”的剪辑的第二个模块“上下文问题”中找到有关摄像机类比的逐步介绍。


4
@ZackJannsen如果您DateTimeOffset在C#中有一个,则应该DATETIMEOFFSET在SQL Server中将其持久化到一个。 DATETIME2或仅DATETIME(取决于所需范围)适合常规DateTime值。是的-您可以通过任何时区+ dto或utc配对来解析本地时间。区别在于-您是否总是要在每个解决方案中计算规则,还是要预先计算它们?在许多情况下(有时出于法律考虑),DTO是更好的选择。
马特·约翰逊

3
@ZackJannsen对于您问题的第二部分,我建议尽可能多地在服务器端进行操作。Javascript对于时区计算不是那么好。如果必须这样做,请使用以下库之一。但是服务器端最好。如果您还有其他更详细的问题,请为他们提出一个新的SO问题,如果可以,我会回答。谢谢。
马特·约翰逊

4
@JoaoLeme-这取决于您从何处获得它。您是DateTimeOffset.Now对的,如果您在服务器上说,您确实会得到服务器的偏移量。关键是该DateTimeOffset类型可以保留该偏移量。您可以轻松地在客户端上执行此操作,然后将其发送到服务器,然后服务器将知道客户端的偏移量。
马特·约翰逊·品脱

8
真的很喜欢相机的比喻。
Sachin Kainth

2
对,那是正确的。除了DTO存储为(本地时间,偏移量)对,而不是(utc时间,偏移量)对。换句话说,与UTC的偏移已反映在当地时间。要转换回utc,请反转偏移量的符号并将其应用于当地时间。
Matt Johnson-Pint 2014年

328

从Microsoft:

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

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

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

DateTime当您只想处理日期,只处理时间或一般意义上的处理时,我认为这很有用。例如,如果您有一个要每天早上7点关闭的警报,则可以将其存储在DateTime一个DateTimeKindof中,Unspecified因为您希望它在早上7点关闭,而不考虑夏令时。但是,如果要表示警报发生的历史记录,可以使用DateTimeOffset

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


146
公认的答案太长,类推比较费力,这是IMO更好,更简洁的答案。
nexus说

10
我只是说我也喜欢这个答案,并且投票赞成。尽管在最后一部分中-即使确保Kind相同,但比较可能会出错。如果双方都没有,DateTimeKind.Unspecified您真的不知道他们来自同一时区。如果双方都是DateTimeKind.Local,则大多数比较都会很好,但是您可能仍然会出错,因为一方在当地时区中含糊不清。真正只有DateTimeKind.Utc比较是万无一失的,是的,DateTimeOffset通常是首选。(干杯!)
马特·约翰逊·品脱

1
+1我要补充一点:您选择的DataType应该反映您的意图。请勿在任何地方都使用DateTimeOffset,这只是原因。如果“偏移量”对您的计算和从数据库读/持久化很重要,请使用DateTimeOffset。如果没关系,请使用DateTime,这样您就可以理解(仅通过查看DataType)Offset应该没有影响,并且Time应该相对于您的C#代码在其上运行的服务器/机器的本地性保持不变。
MikeTeeVee

“但是,如果要表示警报发生的历史记录,则可以使用DateTimeOffset。” 您是否愿意解释为什么会这样?我可以通过阅读本页上的所有信息来进行填写,但也许您也可以以易于阅读的方式提供此类信息。这是一个非常容易理解的答案。
巴罗西

77

DateTime只能存储两个不同的时间,本地时间和UTC。该种类属性表示。

DateTimeOffset可以在世界任何地方存储本地时间,从而对此进行了扩展。它还存储该本地时间和UTC之间的时间偏移。请注意,除非您将额外的成员添加到类中以存储该UTC偏移量,否则DateTime不能做到这一点。或仅与UTC一起使用。顺便说一句,这本身就是个好主意。


33

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

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


12
DateTimeOffset不能解决DST问题
JarrettV 2013年

2
使用TimeZoneInfo类确实包含DST的规则。如果您使用的是.net 3.5或更高版本,则使用TimeZone或TimeZoneInfo类来处理必须与时区偏移量同时处理夏时制的日期。
Zack Jannsen

1
是一个很好的例外示例(警报应用程序),但是当时间比实际日期更重要时,您应该将其单独存储在应用程序的日程表数据结构中,即发生类型=每天,时间= 09:00。这里的重点是开发人员需要知道他们正在记录,计算或呈现给用户的日期类型。特别是应用程序趋向于全球化,现在我们已经将互联网作为标准,并为大型应用程序商店编写软件。作为副节点,我还希望看到Microsoft添加一个单独的日期和时间结构。
Tony Wall

3
总结Jarrett和Zack的评论:听起来像单独使用DateTimeOffset不能解决DST问题,但是结合使用DateTimeOffset和TimeZoneInfo可以解决该问题。这与种类为Utc的DateTime没什么不同。在这两种情况下,我都必须知道要为其投影时刻的日历的时区(而不仅仅是偏移)。(如果可能的话,我可能会将其存储在用户的个人资料中,或者从客户端(例如Windows)中获取)。听起来对吗?
杰里米·库克

“在某些地方,DateTimeOffset有意义。” ---可以说,它常常比没有意义。
罗尼·奥弗比

23

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

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

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

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


14
有了以上所有答案,我想知道为什么没人愿意写您的一句话来概括这一切了The most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
-Korayem

9
DateTimeOffset不存储时区信息。MS标题为“在DateTime,DateTimeOffset,TimeSpan和TimeZoneInfo之间选择”的文档指定了以下说明:“ DateTimeOffset值不绑定到特定时区,但可以源自任何时区。” 就是说,DateTimeOffset是时区AWARE,包含与UTC的偏移量,这造成了所有差异,这就是为什么在处理涉及日期信息的应用程序开发时,MS推荐使用默认类。如果您真正在意数据来自哪个特定时区,则必须单独保存该数据
stonedauwg

1
是的,但是正如许多地方所显示的,+或-小时无法说明您所处的时区,最终毫无用处。根据您需要执行的操作,您可以将日期时间存储为Kind.Unspecified,然后存储其时区的ID,我认为您实际上会更好。
阿温

13

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

9

大多数答案都不错,但是我想添加更多MSDN链接以获得更多信息。



7

一个主要区别是DateTimeOffset可以结合使用,TimeZoneInfo以将时区转换为当前时间以外的时区。

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


3
@Bugeo Bugeo是真实的,但存在风险。您可以通过首先在每个日期上调用“ ToUniversalTime”来比较两个DateTime。如果比较中只有一个值为DateTimeKind =未指定,则您的策略将失败。当需要转换到本地时间时,这种潜在的失败是在考虑DateTimeOffset而不是DateTime的原因。
Zack Jannsen

如上所述,我认为在这种情况下,存储TimeZoneId比使用DateTimeOffset更好,因为后者最终没有任何意义。
阿温

2

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

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.