Time-travel with .NET or DateTime, DateTimeOffset and the lost DST hour [Greg]
Every year again comes the DST change...
And every year again do we need to work with customers on helping them understand some legacy design decisions, and how to work around these ghosts of the past.
Recently, around the end of October and the start of November the daylight saving time (DST) period finished in most areas of the northern hemisphere. As if it was an attempt to make up for the oncoming cold weather, we set our clocks back and got an extra hour of sleep. (In the southern hemisphere, the DST begins at the end of the year, and they turn their clocks forwards.) And one of the basic .NET types – DateTime
– does not play well with the annual end-of-DST transition. This issue is well-known and so is the workaround, but nevertheless it gets reported to us regularly by customers who rediscover the issue, especially around the DST transition season. There are a number of resources that treat this topic in great detail. Some of these resources are listed below. Here, I provide some of the background for the reasons behind the issues and a brief refresher on how to solve the problems.
First the gist: The DateTimeOffset
type helps avoiding most of the problems associated with DateTime
. If you have trouble with DateTime
in the context of a DST transition, consider using DateTimeOffset
instead.
Issue: Incorrect time zone display.
One issue with the DateTime
structure is that during transition from summer time to winter time (i.e. from DST to no-DST), the winter-time time-zone-offset it displayed an hour too early (when shown using a default built-in functionality such as DateTime``.``Now``.``ToString(``"o"``)
). However, the actual clock time is always displayed correctly. This symptom is caused by the interplay of the fact that DateTime
stores the local time only, and the nature of DST transitions.
Similar to the DateTime
type, the Windows DST database, on which Windows and .NET applications base their time calculations, specifies the transition times in local time (not UTC). During summer-to-winter time transition in the northern hemisphere the clocks are typically set one hour back. Assume, w.l.o.g., that the transition occurs at 3:00. Then, the times between 2:00 and 2:59 will occur twice: once during summer time, and once again during winter time after the clock has been set back. When the system is queried whether a time, say, 2:28, is in summer (say UTC -7) or in winter time (UTC -8), there is no way for the system to know which one is correct. In such cases, .NET assumes winter time, as that is typically the default time for the time zone (i.e. no DST). Note that if we changed this behaviour, the problem would still occur, only the other way around. For instance, the old DST state would show up one hour too long.
In this particular case, the actual time is always shown correctly, it is just the DST state that is incorrect.
Solution: Track the DST mode changes.
At one second past the hour (or any other pre-specified time), store the local system time. Then compare the current time against the timestamp stored when the one-second-part-the-hour event occurred previously. If they match, we know that we just transitioned to winter time: the offset has changed (e.g. using the previous example, to UTC-8). Otherwise, we are still in summer time: it is still UTC-7.
This workaround is complex and applies only to applications that run for at least one hour. This is not the best approach for a general fix.
Better solution: Use a type that stores time as UTC instead of local time.
UTC (coordinated universal time) describes the time at the Greenwich meridian and does not undergo any DST transitions. In a way (simplified but appropriate for this context), it can be understood as the “universal cosmological time”. The DateTimeOffset
type stores the local time as a pair of values: one value describes the UTC time, and the other value describes the offset between UTC and the local time. For instance, 02:28 in the UTC-7 time zone is stored as {UtcTime - Offset} = {10:28 - 420minutes}. 02:28 in the UTC-8 time zone is stored as {11:28 - 480minutes}. In contrast to DateTime
, these two values, although describing the same local time, describe two different points in time, and can be easily differentiated from each other. As a result, issues related to DST transitions can be handled correctly when using DateTimeOffset
.
Putting it all together: Recommendations.
The reading list below contains a wealth of background information on the DateTime
and DateTimeOffset
types and how to use them. Here is a short and practical summary of how these types should be used.
Use DateTime to: |
Use DateTimeOffset to: |
---|---|
|
|
More guidance on choosing the best date and time type for your circumstances is provided through the links below.
Further reading:
- The System.DateTimeOffset structure.
- The System.DateTime structure.
- Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo.
- DateTimeOffset: A New DateTime Structure in .NET 3.5.
- A Brief History of DateTime.
- A Brief History of DateTime Follow-up.
- DateTime FAQ Entries.
- DateTime.ToUniversalTime returns MaxValue/MinValue on overflow.
- Time Zones in the .NET Framework.
- TimeZoneInfo: Working with Ambiguous and Invalid Points in Time.
Comments
Anonymous
November 28, 2010
Thanks for the post, Now I know what is wrong with my HTC HD2 running on windows mobile 6.5Anonymous
December 10, 2010
I don't understand why DateTime/DateTimeOffset was not designed to simply hold the UTC time, and nothing more. The local time can easily be calculated when you need it. This is like Windows always did it: time stamps in NTFS is UTC, and there never was any problems until .NET came along?Anonymous
January 16, 2011
Hi osexpert, The answers to your questions are in the further reading links above. In particular, these will be the most relevant ones: msdn.microsoft.com/.../bb384267.aspx blogs.msdn.com/.../a-brief-history-of-datetime-anthony-moore.aspx blogs.msdn.com/.../a-brief-history-of-datetime-follow-up-anthony-moore.aspx One of the advantages offered by DateTimeOffset is that it allows to represent a point in time unambiguously, and yet allows to specify a local time without referencing a particular geo-political time zone. This allows for instance persisting local times without persisting all relevant time zone and DST rules (as they may change over time). Please refer to the above links for further details. Thanks, -Greg