使用日期和时间执行算术运算
虽然 DateTime 和 DateTimeOffset 结构都提供对其值执行算术运算的成员,但是算术运算的结果却截然不同。 本主题将分析这些差异,将它们与日期和时间数据中的时区识别能力的程度相关联,并讨论如何使用日期和时间数据执行完全时区识别运算。
使用 DateTime 值进行比较和算术运算
从 .NET Framework 2.0 版开始,DateTime 值具有有限程度的时区识别能力。 DateTime.Kind 属性允许将 DateTimeKind 值赋给日期和时间,以指示它表示的是本地时间、协调世界时 (UTC) 还是未指定的时区中的时间。 但是,当比较 DateTime 值或对其执行日期和时间算术运算时将忽略这些有限的时区信息。 下面的示例阐释了这一点,它对当前的本地时间与当前的 UTC 时间进行比较。
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
using System;
public enum TimeComparison
{
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.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(typeof(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.
CompareTo(DateTime) 方法报告本地时间比 UTC 时间早(或小), 并且减法运算指示 UTC 与位于美国太平洋标准时区的系统的本地时间相差七个小时。 但由于这两个值提供了单个时间点的不同表示形式,因此,在本例中很明显这个时间间隔完全可归因于本地时区相对于 UTC 的偏移量。
一般来说,DateTime.Kind 属性虽然能够影响对 DateTime 比较和算术方法返回的结果的解释,但是它不会影响这些结果(正如对两个相同的时间点的比较所指示的那样)。 例如:
对两个其 DateTime.Kind 属性都等于 Utc 的日期和时间值执行的任何算术运算的结果都反映这两个值之间实际的时间间隔。 同样,对两个此类日期和时间值的比较准确反映两个时间之间的关系。
对两个其 DateTime.Kind 属性都等于 Local 的日期和时间值或者对两个其 DateTime.Kind 属性值不同的日期和时间值执行的任何算术或比较运算的结果都反映这两个值在时钟时间上的差异。
对本地日期和时间值执行的算术或比较运算不考虑某个特定值是否是不明确的或无效的,也不考虑由于本地时区与夏时制之间的转换带来的任何调整规则的影响。
任何比较或计算 UTC 与本地时间之差的运算的结果中都包括一个等于本地时区相对于 UTC 的偏移量的时间间隔。
任何比较或计算未指定的时间与 UTC 或本地时间之差的运算都反映简单的时钟时间。 不会考虑时区差异,并且结果不反映时区调整规则的应用。
任何比较或计算两个未指定的时间之差的运算都可能包括未知的时间间隔以反映两个不同的时区中的时间的差异。
有许多这样的方案,在这些方案中,时区差异不会影响日期和时间计算(有关其中一些方案的讨论,请参见在 DateTime、DateTimeOffset 和 TimeZoneInfo 之间进行选择),或者在这些方案中日期和时间数据的上下文定义比较或算术运算的含义。
使用 DateTimeOffset 值进行比较和算术运算
DateTimeOffset 值不仅包括日期和时间,而且还包括一个偏移量,它明确地定义该日期和时间相对于 UTC 的偏移量。 这使得可以定义与 DateTime 值稍有不同的相等关系。 然而,如果它们的日期和时间值相同,则 DateTime 值相等;如果它们指的都是同一个时间点,则 DateTimeOffset 值相等。 这使得当在比较和大多数确定两个日期和时间之间的时间间隔的算术运算中使用时,DateTimeOffset 值更精确,需要的解释更少。 下面的示例阐释了这种行为差异,它等效于前面的比较本地和 UTC DateTime 值的示例,只是它是针对 DateTimeOffset 的。
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)
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: {0}:{1:D2} hours",
(localTime - utcTime).Hours,
(localTime - utcTime).Minutes);
Console.WriteLine("The local time is {0} UTC.",
Enum.GetName(typeof(TimeComparison), localTime.CompareTo(utcTime)));
}
}
// 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.
在本示例中,CompareTo 方法指示当前本地时间与当前 UTC 时间相等,而 DateTimeOffset 值的减法运算指示这两个时间之差是 TimeSpan.Zero。
在日期和时间算术中使用 DateTimeOffset 值的主要限制是虽然 DateTimeOffset 值具有一定的时区识别能力,但它们没有完全的时区识别能力。 虽然首次为 DateTimeOffset 变量赋值时 DateTimeOffset 值的偏移量反映时区相对于 UTC 的偏移量,但以后它就与时区解除关联了。 由于它不再直接与可识别的时间关联,因此日期和时间间隔的加减运算不考虑时区的调整规则。
举例来说,美国中部标准时区转换到 夏时制的时间发生在凌晨 2:00。 该时区会转换到夏时制。 这意味着,如果向 2008 年 3 月 9 日凌晨 1:30 的中部标准时间增加两个半小时的时间间隔, 那么得到的日期和时间应为凌晨 5:00。 该时区会转换到夏时制。 但是,如下面的示例所示,加法运算的结果是凌晨 4: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
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("{0} + {1} hours = {2}", centralTime1,
twoAndAHalfHours.ToString(),
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
使用时区中的时间执行算术运算
TimeZoneInfo 类包括多种转换方法,这些转换方法在将时间从一个时区转换为另一个时区时会自动应用调整。 这些要求包括:
ConvertTime 和 ConvertTimeBySystemTimeZoneId 方法,在任意两个时区之间转换时间。
ConvertTimeFromUtc 和 ConvertTimeToUtc 方法,将某个特定的时区中的时间转换为 UTC,或者将 UTC 转换为某个特定的时区中的时间。
有关详细信息,请参见在不同时区之间转换时间。
TimeZoneInfo 类并没有提供任何在您执行日期和时间算术运算时自动应用调整规则的方法。 但是,您可以通过以下操作来实现这一点:将某个时区中的时间转换为 UTC,执行算术运算,然后从 UTC 转换回该时区中的时间。 有关详细信息,请参见如何:在日期和时间算法中使用时区。
例如,下面的代码类似于前面向凌晨 2:00 增加两个半小时的代码。 该时区会转换到夏时制。 但是,由于它在执行日期和时间算术运算之前将中部标准时间转换成了 UTC,然后再将结果从 UTC 转换回中部标准时间,因此得到的时间反映了中部标准时区到夏时制的转换。
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
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("{0} + {1} hours = {2}", centralTime1,
twoAndAHalfHours.ToString(),
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