在 DateTime、DateTimeOffset 和 TimeZoneInfo 之間選擇
使用日期和時間資訊的 .NET Framework 應用程式有很多種,而且可以利用幾種不同方式使用該資訊。 日期和時間資訊的常見使用方式包括下列一項或多項:
只反映日期,因此時間資訊不重要。
只反映時間,因此日期資訊不重要。
反映絕對日期和時間,該日期和時間不屬於特定時間和地點 (例如,跨國連鎖的大多數商店都在星期一至五早上 9:00 開門營業)。
從 .NET Framework 以外的來源擷取日期和時間資訊,在這些來源中,日期和時間資訊通常是以簡單的資料型別儲存。
以唯一且明確的方式識別單一時間點。 有些應用程式只需要主機系統上有明確的日期和時間,有些則需要所有系統都有明確的日期和時間 (也就是說,在某一系統上序列化的日期,可在全球任何一處的另一個系統上有意義地還原序列化和使用)。
保留多個相關時間 (例如要求者的本地時間以及伺服器收到 Web 要求的時間)。
執行日期和時間運算,可能是對唯一且明確識別單一時間點的結果執行。
.NET Framework 包含 DateTime、DateTimeOffset 和 TimeZoneInfo 型別,這些型別都可用來建置使用日期和時間的應用程式。
注意事項 |
---|
本主題不討論第四個型別 TimeZone,因為它的功能幾乎已完全整合到 TimeZoneInfo 類別。開發人員應盡可能使用 TimeZoneInfo 類別,而不要使用 TimeZone 類別。 |
DateTime 結構
定義特定日期和時間的 DateTime 值。 從 .NET Framework 2.0 版開始,此結構加入了 Kind 屬性,此屬性可對日期和時間所屬的時區提供有限的資訊。 Kind 屬性傳回的 DateTimeKind 值,會指出 DateTime 值所表示的是本地時間 (DateTimeKind.Local)、國際標準時間 (UTC) (DateTimeKind.Utc) 或未指定的時間 (DateTimeKind.Unspecified)。
DateTime 結構適用於執行下列作業的應用程式:
只使用日期。
只使用時間。
使用絕對日期和時間。
從 .NET Framework 以外的來源 (例如 SQL 資料庫) 擷取日期和時間資訊。 一般來說,這些來源會使用與 DateTime 結構相容的簡單格式儲存日期和時間資訊。
執行日期和時間運算,但通常與運算結果相關。 例如,在將六個月加到特定日期和時間的加法運算中,結果是否調整為日光節約時間通常並不重要。
除非特定 DateTime 值表示的是 UTC,否則該日期和時間值通常是模稜兩可或可攜性有限。 例如,如果 DateTime 值表示本地時間,則在該本地時區內具可攜性 (也就是說,如果此值在同時區的另一個系統上還原序列化,則仍可明確識別某個單一時間點)。 在本地時區以外,該 DateTime 值就可能有多種解讀。 如果值的 Kind 屬性為 DateTimeKind.Unspecified,那麼可攜性就更低:現在它在同時區內會是模稜兩可的,甚至可能在最初序列化的同一個系統上也是如此。 只有在 DateTime 值表示 UTC 的情況下,該值才能在明確識別單一時間點,不論使用它的系統或時區為何。
重要事項 |
---|
儲存或共用 DateTime 資料時,應該使用 UTC,並將 DateTime 值的 Kind 屬性設為 DateTimeKind.Utc。 |
DateTimeOffset 結構
DateTimeOffset 結構表示日期和時間值,以及指出該值與 UTC 時差的位移。 因此,此值永遠會以明確方式識別單一時間點。
雖然 DateTimeOffset 型別包含 DateTime 型別的大部分功能,但它並不是要用來在應用程式開發取代 DateTime 型別。 反之,它適合用於執行下列作業的應用程式:
以唯一且明確的方式識別單一時間點。 DateTimeOffset 型別可用來明確定義「現在」的意義,記錄交易時間、記錄系統或應用程式事件的時間,以及記錄檔案建立和修改的時間。
執行一般日期和時間運算。
保留多個相關時間,但前提是這些時間儲存成兩個不同的值,或是結構的兩個成員。
注意事項 |
---|
以上使用 DateTimeOffset 值的情形要比使用 DateTime 值常見許多。因此,在進行應用程式開發時,請考慮使用 DateTimeOffset 做為預設日期和時間型別。 |
DateTimeOffset 值不屬於特定的時區,但可能來自各個不同的時區。 為說明此點,下列範例列出了數個 DateTimeOffset 值 (包括本地太平洋標準時間) 可能屬於的時區。
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
從輸出可以看到,這個範例中的每個日期和時間值都可能屬於至少三個不同的時區。 DateTimeOffset 值 6/10/2007 顯示,如果日期和時間值表示的是日光節約時間,它與 UTC 之間的位移甚至不一定相當於原始時區的基礎 UTC 位移,或是與其顯示名稱找到的 UTC 之間的位移。 這表示,由於單一 DateTimeOffset 值並未與其時區緊密連結,因此無法反映出時區與日光節約時間的來回轉換。 當使用日期和時間計算 DateTimeOffset 值的時候,問題就更為明顯 (如需如何在執行日期和時間運算時,將時區的調整規則列入考量的討論,請參閱使用日期和時間執行算術運算)。
TimeZoneInfo 類別
TimeZoneInfo 類別可表示全球任何時區,並可將某一時區的任何日期和時間轉換成另一個時區的日期和時間。 TimeZoneInfo 類別可在使用日期和時間時,讓任何日期和時間值都明確識別某個單一時間點。 TimeZoneInfo 類別也是可以延伸的。 雖然它依賴提供給 Windows 系統以及定義在登錄中的時間資訊,但也支援建立自訂時區。 此外也支援時區資訊的序列化和還原序列化。
在某些情況下,要完整利用 TimeZoneInfo 類別,可能需要額外的開發工作。 首先,日期和時間值並未與其所屬時區緊密結合。 因此,除非應用程式提供機制來將日期和時間與其相關時區連結,否則特定日期和時間值就很容易切斷與其時區的關聯 (連結此資訊的其中一個方法是定義類別或結構,其中同時包含日期和時間值以及其相關的時區物件)。其次,Windows XP 和更早的 Windows 版本並沒有針對歷史時區資訊提供支援,而 Windows Vista 的支援則有限。 設計處理歷史日期和時間的應用程式勢必要使用大量的自訂時區。
要利用 .NET Framework 中的時區支援,就必須在具現化日期和時間物件時,知道日期和時間值屬於哪一個時區。 但通常情形並非如此,尤其是在 Web 或網路應用程式中。