Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.
Anyone who has done any .NET programming has most likely used the DateTime structure at one time or another in their development. It is a handy structure for storing dates, times, or date-times relating to the local time zone (or, alternatively, UTC).
That said, there are times when you may be writing a program where you want to preserve a time in its given offset and not convert it to local time. This is where the .NET 3.5 DateTimeOffset structure comes in handy.
The Problem: DateTime parsing can convert to local time
Let’s say you are consuming a file, web service, etc. from a third party whose servers are in a different time zone. Further, they have several fields in their response data that are meant to represent dates but are actually serialized DateTime instances where they have a date and a time set to midnight. For example, let’s say they pass a birth date of a patient as:
2012-03-01 00:00:00-05:00
Which indicates that the person was born on 3/1/2012 with no time meant to be specified (most forms don’t ask you to fill in your birth-time, after all) but because they serialized a DateTime directly (or something similar) it contains a time portion set to midnight in their time zone.
So, knowing that date is compatible with the Eastern Time Zone, and then let’s say we’re in the Central Time Zone and we parse it thusly:
1: // obviously this would be read from the stream/file/etc
2: var dateString = "2012-03-01 00:00:00-05:00";
3:
4: // parse into a DateTime
5: var birthDay = DateTime.Parse(dateString);
Seems peachy, right? But there’s a problem here, if we check the contents of this DateTime now on our local machine set to the Central Time zone, we see that the birthday is now:
2012-02-29 11:00:00 PM
What happened? Well, the DateTime.Parse() converted the date to the local time zone since the original birth date string had an offset specified. It simply did you the favor of converting that date and time with offset to your local date and time. Which wouldn’t be a bad thing if it weren’t for the fact that suddenly this person’s birthday has been shifted to 2/29 instead of 3/1.
Sure, we could ask the third party service to stop sending the time with the date string, or to stop sending the time offset (in which case it won’t convert to local, but will leave it as DateTimeKind.Unspecified), but some times we don’t have that much control over what our third parties can send.
Regardless of the reason, there are times you want to read in a date and time with an offset and not convert it to your local time. This is where DateTimeOffset comes in handy.
DateTimeOffset – Stores a DateTime and Offset
So what is DateTimeOffset? It’s as simple as the name itself, a DateTimeOffset is a Date + a Time + an Offset. That is, it represents a much more precise point in time because it includes as information the offset at which that date and time were taken.
In truth, there is a lot of overlap in the functionality of DateTime and DateTimeOffset, and as such Microsoft has some guidance as to when you should prefer one over the other listed in the MSDN article: Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo.
In general, you can use DateTime if you are sticking within the same time zone or are using UTC times only, but if you intend on using dates and times from multiple time zones and want to preserve that offset information without conversion to local time, choose the DateTimeOffset instead.
The DateTimeOffset has many of the same properties as DateTime (such as Day, Month, Year, Hour, Minute, Second, etc.) so I won’t cover the redundant ones in detail. The main difference is the addition of a few new properties:
- DateTime
- Gets the DateTime portion of the value without regard to the offset.
- LocalDateTime
- Returns a DateTime representing the value in respect to the local time zone.
- Offset
- Returns the time offset from UTC.
- UtcDateTime
- Returns a DateTime representing the value in respect to UTC time.
The DateTime property gives you back the DateTime portion (not adjusted to local time) and the Offset property gives you back aTimeSpan that represents the offset from UTC. There are also properties called LocalDateTime and UtcDateTime that let you convert the given DateTimeOffset to a DateTime respective of the local time zone or UTC.
In addition, it should be noted that the Now and UtcNow for DateTimeOffset return not DateTime, but DateTimeOffsets with the appropriate time offset from UTC. Of course, like DateTime, DateTimeOffset has many of the same capabilities for date-time arithmetic and parsing except that the results of these are DateTimeOffset instead of DateTime.
So how does this help us in our example above? Well, let’s say we have that birthday example from before, and we know that the third-party is sending us a date and time in their time-zone which we do not want to convert to local time, thus we can use DateTimeOffset.Parse() instead (or TryParse()) and choose just the Date portion:
1: // obviously, we'd read this from a file/stream/etc.
2: string birthDay = "2012-03-01 00:00:00-05:00";
3:
4: // parse the birthday as a date time offset (doesn't convert to local)
5: var dtOffset = DateTimeOffset.Parse(birthDay);
6:
7: // now if we want to use this to compare to other DateTime instances
8: // locally, we can just use the Date property to retrieve the date
9: // without respect to time or offset
10: var theDay = dtOffset.Date;
In this way, you can easily parse dates without worrying about the “midnight shift”, or really use it anytime you want to read a date and time with an offset and don’t want it converted to local time.
Summary
While the DateTime is a powerful structure for parsing, manipulating, and comparing date-times, it becomes problematic when handling times from different time-zones. The DateTimeOffset is much more portable in that it preserves the offset from UTC.