在 DateTime、DateTimeOffset、TimeSpan 和 TimeZoneInfo 之间进行选择Choosing between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo

使用日期和时间信息的 .NET 应用程序多种多样,并可以多种方式使用此类信息。.NET 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.

  • 反映未绑定到特定时间和位置的抽象的日期和时间(例如,国际上大多数连锁店在工作日上午 9:00 开张)。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.).

  • 若要从 .NET 以外的源中检索日期和时间信息,通常是在简单的数据类型中存储日期和时间信息。To retrieve date and time information from sources outside of .NET, 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).

  • 保留多个相关时间(例如请求程序的本地时间和服务器接收 Web 请求的时间)。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.

.NET 包含 DateTimeDateTimeOffsetTimeSpanTimeZoneInfo 类型,所有这些类型都可用于生成处理日期和时间的应用程序。.NET includes the DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo types, all of which can be used to build applications that work with dates and times.

备注

本主题不讨论 TimeZone,因为其功能几乎完全融入 TimeZoneInfo 类中。This topic doesn't discuss TimeZone because its functionality is almost entirely incorporated in the TimeZoneInfo class. 请尽可能使用 TimeZoneInfo 类,而不是 TimeZone 类。Whenever possible, use the TimeZoneInfo class instead of the TimeZone class.

DateTime 结构The DateTime structure

DateTime 值定义特定日期和时间。A DateTime value defines a particular date and time. 它包含 Kind 属性,该属性提供有关日期和时间所属时区的有限信息。It includes a Kind property that provides limited information about the time zone to which that date and time belongs. DateTimeKind 属性返回的 Kind 值指示 DateTime 值是表示本地时间 (DateTimeKind.Local)、协调世界时 (UTC) (DateTimeKind.Utc) 还是未指定的时间 (DateTimeKind.Unspecified)。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).

DateTime 结构适用于执行以下操作的应用程序: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.

  • 使用缺少时区信息的日期和时间。Work with dates and times for which time zone information is missing.

  • 只使用 UTC 日期和时间。Work with UTC dates and times only.

  • 从 .NET 外部的源(如 SQL 数据库)中检索日期和时间信息。Retrieve date and time information from sources outside of .NET, such as SQL databases. 通常,这些源按与 DateTime 结构兼容的简单格式存储日期和时间信息。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.

除非特定的 DateTime 值表示 UTC,否则日期和时间值通常不明确的或其可移植性受限。Unless a particular DateTime value represents UTC, that date and time value is often ambiguous or limited in its portability. 例如,如果 DateTime 值表示本地时间,则在该本地时区内可移植(即,如果在其他系统的相同时区上反序列化此值,它仍然明确标识单个时间点)。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). 在本地时区之外, DateTime 值可以具有多个解释。Outside the local time zone, that DateTime value can have multiple interpretations. 如果值的 Kind 属性是 DateTimeKind.Unspecified,则它的可移植性更低:此时它在相同时区内(可能甚至在首次序列化的同一系统上)是不明确的。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. 仅当 DateTime 值表示 UTC 时,无论使用此值的系统和时区如何,它都会明确标识单个时间点。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.

重要

在保存或共享 DateTime 数据时,应使用 UTC 并将 DateTime 值的 Kind 属性设置为 DateTimeKind.UtcWhen saving or sharing DateTime data, UTC should be used and the DateTime value's Kind property should be set to DateTimeKind.Utc.

DateTimeOffset 结构The DateTimeOffset structure

DateTimeOffset 结构表示日期和时间值,以及指示此值与 UTC 的差异程度的偏移量。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.

DateTimeOffset 类型包括 DateTime 类型的所有功能以及时区感知功能。The DateTimeOffset type includes all of the functionality of the DateTime type along with time zone awareness. 这使它适用于执行以下操作的应用程序:This makes it suitable for applications that do the following:

  • 唯一、明确地标识单个时间点。Uniquely and unambiguously identify a single point in time. DateTimeOffset 类型可用于明确定义“现在”的含义、记录事务时间、记录系统或应用程序事件时间以及记录创建和修改时间。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.

备注

