概述
寫下本文緣於前幾天博客園一位朋友發表了一篇.NET面試題的文章,其中一個關於DateTime的問題引起了大家激烈的爭論,鑒於日期時間類型是大家開發中會頻繁使用的一個中數據類型,這裡我們有必要來對.NET Framework中的日期時間類型做一個深入的認識。
從.NET Framework 1.0開始,就提供了DateTime類型來表示一個日期時間類型,它是一個結構類型,並且不可以為空,這在一定程度上給我們在往數據庫中保存數據時帶來了很大的麻煩,因為我們知道,在數據庫中datatime類型是可以為Null的,為了解決這個問題,不得不經常使用DateTime.MinValue來表示,但這並不是我們想要的。幸運的是到了.NET Framework 2.0中,提供了可空類型,此時我們就可以使用Nullable<DateTime>來表示一個日期時間類型,它是可以為Null的,這給我們帶來了極大的方便。
到了.NET Framework 3.5中,又為我們提供了一個全新的日期時間類型DateTimeOffset,它通常以相對於格林威治時間(GMT,Greenwich Mean Time)的日期和時間來表示,格林威治時間又被稱為國際標准時間UTC(Universal Time Code)。除此之外,在.NET Framework中還為我們提供了TimeZone類用來表示時區,到了.NET Framework 3.5中,對TimeZone類進一步增強,提供了TimeZoneInfo類來表示世界上的任何時區。
在本文中,我們將對以上日期時間類型、時區類進行詳細的介紹。
DateTime和DateTimeOffset
DateTime 值類型表示值范圍在公元0001 年1 月1 日午夜12:00:00 到公元9999 年12月31日晚上11:59:59 之間的日期和時間;DateTimeOffset包含一個DateTime 值以及一個名為Offset屬性,該屬性用於確定當前 DateTimeOffset 實例的日期和時間與UTC之間的差值,我們先來看一下這段代碼的輸出:
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now);
Console.WriteLine(DateTimeOffset.Now);
}
輸出結果為:
可以看到,DateTime輸出了日期和時間,DateTimeOffset類型不僅輸出了日期和時間,還給出當前時間與UTC之間的差值。接下來我們再看一段代碼,如何手工構造一個DateTime和DateTimeOffset實例:
static void Main(string[] args)
{
DateTime dateA = new DateTime(2008,8,26,23,1,48);
DateTimeOffset dateB = new DateTimeOffset(2008, 8, 26, 23, 1, 48,
new TimeSpan(4,0,0));
Console.WriteLine(dateA);
Console.WriteLine(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(2008,8,24,23,33,58);
DateTime dateB = DateTime.SpecifyKind(dateA, DateTimeKind.Utc);
DateTimeOffset dateC = dateB;
Console.WriteLine(dateB);
Console.WriteLine(dateC);
}
再來寫一個表示本地時間的轉換,如下代碼所示:
static void Main(string[] args)
{
DateTime dateA = new DateTime(2008, 8, 24, 23, 33, 58);
DateTime dateB = DateTime.SpecifyKind(dateA, DateTimeKind.Local);
DateTimeOffset dateC = dateB;
Console.WriteLine(dateB);
Console.WriteLine(dateC);
}
輸出結果如下圖所示:
如果在轉換時指定的時間是Unspecified,轉換後產生的DateTimeOffset的值的偏移量將會為本地時區,如下代碼所示:
static void Main(string[] args)
{
DateTime dateA = new DateTime(2008, 8, 24, 23, 33, 58);
DateTime dateB = DateTime.SpecifyKind(dateA, DateTimeKind.Unspecified);
DateTimeOffset dateC = dateB;
Console.WriteLine(dateB);
Console.WriteLine(dateC);
}
輸出結果如下圖所示,可以看到它產生的輸出是本地時區:
這一點其實從DateTimeOffset的一個參數為DateTime的構造函數中就能夠看出來,它只判斷DateTime是否為UTC,否則就取當前本地時區的偏移量:
public DateTimeOffset(DateTime dateTime) {
轉換DateTimeOffset為DateTime
TimeSpan offset;
if (dateTime.Kind != DateTimeKind.Utc) {
// Local 和 Unspecified 都轉換為Local
offset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
}
else {
offset = new TimeSpan(0);
}
m_offsetMinutes = ValidateOffset(offset);
m_dateTime = ValidateDate(dateTime, offset);
}
在轉換一個DateTimeOffset類型為DateTime類型時,可以使用如下幾個屬性:
DateTime屬性:返回一個指示為Unspecified的DateTime值;
UtcDateTime屬性:返回一個指示為UTC的DateTime值,如果偏移量不為0,它會轉換為UTC時間;
LocalDateTime屬性:返回一個指示為Local的DateTime值。
這三個屬性的在DateTimeOffset中的定義如下代碼所示:
public DateTime DateTime {
get {
return ClockDateTime;
}
}
public DateTime UtcDateTime {
get {
return DateTime.SpecifyKind(m_dateTime, DateTimeKind.Utc);
}
}
public DateTime LocalDateTime {
get {
return UtcDateTime.ToLocalTime();
}
}
可以看到,在LocalDateTime屬性中首先會獲取UtcDateTime,然後調用ToLocalTime()將其轉換為本地時間。我們現在來看一組測試代碼:
static void Main(string[] args)
{
DateTimeOffset basic = new DateTimeOffset(2008, 8, 24, 23, 33, 58,
new TimeSpan(8,0,0));
DateTime dateA = basic.DateTime;
DateTime dateB = basic.LocalDateTime;
DateTime dateC = basic.UtcDateTime;
Console.WriteLine(basic);
Console.WriteLine("--------------------------");
Console.WriteLine("Unspecified DateTime:" + dateA);
Console.WriteLine("Local DateTIme:" + dateB);
Console.WriteLine("UTC DateTime:" + dateC);
}
最後輸出的結果如下圖所示:
在DateTime和DateTimeOffset之間選擇
上面說了這麼多關於DateTime和DateTimeOffset類型,如何在DateTime和DateTimeOffset之間進行選擇呢?從前面的示例中大家已經看到了,DateTime只可以表示UTC或者本地時區的時間,或者不確定的時區,這給我們應用程序的移植帶來了極大的麻煩,除非你指定它表示的是UTC,否則在移植應用程序時會受到諸多的限制,例如下面這段最簡單的代碼:
static void Main(string[] args)
{
DateTime date = DateTime.Now;
Console.WriteLine(date);
}
如果DateTime表示本地時區,那麼應用程序在本地時區內移植是不會有問題的。但是如果你的應用程序需要對不同的時區都支持,建議在使用時盡量將DateTime的Kind屬性設置為Utc,這一點尤其重要,否則就需要考慮使用DateTimeOffset類型。
與DateTime類型不同的是,DateTimeOffset它唯一的標識了一個明確的時間點,即時間值以及相對於UTC的偏移量,它並不依賴於某個特定的時區,在大多數情況下,應當考慮使用DateTimeOffset來代替DateTime類型。並且在SQL Server 2008中也已經提供了對於DateTimeOffset數據類型的支持,詳細信息可以參考這篇文章《SQL Server 2008中的新日期數據類型》。
但是DateTimeOffset類型並不是完全用來代替DateTime類型,在應用程序只用到日期而不涉及時間,如出生日期,用DateTime類型是沒有任何問題的。
時區支持
在.NET Framework 3.5之前,我們只能使用TimeZone來表示一個時區,但是Timezone功能很有限,它只能識別本地時區,可以在UTC和本地時間之間轉換時間;而TimeZoneInfo 對TimeZone進行了很大的增強,它可以表示世界上的任意時區 。看下面一段代碼:
static void Main(string[] args)
{
TimeZone timeZoneA = TimeZone.CurrentTimeZone;
Console.WriteLine(timeZoneA.StandardName);
TimeZoneInfo timeZoneB = TimeZoneInfo.Local;
Console.WriteLine(timeZoneB.StandardName);
TimeZoneInfo timeZoneC = TimeZoneInfo.Utc;
Console.WriteLine(timeZoneC.StandardName);
}
輸出結果如下圖所示:
TimeZone提供的屬性和方法非常有限,TimeZoneInfo在這方面就顯的非常豐富,我們可以使用TimeZoneInfo在兩個不同的時區之間轉換時間,如下面的代碼:
static void Main(string[] args)
{
DateTimeOffset chinaDate = DateTimeOffset.Now;
DateTimeOffset easternDate = TimeZoneInfo.ConvertTime(
chinaDate,
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"));
Console.WriteLine("Now: {0}", chinaDate);
Console.WriteLine("Now in Eastern: {0}", easternDate);
}
輸出結果如下圖所示:
這裡使用FindSystemTimeZoneById方法來根據ID來獲取時區。在推出TimeZoneInfo之後,在以後的開發中完全可以放棄TimeZone類了,TimeZoneInfo已經完全包含了它。
總結
本文介紹了.NET Framework中對於日期時間類型的支持,希望對大家有所幫助。