天天看點

一起談.NET技術,System.DateTime詳解  一、你是否知道System.DateTimeKind?  二、幾個常用DateTime對象的DateTimeKind  三、DateTime的對等性問題  四、通過DateTime類型的ToLocalTime和ToUniversalTime方法實作UTC和Local的轉換  五、通過TimeZoneInfo實作Utc和Local的轉換

  最近一直在負責公司内部架構的更新工作,今天對一個小問題進行了重新思考——時間的處理。具體來說,是如何有效地進行時間的處理以提供對跨時區的支援。對于一個分布式的應用來說,倘若用戶端和服務端部署與不同的地區,在對時間進行處理的時候,就需要考慮時區的問題。以我們現在的一個項目為例,這是一個為澳洲某機構開發的一個基于Smart Client應用(Windows Form用戶端),伺服器部署于墨爾本,應用的最終使用者可能需要跨越不同的州。澳洲地廣人稀,不同的州也有可能會跨越不同的時區。假設資料庫并不支援對時區的區分,服務端需要對針對用戶端所在的時區對時間進行相應的處理。不過,對該問題解決方案的介紹我會放在後續的文章中,在這裡我們先來介紹一些基礎性的内容——談談我們熟悉的System.DateTime類型。

  在DateTime類型中,表示時間類型的Kind屬性是隻讀的,隻能在構造函數中指定。相關構造函數和Kind屬性的定義如下面的代碼片斷所示:

  雖然,Kind屬性是隻讀的,但是我們還用另外一中設定Kind的方式,那就是調用DateTime的靜态方法的SpecifyKind。該方法不會真正去修改一個現有DateTime對象的Kind屬性,而是會重新建立一個新的DateTime對象。方法傳回的對象具有和指定時間相同的基本屬性(年、月、日、時、分、秒和毫秒),該DateTime對象具有你指定的DateTimeKind值。

  處理直接通過構造函數建構DateTime對象之外,我們還經常用到DateTime的幾個靜态隻讀屬性去擷取一些特殊的時間,比如Now、UtcNow、MinValue和MaxValue等,那麼這些DateTime對象的DateTimeKind又是什麼呢?

當我們通過構造函數建立一個DateTime對象的時候,Kind預設為DateTimeKind.Unspecified。

DateTime.Now表示目前系統時間,Kind屬性值為DateTimeKind.Local,是以DateTime.Now應該是DateTime.LocalNow;

而DateTime.UtcNow傳回以UTC表示的目前時間,毫無疑問,Kind屬性自然是DateTimeKind.Utc;

