在 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 點開始營業)。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 包含DateTimeDateTimeOffsetTimeSpan和類型,這些都可以用來建立使用日期和時間的應用程式。TimeZoneInfo.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 does not discuss a fourth type, TimeZone, because its functionality is almost entirely incorporated in the TimeZoneInfo class. 可能的話,開發人員應該使用 TimeZoneInfo 類別而非 TimeZone 類別。Whenever possible, developers should 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. (如需在考慮時區調整規則的方式下該如何執行日期和時間運算的討論,請參閱 Performing arithmetic operations with dates and times。)(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.

值也可以做為DateTime值的取代, 而該值會反映沒有特定日期參考的時間。 TimeSpanA 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.TimeOfDay于和DateTimeOffset.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