DateTime与DateTimeOffset

Dav*_*eis 669 .net c# timezone datetime datetimeoffset

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

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

Mat*_*int 1088

DateTimeOffset瞬时时间(也称为绝对时间)的表示.通过这种方式,我的意思是对每个人都是普遍的时刻(不考虑闰秒,或时间膨胀的相对论效应).代表瞬时间的另一种方法是用DateTime哪里.KindDateTimeKind.Utc.

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

那么,为什么DateTimeOffset而不是UTC DateTime呢? 这完全取决于观点. 让我们用一个类比 - 我们假装是摄影师.

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

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

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

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

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

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

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

  • 如果您必须始终确定当下,请确保您正在表示瞬时时间.使用DateTimeOffset强制执行,或者使用UTC DateTime通过约定.

  • 如果您需要跟踪瞬时时刻,但您还想知道"用户在当地日历上的时间是什么时候?" - 那你必须用一个DateTimeOffset.这对于计时系统非常重要,例如 - 无论是技术问题还是法律问题.

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

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

  • 有时,您会希望表示"任何人正在查看它"的本地日历时间.例如,在定义今天的意义时.今天总是午夜到午夜,但这些代表了瞬时时间轴上几乎无限数量的重叠范围.(实际上我们有一个有限数量的时区,但是你可以将偏移量表示为勾号)所以在这些情况下,请确保你理解如何限制"谁在问?" 向下一个时区提问,或者处理将它们转换回适当的瞬时时间.

以下是关于DateTimeOffset这个类比的一些其他一点点,以及保持它的一些提示:

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

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

  • .Net框架内置了一个单向隐式转换,允许您将DateTime任何DateTimeOffset参数或变量传递给它.这样做.Kind事情.如果你传递一个UTC一种,它将搭载与零偏移,但如果你通过两种.Local.Unspecified,就会认为是本地.该框架基本上是在说:"嗯,你让我把日历时间转换为瞬间时间,但我不知道它来自哪里,所以我只想使用本地日历." 如果您DateTime在具有不同时区的计算机上加载未指定的内容,则这是一个巨大的问题.(恕我直言 - 这应该抛出异常 - 但事实并非如此.)

无耻插头:

许多人与我分享他们发现这个类比非常有价值,所以我把它包括在我的Pluralsight课程,日期和时间基础.您将在标题为"日历时间与瞬时时间"的剪辑中找到第二个模块"上下文事项"中相机类比的逐步演练.

  • 真的很喜欢相机的比喻. (6认同)
  • 看完图片我现在更加困惑 (5认同)
  • @ZackJannsen如果在C#中有一个`DateTimeOffset`,那么你应该将它保存到SQL Server中的`DATETIMEOFFSET`.`DATETIME2`或只是'DATETIME`(取决于所需的范围)适用于常规`DateTime`值.是的 - 您可以通过任何时区+ dto或utc配对来解决当地时间.不同之处在于 - 你是否总是希望用每个决心计算规则,或者你想要预先计算它们?在许多情况下(有时是出于法律问题),DTO是更好的选择. (4认同)
  • @JoaoLeme - 这取决于你从哪里获得它.你是正确的,如果你在服务器上说'DateTimeOffset.Now`,你的确会得到服务器的偏移量.关键是`DateTimeOffset`类型可以保留该偏移量.您可以轻松地在客户端上执行此操作,将其发送到服务器,然后您的服务器将知道客户端的偏移量. (4认同)
  • @ZackJannsen对于你问题的第二部分,我建议尽可能多地服务器端.Javascript对于时区计算来说并不是那么好.如果必须这样做,请使用[这些库]之一(http://stackoverflow.com/a/15171030/634824).但服务器方面是最好的.如果您有其他更详细的问题,请为他们开始一个新的SO问题,如果可以,我会回答.谢谢. (3认同)
  • @MattJohnson 你说的想法是什么意思?我有一个 UTC 日期,客户告诉我他们相对于 UTC 的偏移量是多少,我给他们调整后的日期。很困惑为什么其他事情与该操作相关。 (3认同)
  • 哈哈,向下滚动“这一定是乔恩·斯基特,对吧?”。但没有:) (3认同)
  • 对,那是正确的.除了DTO存储为(本地时间,偏移)对,而不是(utc时间,偏移)对.换句话说,UTC的偏移量已经反映在当地时间.要转换回utc,请反转偏移的符号并将其应用于本地时间. (2认同)
  • 这是一个非常好的答案,对于令人困惑的DateTime库构造函数和方法真的很有意义. (2认同)
  • 我认为时区和utc与相机和摄影师的角度关系不大。带您的孩子穿越时区,即使是7岁的孩子也可以理解。 (2认同)

