選擇 DateTime、DateOnly、DateTimeOffset、TimeSpan、TimeOnly 和 TimeZoneInfo
.NET 應用程式可以使用數種方式的日期和時間資訊。 日期與時間資訊的常見用法包括:
- 只反映日期,讓時間資訊不重要。
- 只反映時間,使日期資訊不重要。
- 為了反映一個不受特定時間和地點限制的抽象日期和時間(例如,在國際連鎖店中,大多數商店會在平日早上9點開門)。
- 若要從 .NET 外部的來源擷取日期和時間資訊,通常日期和時間資訊會儲存在簡單數據類型中。
- 若要唯一且明確地識別單一時間點。 有些應用程式要求日期和時間只在主機系統上明確。 其他應用程式要求在跨系統時明確表示(也就是說,在一個系統上序列化的日期可以在世界上任何地方的另一個系統上被成功反序列化並使用)。
- 若要保留多個相關時間(例如要求者的當地時間,以及伺服器收到 Web 要求的時間)。
- 要執行日期和時間運算,並得到能唯一且明確識別單一時間點的結果。
.NET 包含 DateTime、DateOnly、DateTimeOffset、TimeSpan、TimeOnly和 TimeZoneInfo 類型,所有這些類型都可以用來建置使用日期和時間的應用程式。
注意
本文不會討論 TimeZone,因為它的功能幾乎完全併入 TimeZoneInfo 類別中。 盡可能使用 TimeZoneInfo 類別,而不是 TimeZone 類別。
DateTimeOffset 結構
DateTimeOffset 結構代表日期和時間值,並包括一個位移,用以指出該值與UTC之間的差異。 因此,值一律明確識別單一時間點。
DateTimeOffset 類型包含 DateTime 類型的所有功能,以及時區感知。 這適用於下列應用程式:
- 唯一且明確地識別單一時間點。 DateTimeOffset 類型可用來明確定義「現在」的意義、記錄事務時間、記錄系統或應用程式事件的時間,以及記錄檔案建立和修改時間。
- 執行一般日期和時間算術。
- 保留多個相關的時間,只要這些時間以兩個獨立的值或結構中的兩個成員形式儲存。
注意
這些用於 DateTimeOffset 值的用法比 DateTime 值更常見。 因此,請考慮 DateTimeOffset 作為應用程式開發的預設日期和時間類型。
DateTimeOffset 值不會系結至特定時區,但可能源自各種時區。 下列範例會列出一些 DateTimeOffset 值(包括當地太平洋標準時間)可以所屬的時區。
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
輸出顯示此範例中的每個日期和時間值至少可以屬於三個不同的時區。 DateTimeOffset 值在 2007/6/10 顯示,如果日期和時間值代表夏令時間,那麼其與 UTC 的偏差甚至不需要對應於原始時區的基本 UTC 偏差,或者其顯示名稱中的 UTC 偏差。 因為單一 DateTimeOffset 值與其時區沒有緊密結合,所以無法反映時區進入或結束夏令時間的轉換。 當日期和時間算術用來操作 DateTimeOffset 值時,這可能會有問題。 如需如何以考慮時區調整規則的方式來執行日期和時間算術的討論,請參閱 使用日期和時間執行算術運算。
DateTime 結構
DateTime 值會定義特定的日期和時間。 它包含 Kind 屬性,可提供該日期和時間所屬時區的有限資訊。 DateTimeKind 屬性傳回的 Kind 值表示 DateTime 值是否代表當地時間(DateTimeKind.Local)、國際標準時間(UTC)(DateTimeKind.Utc)或未指定的時間(DateTimeKind.Unspecified)。
DateTime 結構適用於具有下列一或多個特性的應用程式:
- 處理抽象的日期和時間。
- 處理缺少時區資訊的日期和時間。
- 務必僅使用 UTC 日期和時間。
- 執行日期和時間的運算,但考慮整體結果。 例如,在將六個月新增至特定日期和時間的加法作業中,是否調整日光節約時間的結果通常並不重要。
除非特定 DateTime 值代表 UTC,否則該日期和時間值在可移植性中通常模棱兩可或有限。 例如,如果 DateTime 值代表當地時間,則會在該當地時區內進行可移植性(也就是說,如果該值在相同時區的另一個系統上還原串行化,該值仍明確識別單一時間點)。 在本機時區之外,DateTime 值可以有多個解譯。 如果值的 Kind 屬性是 DateTimeKind.Unspecified,那麼它甚至更加不便於攜帶:現在,即便在相同的時區內,或者甚至是在最初進行序列化的相同系統上,它也是模棱兩可的。 只有當 DateTime 值代表 UTC 時,該值才會明確識別單一時間點,而不論使用該值的系統或時區為何。
重要
儲存或共享 DateTime 資料時,請使用 UTC 並將 DateTime 值的 Kind 屬性設定為 DateTimeKind.Utc。
DateOnly 結構
DateOnly 結構表示特定的日期,不包含時間。 因為它沒有時間元件,所以代表從一天開始到一天結束的日期。 此結構很適合用來儲存特定日期,例如出生日期、周年日期、假日或商務相關日期。
雖然您可以在忽略時間元件時使用 DateTime
,但使用 DateOnly
比 DateTime
有幾個優點:
- 如果
DateTime
結構因時區偏移,可能會移動到前一天或隔天。DateOnly
無法由時區位移,而且一律代表所設定的日期。 - 串行化
DateTime
結構包含時間元件,這可能會遮蔽數據的意圖。 此外,DateOnly
序列化較少的數據。 - 當程式代碼與 SQL Server 等資料庫互動時,整個日期通常會儲存為
date
數據類型,但不包含時間。DateOnly
更符合資料庫類型。
如需 DateOnly
的詳細資訊,請參閱 如何使用 DateOnly 和 TimeOnly 結構。
重要
DateOnly
不適用於 .NET Framework。
TimeSpan 結構
TimeSpan 的結構代表一個時間間隔。 其兩個一般用途如下:
- 反映兩個日期和時間值之間的時間間隔。 例如,從另一個值減去一個 DateTime 值會傳回 TimeSpan 值。
- 測量經過的時間。 例如,Stopwatch.Elapsed 屬性會傳回 TimeSpan 值,這個值會反映自從呼叫其中一個開始測量經過時間的 Stopwatch 方法之後所經過的時間間隔。
當該值反映某個時間而不參考特定日期時,TimeSpan 值也可以用來取代 DateTime 值。 此使用方式類似於 DateTime.TimeOfDay 和 DateTimeOffset.TimeOfDay 屬性,其會傳回 TimeSpan 值,代表沒有參考日期的時間。 例如,TimeSpan 結構可用來反映商店的每日開啟或關閉時間,或是用來表示任何一般事件發生的時間。
下列範例會定義 StoreInfo
結構,其中包含用於存放區開啟和關閉時間 TimeSpan 物件,以及代表存放區時區的 TimeZoneInfo 物件。 結構也包含兩種方法,IsOpenNow
和 IsOpenAt
,指出存放區是否在使用者指定的時間開啟,該使用者假設位於當地時區。
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()[tz.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 AndAlso 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(tz.GetAdjustmentRules().Length - 1).DaylightDelta
End If
Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
Return (comparisonTime >= open AndAlso comparisonTime <= close)
End If
End Function
End Structure
然後,用戶端程式代碼可以使用 StoreInfo
結構,如下所示。
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: True
// Store is open at 04:59:00: False
// Store is open at 18:31:00: True
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
TimeOnly 結構
TimeOnly 結構代表一天時間值,例如每日鬧鐘或您每天吃午餐的時間。
TimeOnly
僅限於 00:00:00.0000000 - 23:59:59.9999999範圍,特定一天中的時刻。
在引進 TimeOnly
類型之前,程式設計人員通常會使用 DateTime 類型或 TimeSpan 類型來代表特定時間。 不過,使用這些結構來模擬沒有日期的時間,可能會造成一些問題,TimeOnly
解決:
-
TimeSpan
代表經過的時間,例如使用秒表測量的時間。 上限範圍超過 29,000 年,而且其值可以是負值,表示在時間中向後移動。 負TimeSpan
不會指出一天中的特定時間。 - 如果
TimeSpan
用作一天的時間,有可能被操控為超出 24 小時範圍的值。TimeOnly
沒有這種風險。 例如,如果員工的工作班次從 18:00 開始,且持續 8 小時,將 8 小時新增至TimeOnly
結構後,時間會移轉到 2:00。 - 使用
DateTime
一天中的時間,需將某個任意日期關聯到時間,然後再予以忽略。 選擇DateTime.MinValue
(0001-01-01-01) 作為日期是常見的做法,不過,如果從DateTime
值減去小時,可能會發生OutOfRange
例外狀況。TimeOnly
沒有這個問題,因為時間在24小時內來回循環。 - 串行化
DateTime
結構包含日期元件,這可能會遮蔽數據的意圖。 此外,TimeOnly
序列化較少的數據。
如需 TimeOnly
的詳細資訊,請參閱 如何使用 DateOnly 和 TimeOnly 結構。
重要
TimeOnly
不適用於 .NET Framework。
TimeZoneInfo 類別
TimeZoneInfo 類別代表地球的任何時區,並可讓您將某個時區中的任何日期和時間轉換成另一個時區的對等時區。 TimeZoneInfo 類別可讓您使用日期和時間,讓任何日期和時間值明確識別單一時間點。 TimeZoneInfo 類別也是可延伸的。 雖然它相依於 Windows 系統提供的時區資訊,並在登錄中定義,但它支援建立自定義時區。 它也支援時區資訊的序列化和反序列化。
在某些情況下,充分利用 TimeZoneInfo 類別可能需要進一步的開發工作。 如果日期和時間值與所屬的時區沒有緊密結合,則需要進一步的工作。 除非您的應用程式提供一些機制來連結日期和時間與其相關聯的時區,否則特定日期和時間值很容易與時區解除關聯。 連結這項資訊的其中一種方法是定義類別或結構,其中包含日期和時間值及其相關聯的時區物件。
若要利用 .NET 中的時區支援,您必須知道日期和時間值在具現化該日期和時間物件時所屬的時區。 時區通常不為人知,特別是在 Web 或網路應用程式中。