Currently, we have a standard way of dealing with .net DateTimes 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. So the question is, what would be the advantages of using DateTimeOffset
vs what we have already been doing?
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.This piece of code from Microsoft explains everything:
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".
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.
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.
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.