Working with Dates and Times in .Net 3.5 Series:
Part 1: DateTime vs. DateTimeOffset in .Net
Part 2: TimeZone vs. TimeZoneInfo in .Net
The DateTime data type has been around since .Net 1.0 and suited us well for a long time. It simply defines a date and a time together and offers methods for working with these constructs. It is a struct and hence can’t be set to null. This caused some people some problems over the years. Especially when modeling a DateTime from the database, since in most databases like SQL Server, the datetime field can be null. So, usually a null DateTime would be set to DateTime.MinValue. However, this wasn’t the most optimal solution.
In .Net 2.0 nullable types were introduced. Now, we could have a null DateTime by simply defining the field as System.Nullable<DateTime> (or DateTime?) instead of the standard DateTime. This allowed us to set the field to null, and pull the value using the Value property of the field.
In .Net 3.5 an entirely new date/time field was added called DateTimeOffset. This data extends the DateTime to add in the concept of a timezone offset. It is still a struct, so you need to use System.Nullable<DateTimeOffest> if you want to be able to have a null value. A DateTimeOffset value is not tied to a particular time zone, but can originate from any of a variety of time zones. For example, there are can multiple timezones that represent the same offset such as UTC –6:00 which can be Central Time, Central American, Saskatchewan, or Guadalajara. So you don’t know the timezone that the datetime represents, but you do know the offset. This is all you need to perform comparisons to other DateTimeOffsets.
The offset value represents the time difference between the current time and Greenwich Mean Time (GMT) this is known as Coordinated Universal Time (UTC). GMT historically is the time when the sun crosses the Prime Meridian line which is located a 0 Longitude. IT is measured from the Greenwich Meridian Line at the Royal Observatory in Greenwich, England. This is the place from where all time zones are measured. Even the atomic clocks of the world are adjusted by leap seconds to maintain synchronicity with GMT.
So why is UTC so important to software developers? This may be an obvious answer to most, but if you haven’t been exposed to this, it’s because many applications need to be globalized and may be run in different timezones. If someone enters in a date and time, is that relative to their timezone or the timezone the server is sitting on. This can be vastly different and can really cause problems in your application. Comparing all dates and times to the relative time of GMT allows applications to understand what the date and time actually represent.
What does DateTime and DateTimeOffest look like
Console.WriteLine(DateTime.Now);
Console.WriteLine(DateTimeOffset.Now);
This print out shows the basic difference in the two types. The DateTime shows the Date and Time, while the DateTimeOffset shows the Date, Time, and offset. This offset represents the difference from GMT. In this case 4 hours behind GMT.
How to convert from DateTime to DateTimeOffset
DateTime d1 = DateTime.Now;
DateTimeOffset o1 = new DateTimeOffset(d1);
DateTimeOffset of1 = new DateTimeOffset(d1, new TimeSpan(-5, 0, 0));
To convert a DateTime into a DateTimeOffset, you just need to pass the DateTime value into one of the constructors of the DateTimeOffset. You could just pass the DateTime in (as in o1 above), or you can also pass in the DateTime and an offset (as in of1 above). If you don’t supply an offset, then GMT is assumed.
How to convert from DateTimeOffest to DateTime
DateTimeOffset o2 = DateTimeOffset.Now;
DateTime d2 = o2.DateTime;
Converting a DateTimeOffset to a DateTime is easy. Since the DateTimeOffset value contains all of the data of a DateTime field and more, there is a simple DateTime property on the DateTimeOffset struct that returns a DateTime representation of the value.
How to manually set DateTime and DateTimeOffset
DateTime dt1 = new DateTime(2008, 8, 22, 1, 0, 0);
DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));
Console.WriteLine(dt1);
Console.WriteLine(do1);
Manually setting a DateTimeOffset is very similar to setting the value of a DateTime. The only difference is that you can also pass in a
TimeSpan as the last argument that represents the GMT offset.
Arithmetic with Datetime and DateTimeOffest
DateTime dateTime1 = DateTime.Now;
DateTime dateTime2 = DateTime.UtcNow;
Console.WriteLine("{0} - {1} =", dateTime1, dateTime2);
Console.WriteLine(dateTime1 - dateTime2);
Console.WriteLine();
DateTimeOffset offset1 = DateTimeOffset.Now;
DateTimeOffset offset2 = DateTimeOffset.UtcNow;
Console.WriteLine("{0} - {1} =", offset1, offset2);
Console.WriteLine(offset1 - offset2);
With this example we can see that the difference between Now and UtcNow is 0. This means that the DateTimeOffset sees the UTC version of the time and the local version of the time as equal. But what about something more complicated? Are 12 PM EST and 9AM PST equal?
DateTimeOffset ao1 = new DateTimeOffset(2008, 8, 22, 12, 0, 0, new TimeSpan(-5, 0, 0));
DateTimeOffset ao2 = new DateTimeOffset(2008, 8, 22, 9, 0, 0, new TimeSpan(-8, 0, 0));
Console.WriteLine(ao1 == ao2);
This code creates 2 times. One is 12 PM August, 22 2008 and the second is 9 AM August, 22 2008. However, the offsets for each are set for Eastern Standard time and Pacific Standard time respectively. If we compare these two values, are they equal? If you guessed “Yes”, then you are right. The Equals() method of DateTimeOffset knows now to compare against the offsets. This is incredibly useful when comparing times cared in different time zones.
When to use DateTime or DateTimeOffest
Use DateTimeOffest structure to work with dates and times whose offset (or difference) from UTC is known or when calculations need to be performed on dates that may be from different timezones. The DateTimeOffset value is also more portable from one computer to another than a DateTime value. DateTimeOffset doesn’t necessarily replace DateTime though. DateTime is still useful to represent a single date or time or when the timezone information is unknown or not important. However most developers should explore using DateTimeOffest more in general use especially in enterprise level systems.
DateTime may also be the best type to represent a DateTime from the database since the DateTime fields in most databases don’t store timezone information. In SQL Server 2008 a DateTimeOffset structure was introduced to the database as well. This type is very comparable to the DateTimeOffset field in .Net. So a good rule of thumb may be to use the corresponding types when mapping between SQL Server 2008 and .Net 3.5.
Update: Nicholas Allen posted a reminder of how to deal with DateTimeOffest fields when working with svcutil.exe: http://blogs.msdn.com/drnick/archive/2008/09/04/getting-better-time-formats.aspx