DateTime.MinValue和DateTime.MaxValue表示的DateTime所能表示的最大範圍,它們的Kind屬性為DateTimeKind.Unspecified。

  上面清單對幾個常用DateTime對象Kind屬性的描述可以通過下面的程式來證明:

  輸出結果:

  接下來,我們來談談另外一個比較有意思的問題——兩個DateTime對象對等性。在這之前,我首先提出這樣一個問題:“如果兩個DateTime對象相等,是否意味着它們表示同一個時間點?”我想有人會認為是。但是答案是“不一定”,我們可以舉一個反例。在下面的程式中,我建立了三個DateTime對象,年、月、日、時、分、秒均是相同的,但Kind分分别指定為DateTimeKind.Local、DateTimeKind.Unspecified和DateTimeKind.Utc。

  由于我們處于東8區,基于DateTimeKind.Local的endOfTheWorld1和基于DateTimeKind.Utc的endOfTheWorld3,不可能表示的是同一個時刻。但是從下面的輸出結果來看,它們卻是“相等的”,不但如此,Kind為Unspecified的endOfTheWorld2也和這兩個時間對象相等。

  注意,這裡的基準時間0001 年 1 月 1 日午夜 12:00:00,并沒有說是一定是UTC時間,是以Ticks和DateTimeKind無關,這裡通過下面的執行個體看出來:

  從下面的輸出結果我們不難看出,上面建立的具有不同DateTimeKind的三個DateTime的Ticks屬性的值都是相等的。實際上,DateTime的對等性判斷就是通過Ticks的大小來判斷的。

  我們經常說的UTC時間和本地時間之間的互相轉化,實際上指的就是将一個具有某種DateTimeKind的DateTime對象轉化成具有另外一種DateTimeKind的DateTime對象,并且確定兩個DateTime對象對象表示相同的時間點。關于時間轉換的實作,我們有很多不同的選擇。

  對基于三種不同DateTimeKind的DateTime對象之間的轉化,最友善的就是直接采用DateTime類型的兩個對應的方法:ToLocalTime和ToUniversalTime,這兩個方法的定義如下。

  實際上我們所說的不同DateTimeKind之間的DateTime之間的轉化主要包括兩個方面:将一個DateTimeKind.Local(或者DateTimeKind.Unspecified)時間轉換成DateTimeKind.Utc時間,或者将DateTimeKind.Utc(或者DateTimeKind.Unspecifed時間)轉換成DateTimeKind.Local時間。為了深刻地了解兩種不同轉換采用的轉化規則,我寫了如下一段程式:

  對于DataTimeKind為Utc和Local之間的轉化,沒有什麼可以說得,就是一個基于時差的換算而已。大家容易忽視的是DataTimeKind.Unspecifed時間分别向其他兩種DateTimeKind時間的轉換問題。從下面的輸出我們可以看出,當DateTimeKind.Unspecifed時間向DateTimeKind.Local轉換的時候,實際上是當成DateTimeKind.Utc時間;而向DateTimeKind.Utc轉換的時候,則當成是DateTimeKind.Local。順便補充一下:不論被轉換的時間屬于怎麼的DateTimeKind,調用ToLocalTime和ToUniversalTime方法的傳回的時間的Kind屬性總是DateTimeKind.Local和DateTimeKind.Utc,兩者之間的轉換并不隻是年月日和時分秒的改變。

  我們照例來做個試驗。還是剛才建立的三個DateTime對象,現在我們分别調用ConvertTimeFromUtc将DateTimeKind.Utc或者DateTimeKind.Unspecified時間轉換成DateTimeKind.Local時間;然後将調用ConvertTimeToUtc将DateTimeKind.Local或者DateTimeKind.Unspecified時間轉換成DateTimeKind.Utc時間。

  同上面進行的轉換方式一樣,在向DateTimeKind.Utc時間進行轉換的時候,DateTimeKind.Unspecifed時間被當成DateTimeKind.Local;而在向DateTimeKind.Local時間轉換的時候,DateTimeKind.Unspecifed則被當成DateTimeKind.Utc時間。

  ConvertTimeFromUtc和ConvertTimeToUtc方法在轉換的時候,如果發現被轉換的時間和需要轉化時間具有相同的DateTimeKind會抛出異常。也就是說,我們不能調用ConvertTimeFromUtc方法并傳入DateTimeKind.Local時間,也不能調用ConvertTimeToUtc方法并傳入DateTimeKind.Urc時間。如右圖所式,我們将一個DateTimeKind.Utc時間(DateTime.UtcNow)傳入ConvertTimeToUtc方法,結果抛出一個ArgumentException異常。錯誤消息為:“The conversion could not be completed because the supplied DateTime did not have the Kind property set correctly.  For example, when the Kind property is DateTimeKind.Local, the source time zone must be TimeZoneInfo.Local.

Parameter name: sourceTimeZone”。

一起談.NET技術,System.DateTime詳解  一、你是否知道System.DateTimeKind?  二、幾個常用DateTime對象的DateTimeKind  三、DateTime的對等性問題  四、通過DateTime類型的ToLocalTime和ToUniversalTime方法實作UTC和Local的轉換  五、通過TimeZoneInfo實作Utc和Local的轉換