概述
寫下本文緣於前幾天部落格園一位朋友發表了一篇.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) { 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
在轉換一個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中對於日期時間類型的支援,希望對大家有所協助。