Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo

.NET Framework applications that use date and time information are very diverse and can use that information in several ways. The more common uses of date and time information include one or more of the following:

  • To reflect a date only, so that time information is not important.

  • To reflect a time only, so that date information is not important.

  • To reflect an abstract date and time that is not tied to a specific time and place (for example, most stores in an international chain open on weekdays at 9:00 A.M.).

  • To retrieve date and time information from sources outside the .NET Framework, typically where date and time information is stored in a simple data type.

  • To uniquely and unambiguously identify a single point in time. Some applications require that a date and time be unambiguous only on the host system; others require that it be unambiguous across systems (that is, a date serialized on one system can be meaningfully deserialized and used on another system anywhere in the world).

  • To preserve multiple related times (such as the requestor's local time and the server's time of receipt for a Web request).

  • To perform date and time arithmetic, possibly with a result that uniquely and unambiguously identifies a single point in time.

The .NET Framework includes the DateTime, DateTimeOffset, and TimeZoneInfo types, all of which can be used to build applications that work with dates and times.

Note

This topic does not discuss a fourth type, TimeZone, because its functionality is almost entirely incorporated in the TimeZoneInfo class. Whenever possible, developers should use the TimeZoneInfo class instead of the TimeZone class.

The DateTime Structure

A DateTime value defines a particular date and time. Starting with version 2.0 of the .NET Framework, it includes a Kind property that provides limited information about the time zone to which that date and time belongs. The DateTimeKind value returned by the Kind property indicates whether the DateTime value represents the local time (DateTimeKind.Local), Coordinated Universal Time (UTC) (DateTimeKind.Utc), or an unspecified time (DateTimeKind.Unspecified).

The DateTime structure is suitable for applications that do the following:

  • Work with dates only.

  • Work with times only.

  • Work with abstract dates and times.

  • Retrieve date and time information from sources outside the .NET Framework, such as SQL databases. Typically, these sources store date and time information in a simple format that is compatible with the DateTime structure.

  • Perform date and time arithmetic, but are concerned with general results. For example, in an addition operation that adds six months to a particular date and time, it is often not important whether the result is adjusted for daylight saving time.

Unless a particular DateTime value represents UTC, that date and time value is often ambiguous or limited in its portability. For example, if a DateTime value represents the local time, it is portable within that local time zone (that is, if the value is deserialized on another system in the same time zone, that value still unambiguously identifies a single point in time). Outside the local time zone, that DateTime value can have multiple interpretations. If the value's Kind property is DateTimeKind.Unspecified, it is even less portable: it is now ambiguous within the same time zone and possibly even on the same system on which it was first serialized. Only if a DateTime value represents UTC does that value unambiguously identify a single point in time regardless of the system or time zone in which the value is used.

Important noteImportant Note:

When saving or sharing DateTime data, UTC should be used and the DateTime value's Kind property should be set to DateTimeKind.Utc.

The DateTimeOffset Structure

The DateTimeOffset structure represents a date and time value, together with an offset that indicates how much that value differs from UTC. Thus, the value always unambiguously identifies a single point in time.

Although the DateTimeOffset type includes most of the functionality of the DateTime type, it is not intended to replace the DateTime type in application development. Instead, it is suitable for applications that do the following:

  • Uniquely and unambiguously identify a single point in time. The DateTimeOffset type can be used to unambiguously define the meaning of "now", to log transaction times, to log the times of system or application events, and to record file creation and modification times.

  • Perform general date and time arithmetic.

  • Preserve multiple related times, as long as those times are stored as two separate values or as two members of a structure.

Note

These uses for DateTimeOffset values are much more common than those for DateTime values. As a result, DateTimeOffset should be considered the default date and time type for application development.

A DateTimeOffset value is not tied to a particular time zone, but can originate from any of a variety of time zones. To illustrate this, the following example lists the time zones to which a number of DateTimeOffset values (including a local Pacific Standard Time) can belong.

Imports System.Collections.ObjectModel

Module TimeOffsets
   Public Sub Main()
      Dim thisTime As DateTimeOffset 

      thisTime = New DateTimeOffset(#06/10/2007#, New TimeSpan(-7, 0, 0))
      ShowPossibleTimeZones(thisTime) 

      thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(-6, 0, 0))  
      ShowPossibleTimeZones(thisTime)

      thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(+1, 0, 0))
      ShowPossibleTimeZones(thisTime)
   End Sub 

   Private Sub ShowPossibleTimeZones(offsetTime As DateTimeOffset)
      Dim offset As TimeSpan = offsetTime.Offset
      Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo)

      Console.WriteLine("{0} could belong to the following time zones:", _
                        offsetTime.ToString())
      ' Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones()     
      ' Iterate time zones 
      For Each timeZone As TimeZoneInfo In timeZones
         ' Compare offset with offset for that date in that time zone 
         If timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset) Then
            Console.WriteLine("   {0}", timeZone.DisplayName)
         End If    
      Next
      Console.WriteLine()
   End Sub 