DateTimeOffset 值的使用频率比 DateTime 值的更高。These uses for DateTimeOffset values are much more common than those for DateTime values. 因此,在应用程序开发中应考虑使用 DateTimeOffset 作为默认日期和时间类型。As a result, DateTimeOffset should be considered the default date and time type for application development.

DateTimeOffset 值不限于特定时区,而可以来自各个时区。A DateTimeOffset value is not tied to a particular time zone, but can originate from any of a variety of time zones. 为了说明这一点,以下示例列出了 DateTimeOffset 值(包括本地太平洋标准时间)可属于的时区。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.

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
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

输出显示,此示例中的每个日期和时间值至少可以属于三个不同时区。The output shows that each date and time value in this example can belong to at least three different time zones. 2007 年 6 月 10 日的 DateTimeOffset 值显示,如果日期和时间值表示夏令时,则其相对于 UTC 的偏移量甚至不一定对应于原始时区的基本 UTC 偏移量或其显示名称中找到的相对于 UTC 的偏移量。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. 这意味着,因为单个 DateTimeOffset 值与其时区并非紧密耦合的,因此它无法反映到/从夏令时的时区转换。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. 这在使用日期和时间运算操纵 DateTimeOffset 值时特别容易出现问题。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.

TimeSpan 结构The TimeSpan structure

TimeSpan 结构表示时间间隔。The TimeSpan structure represents a time interval. 它的两个典型用途是:Its two typical uses are:

  • 反映两个日期和时间值之间的时间间隔。Reflecting the time interval between two date and time values. 例如,两个 DateTime 值相减将返回 TimeSpan 值。For example, subtracting one DateTime value from another returns a TimeSpan value.

  • 测量运行时间。Measuring elapsed time. 例如,Stopwatch.Elapsed 属性返回一个 TimeSpan 值,该值反映自调用开始测量运行时间的其中一种 Stopwatch 方法以来所经过的时间间隔。For example, the Stopwatch.Elapsed property returns a TimeSpan value that reflects the time interval that has elapsed since the call to one of the Stopwatch methods that begins to measure elapsed time.

如果值反映不引用特定日期的时间,还可以使用 TimeSpan 值作为 DateTime 值的替代值。A TimeSpan value can also be used as a replacement for a DateTime value when that value reflects a time without reference to a particular day. 此用法与 DateTime.TimeOfDayDateTimeOffset.TimeOfDay 属性相似,后者返回一个 TimeSpan 值,该值表示不引用日期的时间。This usage is similar to the DateTime.TimeOfDay and DateTimeOffset.TimeOfDay properties, which return a TimeSpan value that represents the time without reference to a date. 例如, TimeSpan 结构可用于反映商店每天的开张或打烊时间,还可用来表示任何常规事件发生的时间。For example, the TimeSpan structure can be used to reflect a store's daily opening or closing time, or it can be used to represent the time at which any regular event occurs.

以下示例定义了 StoreInfo 结构,其中包含表示商店开张或打烊时间的 TimeSpan 对象,以及表示商店所在时区的 TimeZoneInfo 对象。The following example defines a StoreInfo structure that includes TimeSpan objects for store opening and closing times, as well as a TimeZoneInfo object that represents the store's time zone. 此结构还包含两个方法(IsOpenNowIsOpenAt),用于指示假定用户处于本地时区时其所指定的时间商店是否开张。The structure also includes two methods, IsOpenNow and IsOpenAt, that indicates whether the store is open at a time specified by the user, who is assumed to be in the local time zone.

using System;

public struct StoreInfo
{
   public String store;
   public TimeZoneInfo tz;
   public TimeSpan open;
   public TimeSpan close;

   public bool IsOpenNow()
   {
      return IsOpenAt(DateTime.Now.TimeOfDay);
   }
   
