最近一直在負(fù)責(zé)公司內(nèi)部框架的升級(jí)工作,,今天對(duì)一個(gè)小問(wèn)題進(jìn)行了重新思考——時(shí)間的處理。具體來(lái)說(shuō),,是如何有效地進(jìn)行時(shí)間的處理以提供對(duì)跨時(shí)區(qū)的支持,。對(duì)于一個(gè)分布式的應(yīng)用來(lái)說(shuō),倘若客戶端和服務(wù)端部署與不同的地區(qū),,在對(duì)時(shí)間進(jìn)行處理的時(shí)候,,就需要考慮時(shí)區(qū)的問(wèn)題。以我們現(xiàn)在的一個(gè)項(xiàng)目為例,,這是一個(gè)為澳大利亞某機(jī)構(gòu)開(kāi)發(fā)的一個(gè)基于Smart Client應(yīng)用(Windows Form客戶端),,服務(wù)器部署于墨爾本,應(yīng)用的最終用戶可能需要跨越不同的州,。澳洲地廣人稀,,不同的州也有可能會(huì)跨越不同的時(shí)區(qū)。假設(shè)數(shù)據(jù)庫(kù)并不支持對(duì)時(shí)區(qū)的區(qū)分,,服務(wù)端需要對(duì)針對(duì)客戶端所在的時(shí)區(qū)對(duì)時(shí)間進(jìn)行相應(yīng)的處理,。不過(guò),對(duì)該問(wèn)題解決方案的介紹我會(huì)放在后續(xù)的文章中,,在這里我們先來(lái)介紹一些基礎(chǔ)性的內(nèi)容——談?wù)勎覀兪煜さ腟ystem.DateTime類型,。 一、你是否知道System.DateTimeKind,?System.DateTime類型,,我們?cè)偈煜げ贿^(guò)。順便說(shuō)一下,,這個(gè)類型不是class,,而是一個(gè)struct,換言之它是值類型,,而不是引用類型,。DateTime處理包含我們熟悉的年、月,、日,、時(shí)、分,、秒和毫秒等基本屬性之外,,還具有一個(gè)重要的表示時(shí)間類型(Kind)的屬性:Kind。該屬性的類型為System.DateTimeKind枚舉,。DateTimeKind定義如下,,它具有三個(gè)枚舉值:Unspecified,、Utc和Local。后兩個(gè)分別表示UTC(格林威治時(shí)間)和本地時(shí)間,。Unspecified顧名思義,,就是尚未指定具體類型,這是默認(rèn)值,。 1: [Serializable, ComVisible(true)] 2: public enum DateTimeKind 3: {
4: Unspecified,
5: Utc,
6: Local
7: }
在DateTime類型中,,表示時(shí)間類型的Kind屬性是只讀的,只能在構(gòu)造函數(shù)中指定,。相關(guān)構(gòu)造函數(shù)和Kind屬性的定義如下面的代碼片斷所示: 1: [Serializable]
2: public struct DateTime 3: {
4: //Others... 5: public DateTimeKind Kind { get; } 6:
7: public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind); 8: public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind); 9: public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind); 10: }
雖然,,Kind屬性是只讀的,但是我們還用另外一中設(shè)定Kind的方式,,那就是調(diào)用DateTime的靜態(tài)方法的SpecifyKind,。該方法不會(huì)真正去修改一個(gè)現(xiàn)有DateTime對(duì)象的Kind屬性,而是會(huì)重新創(chuàng)建一個(gè)新的DateTime對(duì)象,。方法返回的對(duì)象具有和指定時(shí)間相同的基本屬性(年,、月、日,、時(shí),、分、秒和毫秒),,該DateTime對(duì)象具有你指定的DateTimeKind值,。 1: public struct DateTime 2: {
3: //Others... 4: public static DateTime SpecifyKind(DateTime value, DateTimeKind kind); 5: }
二、幾個(gè)常用DateTime對(duì)象的DateTimeKind處理直接通過(guò)構(gòu)造函數(shù)構(gòu)建DateTime對(duì)象之外,,我們還經(jīng)常用到DateTime的幾個(gè)靜態(tài)只讀屬性去獲取一些特殊的時(shí)間,,比如Now、UtcNow,、MinValue和MaxValue等,,那么這些DateTime對(duì)象的DateTimeKind又是什么呢?
上面列表對(duì)幾個(gè)常用DateTime對(duì)象Kind屬性的描述可以通過(guò)下面的程序來(lái)證實(shí): 1: DateTime endOfTheWorld = new DateTime(2012, 12, 21); 2: Console.WriteLine("endOfTheWorld.Kind = {0}", endOfTheWorld.Kind); 3: Console.WriteLine("DateTime.SpecifyKind(endOfTheWorld, DateTimeKind.Utc).Kind = {0}", 4: DateTime.SpecifyKind(endOfTheWorld, DateTimeKind.Utc).Kind);
5: Console.WriteLine("endOfTheWorld.Kind = {0}", endOfTheWorld.Kind); 6: Console.WriteLine("DateTime.Now.Kind = {0}", DateTime.Now.Kind); 7: Console.WriteLine("DateTime.UtcNow.Kind = {0}", DateTime.UtcNow.Kind); 8: Console.WriteLine("DateTime.MinValue.Kind = {0}", DateTime.MinValue.Kind); 9: Console.WriteLine("DateTime.MaxValue.Kind = {0}", DateTime.MaxValue.Kind); 輸出結(jié)果: 1: endOfTheWorld.Kind = Unspecified
2: DateTime.SpecifyKind(endOfTheWorld, DateTimeKind.Utc).Kind = Utc
3: endOfTheWorld.Kind = Unspecified
4: DateTime.Now.Kind = Local
5: DateTime.UtcNow.Kind = Utc
6: DateTime.MinValue.Kind = Unspecified
7: DateTime.MaxValue.Kind = Unspecified
三,、DateTime的對(duì)等性問(wèn)題接下來(lái),,我們來(lái)談?wù)劻硗庖粋€(gè)比較有意思的問(wèn)題——兩個(gè)DateTime對(duì)象對(duì)等性,。在這之前,我首先提出這樣一個(gè)問(wèn)題:“如果兩個(gè)DateTime對(duì)象相等,,是否意味著它們表示同一個(gè)時(shí)間點(diǎn),?”我想有人會(huì)認(rèn)為是。但是答案是“不一定”,,我們可以舉一個(gè)反例,。在下面的程序中,,我創(chuàng)建了三個(gè)DateTime對(duì)象,,年、月,、日,、時(shí)、分,、秒均是相同的,,但Kind分分別指定為DateTimeKind.Local、DateTimeKind.Unspecified和DateTimeKind.Utc,。 1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local); 2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified); 3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc); 4:
5: Console.WriteLine("endOfTheWorld1 == endOfTheWorld2 = {0}", endOfTheWorld1 == endOfTheWorld2); 6: Console.WriteLine("endOfTheWorld2 == endOfTheWorld3 = {0}", endOfTheWorld2 == endOfTheWorld3); 由于我們處于東8區(qū),,基于DateTimeKind.Local的endOfTheWorld1和基于DateTimeKind.Utc的endOfTheWorld3,不可能表示的是同一個(gè)時(shí)刻,。但是從下面的輸出結(jié)果來(lái)看,,它們卻是“相等的”,不但如此,,Kind為Unspecified的endOfTheWorld2也和這兩個(gè)時(shí)間對(duì)象相等,。 1: endOfTheWorld1 == endOfTheWorld2 = True
2: endOfTheWorld2 == endOfTheWorld3 = True
由此可見(jiàn),DateTimeKind對(duì)等性判斷和DateTimeKind無(wú)關(guān),,那么在內(nèi)部是如何進(jìn)行判斷的呢,?要回答這個(gè)問(wèn)題,這就要談?wù)凞ateTime另外一個(gè)重要的屬性——Ticks了,。該屬性定義如下,,是DateTime的只讀屬性,類型為長(zhǎng)整型,,表示該DateTime對(duì)象通過(guò)日期和時(shí)間體現(xiàn)出來(lái)的計(jì)時(shí)周期數(shù),。每個(gè)計(jì)時(shí)周期表示一百納秒,即一千萬(wàn)分之一秒,。1 毫秒內(nèi)有 10,000 個(gè)計(jì)時(shí)周期,。此屬性的值表示自公元元年( 0001 年) 1 月 1 日午夜 12:00:00(表示 DateTime.MinValue)以來(lái)經(jīng)過(guò)的以100 納秒為間隔的間隔數(shù)。 1: public struct DateTime 2: {
3: //Others... 4: public long Ticks { get; } 5: }
注意,,這里的基準(zhǔn)時(shí)間0001 年 1 月 1 日午夜 12:00:00,,并沒(méi)有說(shuō)是一定是UTC時(shí)間,,所以Ticks和DateTimeKind無(wú)關(guān),這里通過(guò)下面的實(shí)例看出來(lái): 1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local); 2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified); 3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc); 4:
5: Console.WriteLine("endOfTheWorld1.Ticks = {0}", endOfTheWorld1.Ticks); 6: Console.WriteLine("endOfTheWorld2.Ticks = {0}", endOfTheWorld2.Ticks); 7: Console.WriteLine("endOfTheWorld3.Ticks = {0}", endOfTheWorld3.Ticks); 從下面的輸出結(jié)果我們不難看出,,上面創(chuàng)建的具有不同DateTimeKind的三個(gè)DateTime的Ticks屬性的值都是相等的,。實(shí)際上,DateTime的對(duì)等性判斷就是通過(guò)Ticks的大小來(lái)判斷的,。 1: endOfTheWorld1.Ticks = 634917312000000000
2: endOfTheWorld2.Ticks = 634917312000000000
3: endOfTheWorld3.Ticks = 634917312000000000
我們經(jīng)常說(shuō)的UTC時(shí)間和本地時(shí)間之間的相互轉(zhuǎn)化,,實(shí)際上指的就是將一個(gè)具有某種DateTimeKind的DateTime對(duì)象轉(zhuǎn)化成具有另外一種DateTimeKind的DateTime對(duì)象,并且確保兩個(gè)DateTime對(duì)象對(duì)象表示相同的時(shí)間點(diǎn),。關(guān)于時(shí)間轉(zhuǎn)換的實(shí)現(xiàn),,我們有很多不同的選擇。四,、通過(guò)DateTime類型的ToLocalTime和ToUniversalTime方法實(shí)現(xiàn)UTC和Local的轉(zhuǎn)換對(duì)基于三種不同DateTimeKind的DateTime對(duì)象之間的轉(zhuǎn)化,,最方便的就是直接采用DateTime類型的兩個(gè)對(duì)應(yīng)的方法:ToLocalTime和ToUniversalTime,這兩個(gè)方法的定義如下,。 1: public struct DateTime 2: {
3: //Others... 4: public DateTime ToLocalTime(); 5: public DateTime ToUniversalTime(); 6: }
實(shí)際上我們所說(shuō)的不同DateTimeKind之間的DateTime之間的轉(zhuǎn)化主要包括兩個(gè)方面:將一個(gè)DateTimeKind.Local(或者DateTimeKind.Unspecified)時(shí)間轉(zhuǎn)換成DateTimeKind.Utc時(shí)間,,或者將DateTimeKind.Utc(或者DateTimeKind.Unspecifed時(shí)間)轉(zhuǎn)換成DateTimeKind.Local時(shí)間。為了深刻地理解兩種不同轉(zhuǎn)換采用的轉(zhuǎn)化規(guī)則,,我寫了如下一段程序: 1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local); 2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified); 3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc); 4:
5: Console.WriteLine("endOfTheWorld1.ToLocalTime() = {0}",endOfTheWorld1.ToLocalTime()); 6: Console.WriteLine("endOfTheWorld2.ToLocalTime() = {0}", endOfTheWorld2.ToLocalTime()); 7: Console.WriteLine("endOfTheWorld3.ToLocalTime() = {0}\n", endOfTheWorld3.ToLocalTime()); 8:
9: Console.WriteLine("endOfTheWorld1.ToUniversalTime() = {0}", endOfTheWorld1.ToUniversalTime()); 10: Console.WriteLine("endOfTheWorld2.ToUniversalTime() = {0}", endOfTheWorld2.ToUniversalTime()); 11: Console.WriteLine("endOfTheWorld3.ToUniversalTime() = {0}", endOfTheWorld3.ToUniversalTime()); 對(duì)于DataTimeKind為Utc和Local之間的轉(zhuǎn)化,,沒(méi)有什么可以說(shuō)得,就是一個(gè)基于時(shí)差的換算而已,。大家容易忽視的是DataTimeKind.Unspecifed時(shí)間分別向其他兩種DateTimeKind時(shí)間的轉(zhuǎn)換問(wèn)題,。從下面的輸出我們可以看出,當(dāng)DateTimeKind.Unspecifed時(shí)間向DateTimeKind.Local轉(zhuǎn)換的時(shí)候,,實(shí)際上是當(dāng)成DateTimeKind.Utc時(shí)間,;而向DateTimeKind.Utc轉(zhuǎn)換的時(shí)候,則當(dāng)成是DateTimeKind.Local,。順便補(bǔ)充一下:不論被轉(zhuǎn)換的時(shí)間屬于怎么的DateTimeKind,,調(diào)用ToLocalTime和ToUniversalTime方法的返回的時(shí)間的Kind屬性總是DateTimeKind.Local和DateTimeKind.Utc,兩者之間的轉(zhuǎn)換并不只是年月日和時(shí)分秒的改變,。
1: endOfTheWorld1.ToLocalTime() = 12/21/2012 12:00:00 AM
2: endOfTheWorld2.ToLocalTime() = 12/21/2012 8:00:00 AM
3: endOfTheWorld3.ToLocalTime() = 12/21/2012 8:00:00 AM
4:
5: endOfTheWorld1.ToUniversalTime() = 12/21/2012 4:00:00 PM
6: endOfTheWorld2.ToUniversalTime() = 12/21/2012 4:00:00 PM
7: endOfTheWorld3.ToUniversalTime() = 12/21/2012 12:00:00 AM
五,、通過(guò)TimeZoneInfo實(shí)現(xiàn)Utc和Local的轉(zhuǎn)換上面提供的方式雖然簡(jiǎn)單,但是功能上確有局限,,因?yàn)?strong>轉(zhuǎn)換的過(guò)程是基于本機(jī)當(dāng)前的時(shí)區(qū),。這解決不了我在開(kāi)篇介紹的應(yīng)用場(chǎng)景:服務(wù)端根據(jù)訪問(wèn)者所在的時(shí)區(qū)(而不是本機(jī)的時(shí)區(qū))進(jìn)行時(shí)間的轉(zhuǎn)換。換句話說(shuō),,我們需要能夠基于任意時(shí)區(qū)的時(shí)間轉(zhuǎn)換方式,,這就可以通過(guò)System.TimeZoneInfo。 TimeZoneInfo實(shí)際上對(duì)原來(lái)System.TimeZone類型的一個(gè)改進(jìn)。它是一個(gè)可序列化的類型(這一點(diǎn)在分布式場(chǎng)景中進(jìn)行基于時(shí)區(qū)的時(shí)間處理實(shí)現(xiàn)非常重要),,表示具體某個(gè)時(shí)區(qū)的信息,。它提供了一系列靜態(tài)方法供我們對(duì)某個(gè)DateTime對(duì)象進(jìn)行基于指定TimeZoneInfo的時(shí)間轉(zhuǎn)換,在這我們介紹我們常用的2個(gè):ConvertTimeFromUtc和ConvertTimeToUtc,。前者將一個(gè)DateTimeKind.Utc或者Unspecified的DateTime時(shí)間轉(zhuǎn)換成基于指定時(shí)區(qū)的DateTimeKind.Local時(shí)間,;后者則將一個(gè)基于指定時(shí)區(qū)的DateTimeKind.Local或者DateTimeKind.Unspecified時(shí)間象轉(zhuǎn)化成一DateTimeKind.Utc時(shí)間。此外,,TimeZoneInfo還提供了兩個(gè)靜態(tài)屬性Local和Utc表示本地時(shí)區(qū)和格林威治時(shí)區(qū),。 1: [Serializable]
2: public sealed class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback 3: {
4: //Others... 5: public static DateTime ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone); 6: public static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfo sourceTimeZone); 7:
8: public static TimeZoneInfo Local { get; } 9: public static TimeZoneInfo Utc { get; } 10: }
我們照例來(lái)做個(gè)試驗(yàn)。還是剛才創(chuàng)建的三個(gè)DateTime對(duì)象,,現(xiàn)在我們分別調(diào)用ConvertTimeFromUtc將DateTimeKind.Utc或者DateTimeKind.Unspecified時(shí)間轉(zhuǎn)換成DateTimeKind.Local時(shí)間,;然后將調(diào)用ConvertTimeToUtc將DateTimeKind.Local或者DateTimeKind.Unspecified時(shí)間轉(zhuǎn)換成DateTimeKind.Utc時(shí)間。 1: DateTime endOfTheWorld1 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Local); 2: DateTime endOfTheWorld2 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Unspecified); 3: DateTime endOfTheWorld3 = new DateTime(2012, 12, 21, 0, 0, 0, DateTimeKind.Utc); 4:
5: Console.WriteLine("TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = {0}", 6: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2, TimeZoneInfo.Local));
7: Console.WriteLine("TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3,TimeZoneInfo.Local) = {0}\n", 8: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3, TimeZoneInfo.Local));
9:
10: Console.WriteLine("TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,TimeZoneInfo.Local) = {0}", 11: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1, TimeZoneInfo.Local));
12: Console.WriteLine("TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2,TimeZoneInfo.Local) = {0}", 13: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2, TimeZoneInfo.Local));
同上面進(jìn)行的轉(zhuǎn)換方式一樣,,在向DateTimeKind.Utc時(shí)間進(jìn)行轉(zhuǎn)換的時(shí)候,,DateTimeKind.Unspecifed時(shí)間被當(dāng)成DateTimeKind.Local,;而在向DateTimeKind.Local時(shí)間轉(zhuǎn)換的時(shí)候,,DateTimeKind.Unspecifed則被當(dāng)成DateTimeKind.Utc時(shí)間。 1: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/22/2012 8:00:00 AM
2: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld3,TimeZoneInfo.Local) = 12/22/2012 8:00:00 AM
3:
4: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,TimeZoneInfo.Local) = 12/21/2012 4:00:00 PM
5: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/21/2012 4:00:00 PM
ConvertTimeFromUtc和ConvertTimeToUtc方法在轉(zhuǎn)換的時(shí)候,,如果發(fā)現(xiàn)被轉(zhuǎn)換的時(shí)間和需要轉(zhuǎn)化時(shí)間具有相同的DateTimeKind會(huì)拋出異常,。也就是說(shuō),我們不能調(diào)用ConvertTimeFromUtc方法并傳入DateTimeKind.Local時(shí)間,,也不能調(diào)用ConvertTimeToUtc方法并傳入DateTimeKind.Urc時(shí)間,。如右圖所式,我們將一個(gè)DateTimeKind.Utc時(shí)間(DateTime.UtcNow)傳入ConvertTimeToUtc方法,,結(jié)果拋出一個(gè)ArgumentException異常,。錯(cuò)誤消息為:“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.
[相關(guān)閱讀]
|
|
來(lái)自: WindySky > 《JAVA總結(jié)》