日期时间与日期时间偏移
DateTime
和 DateTimeOffset
之间有什么区别以及何时应该使用它们?
目前,我们有一种以时区感知方式处理 .NET DateTime
的标准方法:每当我们生成 DateTime
时,我们都会以 UTC 格式进行操作(例如使用 >DateTime.UtcNow
),每当我们显示一个时,我们都会从 UTC 转换回用户的本地时间。
这工作正常,但我一直在阅读有关 DateTimeOffset
以及它如何捕获对象本身中的本地和 UTC 时间。
What is the difference between a DateTime
and a DateTimeOffset
and when should one be used?
Currently, we have a standard way of dealing with .NET DateTime
s in a TimeZone-aware way: Whenever we produce a DateTime
we do it in UTC (e.g. using DateTime.UtcNow
), and whenever we display one, we convert back from UTC to the user's local time.
That works fine, but I've been reading about DateTimeOffset
and how it captures the local and UTC time in the object itself.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
DateTimeOffset
是瞬时时间(也称为绝对时间)的表示。我的意思是每个人都通用的时刻(不考虑闰秒,或时间膨胀的相对论效应)。表示瞬时时间的另一种方法是使用DateTime
,其中.Kind
是DateTimeKind.Utc
。这与日历时间(也称为民用时间)不同,后者是某人日历上的一个位置,并且全球有许多不同的日历。我们将这些日历称为时区。日历时间由
DateTime
表示,其中.Kind
是DateTimeKind.Unspecified
或DateTimeKind.Local
。并且.Local
仅在您对使用结果的计算机的位置有隐含了解的情况下才有意义。 (例如,用户的工作站)那么,为什么使用
DateTimeOffset
而不是 UTCDateTime
呢? 一切都与视角有关。让我们打个比方——我们假装是摄影师。想象一下,您站在日历时间轴上,将相机对准您面前的瞬时时间轴上的某个人。您根据时区规则排列相机 - 由于夏令时或时区法律定义的其他变化,时区规则会定期变化。 (你的手不稳定,所以你的相机会摇晃。)
站在照片中的人会看到你的相机的拍摄角度。如果其他人在拍照,他们可能会从不同的角度拍摄。这就是
DateTimeOffset
的Offset
部分所表示的内容。因此,如果您将相机标记为“东部时间”,有时您会从 -5 指向,有时您会从 -4 指向。世界各地都有摄像机,都标记了不同的东西,并且都从不同角度指向同一个瞬时时间线。其中一些彼此相邻(或重叠),因此仅知道偏移量不足以确定时间与哪个时区相关。
那么UTC呢?嗯,这是市面上唯一一款保证稳定的相机。它位于三脚架上,牢固地固定在地面上。它不会去任何地方。我们将其视角称为零偏移。
那么 - 这个类比告诉我们什么?它提供了一些直观的指导原则 -
如果您要表示相对于某个特定地点的时间,请使用
DateTime
以日历时间表示它。请确保您不会将一个日历与另一个日历混淆。Unspecified
应该是您的假设。Local
仅在来自DateTime.Now
时有用。例如,我可能会获取DateTime.Now
并将其保存在数据库中 - 但当我检索它时,我必须假设它是Unspecified
。我不能相信我的本地日历与最初获取的日历相同。如果您必须始终确定时刻,请确保您代表的是瞬时时间。使用
DateTimeOffset
强制执行,或按照惯例使用 UTCDateTime
。如果您需要跟踪瞬时时间,但您还想知道“用户认为本地日历上的时间是几点?” - 那么您必须使用
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 课程中,< a href="http://www.pluralsight.com/courses/date-time-fundamentals" rel="noreferrer">日期和时间基础知识。您将在第二个模块“上下文很重要”的标题为“日历时间与瞬时时间”的剪辑中找到相机类比的分步演练。
DateTimeOffset
is a representation of instantaneous time (also known as absolute time). By that, I mean a moment in time that is universal for everyone (not accounting for leap seconds, or the relativistic effects of time dilation). Another way to represent instantaneous time is with aDateTime
where.Kind
isDateTimeKind.Utc
.This is distinct from calendar time (also known as civil time), which is a position on someone's calendar, and there are many different calendars all over the globe. We call these calendars time zones. Calendar time is represented by a
DateTime
where.Kind
isDateTimeKind.Unspecified
, orDateTimeKind.Local
. And.Local
is only meaningful in scenarios where you have an implied understanding of where the computer that is using the result is positioned. (For example, a user's workstation)So then, why
DateTimeOffset
instead of a UTCDateTime
? It's all about perspective. Let's use an analogy - we'll pretend to be photographers.Imagine you are standing on a calendar timeline, pointing a camera at a person on the instantaneous timeline laid out in front of you. You line up your camera according to the rules of your timezone - which change periodically due to daylight saving time, or due to other changes to the legal definition of your time zone. (You don't have a steady hand, so your camera is shaky.)
The person standing in the photo would see the angle at which your camera came from. If others were taking pictures, they could be from different angles. This is what the
Offset
part of theDateTimeOffset
represents.So if you label your camera "Eastern Time", sometimes you are pointing from -5, and sometimes you are pointing from -4. There are cameras all over the world, all labeled different things, and all pointing at the same instantaneous timeline from different angles. Some of them are right next to (or on top of) each other, so just knowing the offset isn't enough to determine which timezone the time is related to.
And what about UTC? Well, it's the one camera out there that is guaranteed to have a steady hand. It's on a tripod, firmly anchored into the ground. It's not going anywhere. We call its angle of perspective the zero offset.
So - what does this analogy tell us? It provides some intuitive guidelines-
If you are representing time relative to some place in particular, represent it in calendar time with a
DateTime
. Just be sure you don't ever confuse one calendar with another.Unspecified
should be your assumption.Local
is only useful coming fromDateTime.Now
. For example, I might getDateTime.Now
and save it in a database - but when I retrieve it, I have to assume that it isUnspecified
. I can't rely that my local calendar is the same calendar that it was originally taken from.If you must always be certain of the moment, make sure you are representing instantaneous time. Use
DateTimeOffset
to enforce it, or use UTCDateTime
by convention.If you need to track a moment of instantaneous time, but you want to also know "What time did the user think it was on their local calendar?" - then you must use a
DateTimeOffset
. This is very important for timekeeping systems, for example - both for technical and legal concerns.If you ever need to modify a previously recorded
DateTimeOffset
- you don't have enough information in the offset alone to ensure that the new offset is still relevant for the user. You must also store a timezone identifier (think - I need the name of that camera so I can take a new picture even if the position has changed).It should also be pointed out that Noda Time has a representation called
ZonedDateTime
for this, while the .Net base class library does not have anything similar. You would need to store both aDateTimeOffset
and aTimeZoneInfo.Id
value.Occasionally, you will want to represent a calendar time that is local to "whomever is looking at it". For example, when defining what today means. Today is always midnight to midnight, but these represent a near-infinite number of overlapping ranges on the instantaneous timeline. (In practice we have a finite number of timezones, but you can express offsets down to the tick) So in these situations, make sure you understand how to either limit the "who's asking?" question down to a single time zone, or deal with translating them back to instantaneous time as appropriate.
Here are a few other little bits about
DateTimeOffset
that back up this analogy, and some tips for keeping it straight:If you compare two
DateTimeOffset
values, they are first normalized to zero offset before comparing. In other words,2012-01-01T00:00:00+00:00
and2012-01-01T02:00:00+02:00
refer to the same instantaneous moment, and are therefore equivalent.If you are doing any unit testing and need to be certain of the offset, test both the
DateTimeOffset
value, and the.Offset
property separately.There is a one-way implicit conversion built in to the .Net framework that lets you pass a
DateTime
into anyDateTimeOffset
parameter or variable. When doing so, the.Kind
matters. If you pass a UTC kind, it will carry in with a zero offset, but if you pass either.Local
or.Unspecified
, it will assume to be local. The framework is basically saying, "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." This is a huge gotcha if you load up an unspecifiedDateTime
on a computer with a different timezone. (IMHO - that should throw an exception - but it doesn't.)Shameless Plug:
Many people have shared with me that they find this analogy extremely valuable, so I included it in my Pluralsight course, Date and Time Fundamentals. You'll find a step-by-step walkthrough of the camera analogy in the second module, "Context Matters", in the clip titled "Calendar Time vs. Instantaneous Time".
来自微软:
来源:"在 DateTime、DateTimeOffset、TimeSpan 之间进行选择和 TimeZoneInfo",MSDN
我们使用
DateTimeOffset
来处理几乎所有事情,因为我们的应用程序处理特定的时间点(例如,当创建记录时) /更新)。附带说明一下,我们也在 SQL Server 2008 中使用DATETIMEOFFSET
。当您只想处理日期、时间或一般意义上的任何一个时,我认为
DateTime
非常有用。例如,如果您希望每天早上 7 点响起闹钟,则可以利用DateTimeKind
为Unspecified< 将其存储在
DateTime
中。 /code> 因为无论夏令时如何,您都希望它在早上 7 点响起。但如果您想表示警报发生的历史记录,则可以使用DateTimeOffset
。混合使用
DateTimeOffset
和DateTime
时请务必小心,尤其是在类型之间进行分配和比较时。另外,仅比较相同DateTimeKind
的DateTime
实例,因为DateTime
在比较时会忽略时区偏移。From Microsoft:
source: "Choosing Between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo", MSDN
We use
DateTimeOffset
for nearly everything as our application deals with particular points in time (e.g. when a record was created/updated). As a side note, we useDATETIMEOFFSET
in SQL Server 2008 as well.I see
DateTime
as being useful when you want to deal with dates only, times only, or deal with either in a generic sense. For example, if you have an alarm that you want to go off every day at 7 am, you could store that in aDateTime
utilizing aDateTimeKind
ofUnspecified
because you want it to go off at 7am regardless of DST. But if you want to represent the history of alarm occurrences, you would useDateTimeOffset
.Use caution when using a mix of
DateTimeOffset
andDateTime
especially when assigning and comparing between the types. Also, only compareDateTime
instances that are the sameDateTimeKind
becauseDateTime
ignores timezone offset when comparing.DateTime 只能存储两个不同的时间:本地时间和 UTC。 Kind 属性指示是哪一种。
DateTimeOffset 对此进行了扩展,能够存储世界上任何地方的当地时间。它还存储本地时间与 UTC 之间的偏移。请注意 DateTime 如何无法执行此操作,除非您向类添加额外的成员来存储 UTC 偏移量。或者只与 UTC 合作。顺便说一句,这本身就是一个好主意。
DateTime is capable of storing only two distinct times, the local time and UTC. The Kind property indicates which.
DateTimeOffset expands on this by being able to store local times from anywhere in the world. It also stores the offset between that local time and UTC. Note how DateTime cannot do this unless you'd add an extra member to your class to store that UTC offset. Or only ever work with UTC. Which in itself is a fine idea btw.
日期时间.Now
21 月 3 日星期五 18:40:11
DateTimeOffset.Now
Fri 03 Dec 21 18:40:11 +02:00
因此,
DateTimeOffset
存储有关时间与 UTC(基本上是时区)关系的信息。DateTime.Now
Fri 03 Dec 21 18:40:11
DateTimeOffset.Now
Fri 03 Dec 21 18:40:11 +02:00
So,
DateTimeOffset
stores information about how the time relates to UTC, basically the time zone.这段代码来自 Microsoft 解释了一切:
This piece of code from Microsoft explains everything:
最重要的区别是 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.
Although DateTime distinguishes between UTC and Local, there is absolutely no explicit time zone offset associated with it. If you do any kind of serialization or conversion, the server's time zone is going to be used. Even if you manually create a local time by adding minutes to offset a UTC time, you can still get bit in the serialization step, because (due to lack of any explicit offset in DateTime) it will use the server's time zone offset.
For example, if you serialize a DateTime value with Kind=Local using Json.Net and an ISO date format, you'll get a string like
2015-08-05T07:00:00-04
. Notice that last part (-04) had nothing to do with your DateTime or any offset you used to calculate it... it's just purely the server's time zone offset.Meanwhile, DateTimeOffset explicitly includes the offset. It may not include the name of the time zone, but at least it includes the offset, and if you serialize it, you're going to get the explicitly included offset in your value instead of whatever the server's local time happens to be.
TLDR 如果您不想阅读所有这些很棒的答案:-)
显式:
使用
DateTimeOffset
因为时区被迫为 UTC+0 。隐式:
使用
DateTime
,您希望每个人都遵守时区始终为 UTC+0 的不成文规则。(给开发人员的旁注:显式总是比隐式更好!)
(给 Java 开发人员的旁注,C#
DateTimeOffset
== JavaOffsetDateTime
,阅读此内容:https://www.baeldung.com/java-zoneddatetime-offsetdatetime)TLDR if you don't want to read all these great answers :-)
Explicit:
Using
DateTimeOffset
because the timezone is forced to UTC+0.Implicit:
Using
DateTime
where you hope everyone sticks to the unwritten rule of the timezone always being UTC+0.(Side note for devs: explicit is always better than implicit!)
(Side side note for Java devs, C#
DateTimeOffset
== JavaOffsetDateTime
, read this: https://www.baeldung.com/java-zoneddatetime-offsetdatetime)有一些地方
DateTimeOffset
有意义。一是当您处理重复发生的事件和夏令时时。假设我想设置一个闹钟,每天早上 9 点响。如果我使用“存储为 UTC,显示为当地时间”规则,那么当夏令时生效时,闹钟将在不同时间响起。可能还有其他例子,但上面的例子实际上是我过去遇到过的一个(这是在将
DateTimeOffset
添加到 BCL 之前 - 我当时的解决方案是显式存储本地时区中的时间,并保存时区信息:基本上是DateTimeOffset
内部所做的事情)。There's a few places where
DateTimeOffset
makes sense. One is when you're dealing with recurring events and daylight savings time. Let's say I want to set an alarm to go off at 9am every day. If I use the "store as UTC, display as local time" rule, then the alarm will be going off at a different time when daylight savings time is in effect.There are probably others, but the above example is actually one that I've run into in the past (this was before the addition of
DateTimeOffset
to the BCL - my solution at the time was to explicitly store the time in the local timezone, and save the timezone information along side it: basically whatDateTimeOffset
does internally).主要区别在于
DateTimeOffset
可以与TimeZoneInfo
结合使用,以转换为当前时区以外的本地时间。这对于不同时区的用户访问的服务器应用程序(例如 ASP.NET)非常有用。
A major difference is that
DateTimeOffset
can be used in conjunction withTimeZoneInfo
to convert to local times in timezones other than the current one.This is useful on a server application (e.g. ASP.NET) that is accessed by users in different timezones.
我看到的 DateTimeOffset 的唯一负面影响是 Microsoft“忘记”(按照设计)在其 XmlSerializer 类中支持它。但它已被添加到 XmlConvert 实用程序类中。
XmlConvert.ToDateTimeOffset< /a>
XmlConvert.ToString
我说继续使用 DateTimeOffset 和 TimeZoneInfo 因为它们有很多好处,只是在创建将或可能序列化到 XML 或从 XML 序列化的实体(然后是所有业务对象)时要小心。
The only negative side of DateTimeOffset I see is that Microsoft "forgot" (by design) to support it in their XmlSerializer class. But it has since been added to the XmlConvert utility class.
XmlConvert.ToDateTimeOffset
XmlConvert.ToString
I say go ahead and use DateTimeOffset and TimeZoneInfo because of all the benefits, just beware when creating entities which will or may be serialized to or from XML (all business objects then).
我过去曾使用过 DateTimeOffset,但每次它最终都会成为一个问题。三个大问题是:
我几十年来一直用的是DateTime+TimeZone。 DateTime 是 DateTimeKind.Local,TimeZone 是 TimeZoneId 字符串。所以我有
2:00pm
+山地时间
。这是明确的(除了每年一次,当您重复从夏令时 1:00 到 2:00 转换为标准时间时)。这有效,避免了上述 3 个问题,并解决了 DateTimeOffset 解决的每个问题。您将其作为两列存储在数据库中:DateTime 和 TimeZoneId(字符串)。
我编写了一个 DateTimeZone 类来执行此操作。我把它放在 GitHub 和 NuGet。
I have used DateTimeOffset in the past and every time it ended up being a problem. The three big issues are:
What I have used for decades is DateTime+TimeZone. The DateTime is DateTimeKind.Local and the TimeZone is the TimeZoneId string. So I have
2:00pm
+Mountain Time
. This is unambiguous (except for once a year when you have the repeated 1:00am - 2:00am shift from Daylight Time to Standard Time).This works, avoids the above 3 issues, and addresses every issue that DateTimeOffset addresses. You store this in the database as 2 columns, the DateTime and the TimeZoneId (string).
I wrote a class DateTimeZone which does this. I have it up on GitHub and NuGet.