   public bool IsOpenAt(TimeSpan time)
   {
      TimeZoneInfo local = TimeZoneInfo.Local;
      TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;

      // Is the store in the same time zone?
      if (tz.Equals(local)) {
         return time >= open & time <= close;
      }
      else {
         TimeSpan delta = TimeSpan.Zero;
         TimeSpan storeDelta = TimeSpan.Zero;
         
         // Is it daylight saving time in either time zone?
         if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
            delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
            storeDelta = tz.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
         return comparisonTime >= open & comparisonTime <= close;
      }
   }
}
Public Structure StoreInfo
   Dim store As String
   Dim tz As TimeZoneInfo
   Dim open As TimeSpan
   Dim close As TimeSpan

   Public Function IsOpenNow() As Boolean
      Return IsOpenAt(Date.Now.TimeOfDay)
   End Function
   
   Public Function IsOpenAt(time As TimeSpan) As Boolean
      Dim local As TimeZoneInfo = TimeZoneInfo.Local
      Dim offset As TimeSpan = TimeZoneInfo.Local.BaseUtcOffset

      ' Is the store in the same time zone?
      If tz.Equals(local) Then
         Return time >= open And time <= close
      Else
         Dim delta As TimeSpan = TimeSpan.Zero
         Dim storeDelta As TimeSpan = TimeSpan.Zero
         
         ' Is it daylight saving time in either time zone?
         If local.IsDaylightSavingTime(Date.Now.Date + time) Then
            delta = local.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
         End If
         If tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(Date.Now.Date + time, local, tz))
            storeDelta = tz.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
         End If
         Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
         Return (comparisonTime >= open And comparisonTime <= close)
      End If
   End Function
End Structure

随后, StoreInfo 结构可由客户端代码按如下方式使用。The StoreInfo structure can then be used by client code like the following.

public class Example
{
   public static void Main()
   {
      // Instantiate a StoreInfo object.
      var store103 = new StoreInfo();
      store103.store = "Store #103";
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
      // Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0);
      // Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0);
      
      Console.WriteLine("Store is open now at {0}: {1}",
                        DateTime.Now.TimeOfDay, store103.IsOpenNow());
      TimeSpan[] times = { new TimeSpan(8, 0, 0), new TimeSpan(21, 0, 0),
                           new TimeSpan(4, 59, 0), new TimeSpan(18, 31, 0) };
      foreach (var time in times)
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time));
   }
}
// The example displays the following output:
//       Store is open now at 15:29:01.6129911: True
//       Store is open at 08:00:00: True
//       Store is open at 21:00:00: False
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: False
Module Example
   Public Sub Main()
      ' Instantiate a StoreInfo object.
      Dim store103 As New StoreInfo()
      store103.store = "Store #103"
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
      ' Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0)
      ' Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0)
      
      Console.WriteLine("Store is open now at {0}: {1}",
                        Date.Now.TimeOfDay, store103.IsOpenNow())
      Dim times() As TimeSpan = { New TimeSpan(8, 0, 0),
                                  New TimeSpan(21, 0, 0),
                                  New TimeSpan(4, 59, 0),
                                  New TimeSpan(18, 31, 0) }
      For Each time In times
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time))
      Next
   End Sub
End Module
' The example displays the following output:
'       Store is open now at 15:29:01.6129911: True
'       Store is open at 08:00:00: True
'       Store is open at 21:00:00: False
'       Store is open at 04:59:00: False
'       Store is open at 18:31:00: False

TimeZoneInfo 类The TimeZoneInfo class

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 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. 借助 TimeZoneInfo 类,即可处理日期和时间,以使任何日期和时间值均明确标识单个时间点。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. TimeZoneInfo 类也可扩展。The TimeZoneInfo class is also extensible. 虽然它依赖于为 Windows 系统提供且在注册表中定义的时区信息,但仍然支持创建自定义时区。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.

在某些情况下,可能需要进一步的开发工作才可以充分利用 TimeZoneInfo 类。In some cases, taking full advantage of the TimeZoneInfo class may require further development work. 如果日期和时间值不与其所属的时区紧密耦合,则需要进一步的工作。If date and time values are not tightly coupled with the time zones to which they belong, further work is required. 除非您的应用程序提供某种机制来将日期和时间与其关联的时区链接在一起,否则特定日期和时间值很容易与其时区无关。Unless your application provides some mechanism for linking a date and time with its associated time zone, it's 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.

仅当日期和时间对象实例化时其值所属的时区已知,才可使用 .NET 中的时区支持。Taking advantage of time zone support in .NET 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. 这通情况并非如此,尤其是在 Web 或网络应用程序中。This is often not the case, particularly in Web or network applications.

请参阅See also