DateTime雖然 和 DateTimeOffset 結構都提供對其值執行算術運算的成員,但算術運算的結果大不相同。 本文會檢查這些差異,並將其與日期和時間數據中的時區感知程度產生關聯,並討論如何使用日期和時間數據執行完整的時區感知作業。
使用 DateTime 值的比較和算術運算
屬性 DateTime.Kind 允許將 DateTimeKind 值指派給日期和時間,以指出它是否代表當地時間、國際標準時間(UTC)或未指定時區中的時間。 不過,在比較或對 DateTimeKind 值執行日期和時間運算時,這有限的時區資訊會被忽略。 下列範例會比較目前的當地時間與目前的UTC時間,說明如何忽略時區資訊。
using System;
public enum TimeComparison2
{
EarlierThan = -1,
TheSameAs = 0,
LaterThan = 1
}
public class DateManipulation
{
public static void Main()
{
DateTime localTime = DateTime.Now;
DateTime utcTime = DateTime.UtcNow;
Console.WriteLine("Difference between {0} and {1} time: {2}:{3} hours",
localTime.Kind,
utcTime.Kind,
(localTime - utcTime).Hours,
(localTime - utcTime).Minutes);
Console.WriteLine("The {0} time is {1} the {2} time.",
localTime.Kind,
Enum.GetName(typeof(TimeComparison2), localTime.CompareTo(utcTime)),
utcTime.Kind);
}
}
// If run in the U.S. Pacific Standard Time zone, the example displays
// the following output to the console:
// Difference between Local and Utc time: -7:0 hours
// The Local time is EarlierThan the Utc time.
Public Enum TimeComparison As Integer
EarlierThan = -1
TheSameAs = 0
LaterThan = 1
End Enum
Module DateManipulation
Public Sub Main()
Dim localTime As Date = Date.Now
Dim utcTime As Date = Date.UtcNow
Console.WriteLine("Difference between {0} and {1} time: {2}:{3} hours", _
localTime.Kind.ToString(), _
utcTime.Kind.ToString(), _
(localTime - utcTime).Hours, _
(localTime - utcTime).Minutes)
Console.WriteLine("The {0} time is {1} the {2} time.", _
localTime.Kind.ToString(), _
[Enum].GetName(GetType(TimeComparison), localTime.CompareTo(utcTime)), _
utcTime.Kind.ToString())
' If run in the U.S. Pacific Standard Time zone, the example displays
' the following output to the console:
' Difference between Local and Utc time: -7:0 hours
' The Local time is EarlierThan the Utc time.
End Sub
End Module
方法 CompareTo(DateTime) 會報告當地時間早於UTC時間(或小於)UTC時間,減法運算表示美國太平洋標準時區系統UTC與當地時間之間的差異為7小時。 但是,由於這兩個值提供單一時間點的不同表示法,因此在此案例中很明顯,時間間隔完全歸因於當地時區與 UTC 的位移。
更一般而言,DateTime.Kind 屬性不會影響比較和算術方法所傳回的 Kind 結果(如同時間點的兩個相同點的比較所示),雖然可能會影響這些結果的解釋。 例如:
任何在兩個日期和時間值上執行的算術運算結果,其前提是兩者的DateTime.Kind屬性都等於DateTimeKind,這將反映兩個值之間的實際時間間隔。 同樣地,兩個這類日期和時間值的比較會準確地反映時間之間的關聯性。
在兩個日期和時間值上執行的任何算術或比較運算結果,其 DateTime.Kind 屬性同時等於 DateTimeKind 或兩個具有不同 DateTime.Kind 屬性值的日期和時間值,反映了兩個值之間的時鐘時間差異。
當地日期和時間值的算術或比較運算不會考慮特定值是否模棱兩可或無效,也不會考慮從當地時區轉換到日光節約時間所產生的任何調整規則的效果。
任何比較或計算 UTC 與當地時間差異的作業,都包含與結果中當地時區與 UTC 位移相等的時間間隔。
任何比較或計算未指定時間與 UTC 或當地時間之間的差異的作業,都反映簡單的時鐘時間。 不會考慮時區差異,而且結果不會反映時區調整規則的應用。
任何比較或計算兩個未指定時間差異的作業,都可能包含一個未知的間隔,反映兩個不同時區中時間之間的差異。
有許多案例的時區差異不會影響日期和時間計算(如需其中一些案例的討論,請參閱 在 DateTime、DateTimeOffset、TimeSpan 和 TimeZoneInfo 之間選擇),或日期和時間數據的內容定義比較或算術運算的意義。
使用 DateTimeOffset 值的比較和算術運算
值 DateTimeOffset 不僅包含日期和時間,也包含明確定義與 UTC 相對日期和時間的位移。 這個位移可以讓您用不同於 DateTime 值的方式來定義相等。 如果 DateTime 值具有相同的日期和時間值, DateTimeOffset 則值相等,如果兩者都參考相同的時間點,則值會相等。 在比較和大部分算術運算中用來判斷兩個日期和時間之間的間隔時, DateTimeOffset 值會更準確且更不需要解譯。 下列範例 DateTimeOffset 相當於上一個比較本機和UTC DateTimeOffset 值的範例,說明這種行為差異。
using System;
public enum TimeComparison
{
EarlierThan = -1,
TheSameAs = 0,
LaterThan = 1
}
public class DateTimeOffsetManipulation
{
public static void Main()
{
DateTimeOffset localTime = DateTimeOffset.Now;
DateTimeOffset utcTime = DateTimeOffset.UtcNow;
Console.WriteLine($"Difference between local time and UTC: {(localTime - utcTime).Hours}:{(localTime - utcTime).Minutes:D2} hours");
Console.WriteLine($"The local time is {Enum.GetName(typeof(TimeComparison), localTime.CompareTo(utcTime))} UTC.");
}
}
// Regardless of the local time zone, the example displays
// the following output to the console:
// Difference between local time and UTC: 0:00 hours.
// The local time is TheSameAs UTC.
Public Enum TimeComparison As Integer
EarlierThan = -1
TheSameAs = 0
LaterThan = 1
End Enum
Module DateTimeOffsetManipulation
Public Sub Main()
Dim localTime As DateTimeOffset = DateTimeOffset.Now
Dim utcTime As DateTimeOffset = DateTimeOffset.UtcNow
Console.WriteLine("Difference between local time and UTC: {0}:{1:D2} hours.", _
(localTime - utcTime).Hours, _
(localTime - utcTime).Minutes)
Console.WriteLine("The local time is {0} UTC.", _
[Enum].GetName(GetType(TimeComparison), localTime.CompareTo(utcTime)))
End Sub
End Module
' Regardless of the local time zone, the example displays
' the following output to the console:
' Difference between local time and UTC: 0:00 hours.
' The local time is TheSameAs UTC.
' Console.WriteLine(e.GetType().Name)
在此範例中,CompareTo 方法指出目前的當地時間和目前的 UTC 時間相等,而 CompareTo(DateTimeOffset) 的減法則表示這兩個時間之間的差異為 TimeSpan.Zero。
使用 DateTimeOffset 值進行日期和時間運算的主要限制在於,儘管 DateTimeOffset 值具有某種程度的時區感知,卻未能完全感知到所有時區。 雖然DateTimeOffset值的位移在變數DateTimeOffset第一次被指派時反映了時區相對於 UTC 的位移,但之後它會與該時區解除關聯。 因為它不再與可識別的時間直接關聯,所以日期和時間間隔的加減不會考慮時區的調整規則。
為了說明,美國中部標準時區在 2008 年 3 月 9 日上午 2:00 進入夏令時間。 有鑑於此,在 2008 年 3 月 9 日上午 1:30 的中央標準時間加上兩個半小時,應該會是 2008 年 3 月 9 日上午 5:00。 不過,如下列範例所示,加法的結果為 2008 年 3 月 9 日上午 4:00。 此作業的結果確實代表正確的時間點,雖然這不是我們感興趣的時區時間(也就是說,它沒有預期的時區位移)。
using System;
public class IntervalArithmetic
{
public static void Main()
{
DateTime generalTime = new DateTime(2008, 3, 9, 1, 30, 0);
const string tzName = "Central Standard Time";
TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);
// Instantiate DateTimeOffset value to have correct CST offset
try
{
DateTimeOffset centralTime1 = new DateTimeOffset(generalTime,
TimeZoneInfo.FindSystemTimeZoneById(tzName).GetUtcOffset(generalTime));
// Add two and a half hours
DateTimeOffset centralTime2 = centralTime1.Add(twoAndAHalfHours);
// Display result
Console.WriteLine($"{centralTime1} + {twoAndAHalfHours.ToString()} hours = {centralTime2}");
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("Unable to retrieve Central Standard Time zone information.");
}
}
}
// The example displays the following output to the console:
// 3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 4:00:00 AM -06:00
Module IntervalArithmetic
Public Sub Main()
Dim generalTime As Date = #03/09/2008 1:30AM#
Const tzName As String = "Central Standard Time"
Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)
' Instantiate DateTimeOffset value to have correct CST offset
Try
Dim centralTime1 As New DateTimeOffset(generalTime, _
TimeZoneInfo.FindSystemTimeZoneById(tzName).GetUtcOffset(generalTime))
' Add two and a half hours
Dim centralTime2 As DateTimeOffset = centralTime1.Add(twoAndAHalfHours)
' Display result
Console.WriteLine("{0} + {1} hours = {2}", centralTime1, _
twoAndAHalfHours.ToString(), _
centralTime2)
Catch e As TimeZoneNotFoundException
Console.WriteLine("Unable to retrieve Central Standard Time zone information.")
End Try
End Sub
End Module
' The example displays the following output to the console:
' 3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 4:00:00 AM -06:00
具有時區時間的算術運算
類別 TimeZoneInfo 包含轉換方法,這些方法會在將時間從某個時區轉換成另一個時區時自動套用調整。 這些轉換方法包括:
和 ConvertTimeConvertTimeBySystemTimeZoneId 方法,可轉換任兩個時區之間的時間。
ConvertTimeFromUtc和 ConvertTimeToUtc 方法,可將特定時區中的時間轉換成 UTC,或將 UTC 轉換為特定時區的時間。
如需詳細資訊,請參閱 在時區之間轉換時間。
類別 TimeZoneInfo 不會提供當您執行日期和時間算術時自動套用調整規則的任何方法。 不過,您可以將時區中的時間轉換成 UTC、執行算術運算,然後從 UTC 轉換為時區中的時間,以套用調整規則。 如需詳細資訊,請參閱 如何:在日期和時間算術中使用時區。
例如,下列程式代碼類似於先前的程式代碼,其中在 2008 年 3 月 9 日上午 2:00 基礎上加了兩個半小時。 不過,由於它會在執行日期和時間算術之前,將 Central Standard 時間轉換成 UTC,然後將結果從 UTC 轉換為中央標準時間,因此產生的時間會反映中央標準時區轉換為日光節約時間。
using System;
public class TimeZoneAwareArithmetic
{
public static void Main()
{
const string tzName = "Central Standard Time";
DateTime generalTime = new DateTime(2008, 3, 9, 1, 30, 0);
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);
// Instantiate DateTimeOffset value to have correct CST offset
try
{
DateTimeOffset centralTime1 = new DateTimeOffset(generalTime,
cst.GetUtcOffset(generalTime));
// Add two and a half hours
DateTimeOffset utcTime = centralTime1.ToUniversalTime();
utcTime += twoAndAHalfHours;
DateTimeOffset centralTime2 = TimeZoneInfo.ConvertTime(utcTime, cst);
// Display result
Console.WriteLine($"{centralTime1} + {twoAndAHalfHours.ToString()} hours = {centralTime2}");
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("Unable to retrieve Central Standard Time zone information.");
}
}
}
// The example displays the following output to the console:
// 3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 5:00:00 AM -05:00
Module TimeZoneAwareArithmetic
Public Sub Main()
Const tzName As String = "Central Standard Time"
Dim generalTime As Date = #03/09/2008 1:30AM#
Dim cst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tzName)
Dim twoAndAHalfHours As New TimeSpan(2, 30, 0)
' Instantiate DateTimeOffset value to have correct CST offset
Try
Dim centralTime1 As New DateTimeOffset(generalTime, _
cst.GetUtcOffset(generalTime))
' Add two and a half hours
Dim utcTime As DateTimeOffset = centralTime1.ToUniversalTime()
utcTime += twoAndAHalfHours
Dim centralTime2 As DateTimeOffset = TimeZoneInfo.ConvertTime(utcTime, cst)
' Display result
Console.WriteLine("{0} + {1} hours = {2}", centralTime1, _
twoAndAHalfHours.ToString(), _
centralTime2)
Catch e As TimeZoneNotFoundException
Console.WriteLine("Unable to retrieve Central Standard Time zone information.")
End Try
End Sub
End Module
' The example displays the following output to the console:
' 3/9/2008 1:30:00 AM -06:00 + 02:30:00 hours = 3/9/2008 5:00:00 AM -05:00