End Module 
' This example displays the following output to the console: 
'       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones: 
'          (GMT-07:00) Arizona 
'          (GMT-08:00) Pacific Time (US & Canada) 
'          (GMT-08:00) Tijuana, Baja California 
'        
'       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones: 
'          (GMT-06:00) Central America 
'          (GMT-06:00) Central Time (US & Canada) 
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New 
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old 
'          (GMT-06:00) Saskatchewan 
'        
'       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones: 
'          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna 
'          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague 
'          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris 
'          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb 
'          (GMT+01:00) West Central Africa
using System;
using System.Collections.ObjectModel;

public class TimeOffsets
{
   public static void Main()
   {
      DateTime thisDate = new DateTime(2007, 3, 10, 0, 0, 0);
      DateTime dstDate = new DateTime(2007, 6, 10, 0, 0, 0);
      DateTimeOffset thisTime;

      thisTime = new DateTimeOffset(dstDate, new TimeSpan(-7, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(-6, 0, 0));  
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(+1, 0, 0));
      ShowPossibleTimeZones(thisTime);
   }

   private static void ShowPossibleTimeZones(DateTimeOffset offsetTime)
   {
      TimeSpan offset = offsetTime.Offset;
      ReadOnlyCollection<TimeZoneInfo> timeZones;

      Console.WriteLine("{0} could belong to the following time zones:", 
                        offsetTime.ToString());
      // Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones();     
      // Iterate time zones  
      foreach (TimeZoneInfo timeZone in timeZones)
      {
         // Compare offset with offset for that date in that time zone 
         if (timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset))
            Console.WriteLine("   {0}", timeZone.DisplayName);
      }
      Console.WriteLine();
   } 
}
// This example displays the following output to the console: 
//       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones: 
//          (GMT-07:00) Arizona 
//          (GMT-08:00) Pacific Time (US & Canada) 
//          (GMT-08:00) Tijuana, Baja California 
//        
//       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones: 
//          (GMT-06:00) Central America 
//          (GMT-06:00) Central Time (US & Canada) 
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New 
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old 
//          (GMT-06:00) Saskatchewan 
//        
//       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones: 
//          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna 
//          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague 
//          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris 
//          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb 
//          (GMT+01:00) West Central Africa

The output shows that each date and time value in this example can belong to at least three different time zones. The DateTimeOffset value of 6/10/2007 shows that if a date and time value represents a daylight saving time, its offset from UTC does not even necessarily correspond to the originating time zone's base UTC offset or to the offset from UTC found in its display name. This means that, because a single DateTimeOffset value is not tightly coupled with its time zone, it cannot reflect a time zone's transition to and from daylight saving time. This can be particularly problematic when date and time arithmetic is used to manipulate a DateTimeOffset value. (For a discussion of how to perform date and time arithmetic in a way that takes account of a time zone's adjustment rules, see Performing Arithmetic Operations with Dates and Times.)

The TimeZoneInfo Class

The TimeZoneInfo class represents any of the earth's time zones, and enables the conversion of any date and time in one time zone to its equivalent in another time zone. The TimeZoneInfo class makes it possible to work with dates and times so that any date and time value unambiguously identifies a single point in time. The TimeZoneInfo class is also extensible. Although it depends on time zone information provided for Windows systems and defined in the registry, it supports the creation of custom time zones. It also supports the serialization and deserialization of time zone information.

In some cases, taking full advantage of the TimeZoneInfo class may require further development work. First, date and time values are not tightly coupled with the time zones to which they belong. As a result, unless your application provides some mechanism for linking a date and time with its associated time zone, it is easy for a particular date and time value to become disassociated from its time zone. (One method of linking this information is to define a class or structure that contains both the date and time value and its associated time zone object.) Second, Windows XP and earlier versions of Windows have no real support for historical time zone information, and Windows Vista has only limited support. Applications that are designed to handle historical dates and times must make extensive use of custom time zones.

Taking advantage of time zone support in the .NET Framework is possible only if the time zone to which a date and time value belongs is known when that date and time object is instantiated. This is often not the case, particularly in Web or network applications.

See Also

Other Resources

Times and Time Zones