Cla*_*lay 290

来自微软:

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

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

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

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

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

  • 接受的答案是过长的,类比是紧张的,这是一个更好,更简洁的答案IMO. (128认同)
  • 我只是说我也喜欢这个答案,并且赞成.虽然在最后一部分 - 即使确保`Kind`是相同的,但比较可能是错误的.如果双方都有`DateTimeKind.Unspecified`,你真的不知道他们来自同一时区.如果双方都是`DateTimeKind.Local`,*大多数*比较都没问题,但你仍然可能有错误,一方在本地时区是模棱两可的.真的只有`DateTimeKind.Utc`比较是万无一失的,是的,`DateTimeOffset`通常是首选.(干杯!) (9认同)
  • +1我想补充一点:您选择的数据类型应该反映您的意图。不要到处使用 DateTimeOffset,只是因为。如果偏移量对于您的计算和从数据库读取/保留到数据库很重要,则使用 DateTimeOffset。如果不重要,那么使用 DateTime,这样您就明白(只需查看 DataType)偏移量应该没有影响,并且时间应该保持相对于您的 C# 代码运行的服务器/计算机的位置。 (2认同)

Han*_*ant 68

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

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

  • 到目前为止,这篇文章中最简单也是最简单的答案。 (6认同)

Tes*_*act 48

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

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

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

  • 被低估的答案,这应该是最重要的 (5认同)
  • 我建议细化“基本上是时区”-->“基本上是时区的偏移部分”。请参阅文档中有关为什么偏移量不等于时区的注释:https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset#remarks (2认同)

Dea*_*ing 33

有几个地方DateTimeOffset有意义.一个是当你处理经常性事件和夏令时时.假设我想在每天上午9点设置闹钟.如果我使用"以UTC存储,显示为本地时间"规则,那么当夏令时生效时,闹钟将在不同的时间关闭.

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

  • DateTimeOffset不能解决DST问题 (11认同)
  • 总结Jarrett和Zack的评论:听起来像单独的DateTimeOffset**将无法处理DST问题,但将DateTimeOffset与TimeZoneInfo结合使用将会处理它.这与DateTime是没有什么不同,其中kind是Utc.在这两种情况下,我都必须知道我正在预测的日历的时区(而不仅仅是偏移).(如果可能的话,我可以将其存储在用户的个人资料中或从客户端(例如Windows)获取).听起来不对? (3认同)
  • “DateTimeOffset 在某些地方是有意义的。” --- 可以说,它往往是有道理的。 (3认同)
  • 使用TimeZoneInfo类确实携带DST规则.如果您使用的是.net 3.5或更高版本,则使用TimeZone或TimeZoneInfo类来处理必须在时区偏移量的情况下处理夏令时的日期. (2认同)

Tri*_*nko 21

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

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

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

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

  • 有了上述所有答案,我想知道为什么没有人费心去编写你的单句总结起来`最重要的区别是DateTime不存储时区信息,而DateTimeOffset则存在. (12认同)
  • DateTimeOffset不存储时区信息.标题为"在DateTime,DateTimeOffset,TimeSpan和TimeZoneInfo之间选择"的MS文档指定了这一说法:"DateTimeOffset值不依赖于特定时区,但可以源自任何各种时区".也就是说,DateTimeOffset是时区AWARE,包含与UTC的偏移量,这使得它在处理处理日期信息的应用程序开发时MS建议使用默认类别.如果您真正关心数据来自哪个特定时区,则必须单独保留该时区 (8认同)

Blu*_*ell 11

如果您不想阅读所有这些很棒的答案,请使用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 时间点。 (2认同)

dek*_*dev 9

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


Moj*_*aba 8

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
Run Code Online (Sandbox Code Playgroud)


Joe*_*Joe 7

一个主要的区别是DateTimeOffset可以与TimeZoneInfo当前时间区域之外的时区转换为本地时间.

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

  • @Bugeo Bugeo是真的,但存在风险.您可以通过首先在每个DateTime上调用"ToUniversalTime"来比较它们.如果您在比较中只有一个值是DateTimeKind =未指定,则您的策略将失败.当需要转换为本地时间时,这种失败的可能性是考虑DateTimeOffset而不是DateTime的原因. (3认同)