Ask Learn
Preview
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
It becomes necessary to deal with dates and times in most .NET programs. A lot of programs use DateTime but that structure is frought with potential issues when you start serializing, parsing, comparing and displaying dates from different time zones and cultures. In this post I will go over these issues and the APIs and practices you should use to avoid them.
Background
The DateTime structure stores only two pieces of information: Ticks and Kind. 1 tick is 100 nanoseconds (10,000 ticks in a millisecond). The ticks counter represents how many 100ns ticks you are away from 1/1/0001 12:00 AM. It's the part that determines that it's April 6th, 2012, 3:32:07 PM.
Another bit of data is the Kind: represented as a DateTimeKind enumeration. Utc, Local and Unspecified are the possible values. Utc means that the ticks counter represents a Coordinated Universal Time (that doesn't change due to daylight savings or time zones). Local means that it represents the local time of whatever time zone the computer is set to. This one is sensitive to daylight savings. Note that a timezone offset is not built in to the DateTime structure. It can only get back to a real UTC time by checking the current time zone settings on the computer. ToUniversalTime() and ToLocalTime() will do these conversions. It will return a new DateTime value with the Ticks adjusted to get the correct year/month/day and with the requested type. Calling ToUniversalTime() on a UTC DateTime or ToLocalTime() on a Local DateTime has no effect. Remember that these functions are generating a new DateTime value and not modifying the original.
There's also a third type: Unspecified. This means we don't know whether it's local or UTC. All we know is the year, month, day, etc. When working with DateTime values, Unspecified is not very helpful since we don't know what to do when we want to get a local or UTC time from it. If you call ToUniversalTime() on one of these, it assumes that the type must have been Local and converts based on that. If you call ToLocalTime() it assumes that the type must have been UTC and converts in that direction.
DateTimeOffset is a newer structure. It is also based on ticks but instead of storing a Kind, it keeps track of the offset from UTC as a TimeSpan. A DateTimeOffset always knows what time zone it's in. Calling ToUniversalTime() will always result in a TimeSpan.Zero offset, and ToLocalTime() will convert and result in an offset of the user's current time zone.
Use DateTimeOffset, not DateTime
DateTime has countless traps in it that are designed to give your code bugs:
But DateTimeOffset doesn't have any of these problems! It's the newer, more robust version; think of it as DateTime v2. They couldn't fix the old DateTime object because that might break existing code that was relying on the bad behavior.
So you'll want to use DateTimeOffset in any situation that you're referring to a specific point in time. You might still use DateTime for things like general dates (July 4th, 1776) or store hours (9AM-5PM), since they are not affected by time zones.
Pick the right format when serializing
It's common to convert DateTimeOffset values to strings for storage or sending them somewhere else. The list of standard date and time format strings is helpful when you are contemplating how to do this. These are used with the DateTimeOffset.ToString method: for example myDate.ToString("u"). Note how with most of the standard formats you get different values for different cultures: different month names, swapped month/day places, etc. If you use one of these to serialize and Parse back in, it might work if you have the same culture on both sides, but will break if one side uses a different culture. You can get failed parses or swapped month/day values. Only the "o", "r", "s" and "u" formats are culture-invariant. Use one of those for serialization:
Format string | Name | Example | |
"o" | Round-trip | 2012-04-17T16:46:48.0820143+00:00 (UTC) 2012-04-17T09:46:48.0820143-07:00 (local) | The only format that preserves the DateTimeOffset fully. The others round to the nearest second. |
"r" | RFC1123 | Tue, 17 Apr 2012 16:46:48 GMT | Used for HTTP headers |
"u" | Universal sortable | 2012-04-17 16:46:48Z | |
"s" | Sortable | 2012-04-17T16:46:48 (UTC) 2012-04-17T09:46:48 (local) | Not a great format for this use as it has no timezone or UTC marker and can change based on what offset you have |
Conclusion
Use DateTimeOffset and be careful with serialization. If you're dealing with legacy code that's using DateTime, be aware of all the timezone-related pitfalls when comparing, serializing, parsing and converting.
One final issue...
If it is Coordinated Universal Time, why is the acronym UTC and not CUT? When the English and the French were getting together to work out the notation, the french wanted TUC, for Temps Universel Coordonné. UTC was a compromise: it fit equally badly for each language. :)
Anonymous
April 08, 2012
Great info... thanks.
Anonymous
April 10, 2012
Two comments:
Anonymous
April 11, 2012
DateTimeOffset even has a matching type in SQL Server named... datetimeoffset
The lack of love/adoption around DateTimeOffset is maddening, including in MSFT stacks (WCF RIA added it in 1.0 SP2, but even new API's are shipping without support for it)
Anonymous
April 11, 2012
WCF Data Services 5.0 now supports DateTimeOffset properly
Anonymous
April 16, 2012
Oh, good call. I'll have to rewrite this to mention DateTimeOffset. Sounds like it might be immune from a lot of the problems I mentioned. As far as the historical timezones problem: what do you mean by this? How would it affect times that are stored as UTC?
Anonymous
April 17, 2012
And, updated. I'm pretty impressed with DateTimeOffset; it basically fixed every weird behavior that DateTime had.
Anonymous
April 25, 2012
Great article, I am going to make every dev on my team read it and explain it back to me.....
Anonymous
August 09, 2015
Wow you managed to make DateTime and DateTimeOffset understandable! Many have tried and failed to do that! Well done. I've read several articles that over-complicate it, including MSDN articles that make no sense, other people advising using DateTime to persist data and DateTimeOffset to present it. That is all nonsense - you just use DateTimeOffset everywhere as a replacement for DateTime, whenever you need to work with dates or times, persisting data, presenting it, whatever.
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign in