概述
写下本文缘于前几天博客园一位朋友发表了一篇NET面试题的文章其中一个关于DateTime的问题引起了大家激烈的争论鑒于日期时间类型是大家开发中会频繁使用的一个中数据类型这里我们有必要来对NET Framework中的日期时间类型做一个深入的认识
从NET Framework 开始就提供了DateTime类型来表示一个日期时间类型它是一个结构类型并且不可以为空这在一定程度上给我们在往数据库中保存数据时带来了很大的麻烦因为我们知道在数据库中datatime类型是可以为Null的为了解决这个问题不得不经常使用DateTimeMinValue来表示但这并不是我们想要的幸运的是到了NET Framework 中提供了可空类型此时我们就可以使用Nullable<DateTime>来表示一个日期时间类型它是可以为Null的这给我们带来了极大的方便
到了NET Framework 中又为我们提供了一个全新的日期时间类型DateTimeOffset它通常以相对于格林威治时间(GMTGreenwich Mean Time)的日期和时间来表示格林威治时间又被称为国际标准时间UTC(Universal Time Code)除此之外在NET Framework中还为我们提供了TimeZone类用来表示时区到了NET Framework 中对TimeZone类进一步增强提供了TimeZoneInfo类来表示世界上的任何时区
在本文中我们将对以上日期时间类型时区类进行详细的介绍
DateTime和DateTimeOffset
DateTime 值类型表示值范围在公元 年 月 日午夜:: 到公元 年月日晚上:: 之间的日期和时间DateTimeOffset包含一个DateTime 值以及一个名为Offset属性该属性用于确定当前 DateTimeOffset 实例的日期和时间与UTC之间的差值我们先来看一下这段代码的输出
static void Main(string[] args)
{
ConsoleWriteLine(DateTimeNow);
ConsoleWriteLine(DateTimeOffsetNow);
}
输出结果为
可以看到DateTime输出了日期和时间DateTimeOffset类型不仅输出了日期和时间还给出当前时间与UTC之间的差值接下来我们再看一段代码如何手工构造一个DateTime和DateTimeOffset实例
static void Main(string[] args)
{
DateTime dateA = new DateTime();
DateTimeOffset dateB = new DateTimeOffset(
new TimeSpan());
ConsoleWriteLine(dateA);
ConsoleWriteLine(dateB);
}
输出结果如下图所示
转换DateTime为DateTimeOffset
通过上面的两个例子大家应该对DateTimeOffset有了一个基本的认识DateTimeOffset提供了比DateTime更高程度的时区识别能力接下来我们看如何在DateTime和DateTimeOffset之间进行转换开始之前我们先了解一下DateTimeKind枚举在DateTime中提供了一个名为Kind的属性它用来指示DateTime对象是表示本地时间国际标准时间(UTC)还是既不指定为本地时间也不指定为国际标准时间(UTC)DateTimeKind的定义如下
public enum DateTimeKind
{
Unspecified
Utc
Local
}对于UTC 和本地DateTime值得到的DateTimeOffset值的Offset属性准确反映UTC 或本地时区偏移量如下面的代码将 UTC 时间转换为与之等效的DateTimeOffset值
static void Main(string[] args)
{
DateTime dateA = new DateTime();
DateTime dateB = DateTimeSpecifyKind(dateA DateTimeKindUtc);
DateTimeOffset dateC = dateB;
ConsoleWriteLine(dateB);
ConsoleWriteLine(dateC);
}
输出结果如下图所示
再来写一个表示本地时间的转换如下代码所示
static void Main(string[] args)
{
DateTime dateA = new DateTime( );
DateTime dateB = DateTimeSpecifyKind(dateA DateTimeKindLocal);
DateTimeOffset dateC = dateB;
ConsoleWriteLine(dateB);
ConsoleWriteLine(dateC);
}
输出结果如下图所示
如果在转换时指定的时间是Unspecified转换后产生的DateTimeOffset的值的偏移量将会为本地时区如下代码所示
static void Main(string[] args)
{
DateTime dateA = new DateTime( );
DateTime dateB = DateTimeSpecifyKind(dateA DateTimeKindUnspecified);
DateTimeOffset dateC = dateB;
ConsoleWriteLine(dateB);
ConsoleWriteLine(dateC);
}
输出结果如下图所示可以看到它产生的输出是本地时区
这一点其实从DateTimeOffset的一个参数为DateTime的构造函数中就能够看出来它只判断DateTime是否为UTC否则就取当前本地时区的偏移量
public DateTimeOffset(DateTime dateTime) {
TimeSpan offset;
if (dateTimeKind != DateTimeKindUtc) {
// Local 和 Unspecified 都转换为Local
offset = TimeZoneCurrentTimeZoneGetUtcOffset(dateTime);
}
else {
offset = new TimeSpan();
}
m_offsetMinutes = ValidateOffset(offset);
m_dateTime = ValidateDate(dateTime offset);
}
转换DateTimeOffset为DateTime
在转换一个DateTimeOffset类型为DateTime类型时可以使用如下几个属性
DateTime属性返回一个指示为Unspecified的DateTime值
UtcDateTime属性返回一个指示为UTC的DateTime值如果偏移量不为它会转换为UTC时间
LocalDateTime属性返回一个指示为Local的DateTime值
这三个属性的在DateTimeOffset中的定义如下代码所示
public DateTime DateTime {
get {
return ClockDateTime;
}
}
public DateTime UtcDateTime {
get {
return DateTimeSpecifyKind(m_dateTime DateTimeKindUtc);
}
}
public DateTime LocalDateTime {
get {
return UtcDateTimeToLocalTime();
}
} 可以看到在LocalDateTime属性中首先会获取UtcDateTime然后调用ToLocalTime()将其转换为本地时间我们现在来看一组测试代码
static void Main(string[] args)
{
DateTimeOffset basic = new DateTimeOffset(
new TimeSpan());
DateTime dateA = basicDateTime;
DateTime dateB = basicLocalDateTime;
DateTime dateC = basicUtcDateTime;
ConsoleWriteLine(basic);
ConsoleWriteLine();
ConsoleWriteLine(Unspecified DateTime: + dateA);
ConsoleWriteLine(Local DateTIme: + dateB);
ConsoleWriteLine(UTC DateTime: + dateC);
}最后输出的结果如下图所示
在DateTime和DateTimeOffset之间选择
上面说了这么多关于DateTime和DateTimeOffset类型如何在DateTime和DateTimeOffset之间进行选择呢?从前面的示例中大家已经看到了DateTime只可以表示UTC或者本地时区的时间或者不确定的时区这给我们应用程序的移植带来了极大的麻烦除非你指定它表示的是UTC否则在移植应用程序时会受到诸多的限制例如下面这段最简单的代码
static void Main(string[] args)
{
DateTime date = DateTimeNow;
ConsoleWriteLine(date);
}如果DateTime表示本地时区那么应用程序在本地时区内移植是不会有问题的但是如果你的应用程序需要对不同的时区都支持
建议在使用时尽量将DateTime的Kind属性设置为Utc这一点尤其重要否则就需要考虑使用DateTimeOffset类型
与DateTime类型不同的是DateTimeOffset它唯一的标识了一个明确的时间点即时间值以及相对于UTC的偏移量它并不依赖于某个特定的时区在大多数情况下应当考虑使用DateTimeOffset来代替DateTime类型并且在SQL Server 中也已经提供了对于DateTimeOffset数据类型的支持详细信息可以参考这篇文章《SQL Server 中的新日期数据类型》
但是DateTimeOffset类型并不是完全用来代替DateTime类型在应用程序只用到日期而不涉及时间如出生日期用DateTime类型是没有任何问题的
时区支持
在NET Framework 之前我们只能使用TimeZone来表示一个时区但是Timezone功能很有限它只能识别本地时区可以在UTC和本地时间之间转换时间而TimeZoneInfo 对TimeZone进行了很大的增强它可以表示世界上的任意时区 看下面一段代码
static void Main(string[] args)
{
TimeZone timeZoneA = TimeZoneCurrentTimeZone;
ConsoleWriteLine(timeZoneAStandardName);
TimeZoneInfo timeZoneB = TimeZoneInfoLocal;
ConsoleWriteLine(timeZoneBStandardName);
TimeZoneInfo timeZoneC = TimeZoneInfoUtc;
ConsoleWriteLine(timeZoneCStandardName);
}输出结果如下图所示
TimeZone提供的属性和方法非常有限TimeZoneInfo在这方面就显的非常丰富我们可以使用TimeZoneInfo在两个不同的时区之间转换时间如下面的代码
static void Main(string[] args)
{
DateTimeOffset chinaDate = DateTimeOffsetNow;
DateTimeOffset easternDate = TimeZoneInfoConvertTime(
chinaDate
TimeZoneInfoFindSystemTimeZoneById(Eastern Standard Time));
ConsoleWriteLine(Now: {} chinaDate);
ConsoleWriteLine(Now in Eastern: {} easternDate);
}
输出结果如下图所示
这里使用FindSystemTimeZoneById方法来根据ID来获取时区在推出TimeZoneInfo之后在以后的开发中完全可以放弃TimeZone类了TimeZoneInfo已经完全包含了它
总结
本文介绍了NET Framework中对于日期时间类型的支持希望对大家有所帮助