在 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 结构兼容的简单格式存储日期和时间信息。
执行日期和时间算法,但不关心常规结果。 例如,在向特定日期和时间添加六个月的加法运算中,结果是否按夏时制进行调整通常并不重要。
除非是表示 UTC 的特定 DateTime 值,否则,该日期和时间值在可迁移性方面通常是不明确或是有限的。 例如,如果 DateTime 值表示本地时间,那么它在该本地时区内是可迁移的(也就是说,如果在同一时区中的不同系统上反序列化该值,该值仍可以明确标识单个时间点)。 在该本地时区之外,该 DateTime 值可有多种解释。 如果该值的 Kind 属性为 DateTimeKind.Unspecified,那么其可迁移性会更低:现在它在同一时区内不明确,那么它在首次被序列化的同一系统上,可能更不明确。 只有表示 UTC 的 DateTime 值才可以明确标识单个时间点,且与它在哪个系统或是哪个时区中使用无关。
重要事项 |
---|
在保存或共享 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
输出显示,此示例中的每个日期和时间值都可以隶属于至少三个不同时区。 6/10/2007 的 DateTimeOffset 值显示,如果日期和时间值表示夏时制,其相对于 UTC 的偏移量甚至不需要对应于原始时区的基本 UTC 偏移量,或在其显示名称中发现的相对于 UTC 的偏移量。 这意味着,由于单一 DateTimeOffset 值未与其时区紧密关联,因而无法反映时区与夏时制的来回转换。 在使用日期和时间算法处理 DateTimeOffset 值时,这可能是一个特殊的问题。 (有关如何以考虑时区调整规则的方式执行日期和时间算法的讨论,请参见使用日期和时间执行算术运算。)
TimeZoneInfo 类
TimeZoneInfo 类表示世界上的任意时区,并能将一个时区中的任何日期和时间转换为另一个时区中的等效日期和时间。 通过 TimeZoneInfo 类,可以使用日期和时间,以便任何日期和时间值都可以明确标识单个时间点。 TimeZoneInfo 类还是可扩展的。 虽然它依赖于在注册表中定义的、为 Windows 系统提供的时区信息,但它支持创建自定义时区。 它还支持时区信息的序列化和反序列化。
在有些情况下,完全利用 TimeZoneInfo 类可能需要进一步的开发工作。 首先,日期和时间值与其所属的时区没有紧密关联。 因此,除非您的应用程序提供将日期和时间与其相关联的时区链接在一起的某种机制,否则特定的日期和时间值很容易解除与其时区的关联。 (链接此信息的一种方法是定义同时包含日期和时间值及其相关联的时区对象的类或结构。)其次,Windows XP 及 Windows 的早期版本没有提供对历史时区信息的实时支持,而 Windows Vista 只能提供有限的支持。 对于设计用于处理历史日期和时间的应用程序,必须广泛使用自定义时区。
只有在实例化日期和时间对象时已知该日期和时间值所属时区的情况下,才可以利用 .NET Framework 中提供的时区支持。 但通常情况并非如此,特别是在 Web 或网络应用程序中。