System.DateTime 詳解(續)
在《System.DateTime 詳解》一文中,我們從跨時區的角度剖析了我們熟悉的System.DateTime類型。如果你還是采用傳統的ADO.NET編程方式,并使用DataSet作為數據實體,可能你會熟悉System.Data.DataSetDateTime這么一個類型。這個類型也是為實現跨時區場景下對時間處理而設計的,為了對前文的補充,這篇文章就來談談基于DataSet的時間處理問題。
一、你是否關注過DataColumn的DateTimeMode屬性
在ADO.NET編程模型中,DataColumn代表DataTable的一個數據列,大家在熟悉不過了。不過,是否有人關注過一個名稱為DateTimeMode屬性,該屬性在DataColumn中的定義如下:
1: public class DataColumn : MarshalByValueComponent
2: {
3: //Others...
4: public DataSetDateTime DateTimeMode { get; set; }
5: }
從上面的代碼我們可以看出,DateTimeMode屬性的類型為DataSetDateTime,這實際上是一個枚舉類型。下面給出了DataSetDateTime的定義,該枚舉一共包含4個枚舉值:Local、Utc、Unspecified和UnspecifiedLocal。
1: public enum DataSetDateTime
2: {
3: Local = 1,
4: Unspecified = 2,
5: UnspecifiedLocal = 3,
6: Utc = 4
7: }
如果你讀過前文,了解了DateTimeKind,技術你之前不曾關注過這個類型,也會猜出DataColumn用類型為DataSetDateTime的DateTimeMode屬性指定時間的Kind。那么對于不同的DateTimeMode設置之間有何差異?為什么DataSetDateTime提供了另一個額外的成員UnspecifiedLocal呢?
二、不同的DateTimeMode設置對DateTimeKind的影響
這個問題很簡單,對于數據類型為DateTime的DataColumn,如果你對DateTimeMode屬性設置了不同的DataSetDateTime枚舉值,當你對其賦值的時候,系統會自動將設置的時間轉換成相應DateTimeKind的時間。下面的列表提供了基于不同DataSetDateTime枚舉值采用的的轉換規則:
- DataSetDateTime.Local: 對于DateTimeKind.Local時間,不做任何轉換;對于DateTimeKind.Utc時間,基于時區偏移量進行轉換,并將Kind屬性轉換成DateTimeKind.Local;對于DateTimeKind.Unspecified,直接將Kind屬性轉換成DateTimeKind.Local,時間值(年、月、日、時、分、秒、毫秒等)保持不變;
- DataSetDateTime.Utc: 對于DateTimeKind.Utc時間,不做任何轉換;對于DateTimeKind.Local時間,基于時區偏移量進行轉換,并將Kind屬性轉換成DateTimeKind.Utc;對于DateTimeKind.Unspecified,直接將Kind屬性轉換成DateTimeKind.Utc,時間值(年、月、日、時、分、秒、毫秒等)保持不變;
- DataSetDateTime.Unspecified|UnspecifiedLocal:對于任何DateTimeKind類型的時間,直接將Kind屬性轉換成DateTimeKind.Unspecified,時間值(年、月、日、時、分、秒、毫秒等)保持不變。
在這里我需要強調一下,在前文中我們提到:不同是調用DateTime的ToLocalTime/ToUtcTime方法,還是調用TimeZoneInfo的ConvertToUtcTime/ConvertFromUtcTime,如果將DateTimeKind.Unspecified轉換成DateTimeKind.Local時間,實際上是將其當成DateTimeKind.Utc時間;反之,如果轉換成DateTimeKind.Utc時間,則當成是DateTimeKind.Local時間。但是,在這里DateTimeKind.Unspecified的時間值會保留,改變的僅僅是Kind屬性。
三、一個簡單的例子
為了加深對上述轉換規則的理解,我寫了一個簡單的例子。首先我創建了一個ContractDataSet的強類型的DataSet,里面具有一個Contact數據表表示一個聯系人。Contact數據的結構如右圖所示:處理表示Id和名稱的兩個字段之外,我添加了四個DateTime類型的字段表示生日。它們分別是:LocalBirthday、UtcBirthday、UnspecifiedBirthday和UnspecifiedLocalBirthday,前綴表示該數據列采用的DateTimeMode。
然后,我寫了下面三個輔助的方法:CreateContact通過傳入的表示生日的DateTime創建一個ContractDataSet,DisplayBirthday分別將上訴四個字段的時間和Kind打印出來。
1: static ContactDataSet CreateContact(DateTime birthday)
2: {
3: var ds = new ContactDataSet();
4: var row = ds.Contact.NewContactRow();
5: row.Id = Guid.NewGuid().ToString();
6: row.Name = "Foo";
7: SetBirthDay(row,birthday);
8: ds.Contact.AddContactRow(row);
9: return ds;
10: }
11: static void SetBirthDay(ContactDataSet.ContactRow row,DateTime birthDay)
12: {
13: row.LocalBirthDay = birthDay;
14: row.UtcBirthDay = birthDay;
15: row.UnspecifiedBirthDay = birthDay;
16: row.UnspecifiedLocalBirthDay = birthDay;
17: }
18: static void DispalyBirthday(ContactDataSet ds)
19: {
20: var row = ds.Contact[0];
21: Console.WriteLine("\tLocal: \t\t\t{0}", row.LocalBirthDay);
22: Console.WriteLine("\tUtc: \t\t\t{0}", row.UtcBirthDay);
23: Console.WriteLine("\tUnspecified: \t\t{0}", row.UnspecifiedBirthDay);
24: Console.WriteLine("\tUnspecifiedLocal: \t{0}\n", row.UnspecifiedLocalBirthDay);
25:
26: Console.WriteLine("\tLocal: \t\t\t{0}", row.LocalBirthDay.Kind);
27: Console.WriteLine("\tUtc: \t\t\t{0}", row.UtcBirthDay.Kind);
28: Console.WriteLine("\tUnspecified: \t\t{0}", row.UnspecifiedBirthDay.Kind);
29: Console.WriteLine("\tUnspecifiedLocal: \t{0}\n", row.UnspecifiedLocalBirthDay.Kind);
30: }
我們的實例程序是這樣的:分別創建基于三種不同的DateTimeKind的DateTime對象,并據此創建三個ContractDataSet對象。最后調用DisplayBirthday方法將4個基于不同DateTimeMode的字段的時間和DateTimeKind打印出來。
1: var ds1 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Local));
2: var ds2 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Unspecified));
3: var ds3 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Utc));
4:
5: Console.WriteLine("DateTimeKind.Local");
6: DispalyBirthday(ds1);
7: Console.WriteLine("DateTimeKind.Unspecified");
8: DispalyBirthday(ds2);
9: Console.WriteLine("DateTimeKind.Utc");
10: DispalyBirthday(ds3);
最終的輸出結果證實了我們上述的關于時間轉換規則的結論:
1: DateTimeKind.Local
2: Local: 8/24/1981 12:00:00 AM
3: Utc: 8/23/1981 4:00:00 PM
4: Unspecified: 8/24/1981 12:00:00 AM
5: UnspecifiedLocal: 8/24/1981 12:00:00 AM
6:
7: Local: Local
8: Utc: Utc
9: Unspecified: Unspecified
10: UnspecifiedLocal: Unspecified
11:
12: DateTimeKind.Unspecified
13: Local: 8/24/1981 12:00:00 AM
14: Utc: 8/24/1981 12:00:00 AM
15: Unspecified: 8/24/1981 12:00:00 AM
16: UnspecifiedLocal: 8/24/1981 12:00:00 AM
17:
18: Local: Local
19: Utc: Utc
20: Unspecified: Unspecified
21: UnspecifiedLocal: Unspecified
22:
23: DateTimeKind.Utc
24: Local: 8/24/1981 8:00:00 AM
25: Utc: 8/24/1981 12:00:00 AM
26: Unspecified: 8/24/1981 12:00:00 AM
27: UnspecifiedLocal: 8/24/1981 12:00:00 AM
28:
29: Local: Local
30: Utc: Utc
31: Unspecified: Unspecified
32: UnspecifiedLocal: Unspecified
四、DataSetDateTime.Unspecified V.S. DataSetDateTime.UnspecifiedLocal
到不前為止,我們貌似還看不到DataSetDateTime.Unspecified和DataSetDateTime.UnspecifiedLoca的差別。實際上,它們的差別體現在序列化上面:DataSetDateTime.UnspecifiedLoca在序列化的時候會保留基于當前時區的偏移量,而DataSetDateTime.Unspecified則不會。這個結論我也可以實例來證實,為此我寫了如下一段代碼對ContactDataSet進行序列化,并將序列化后的XML打印出來。
1: var ds = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0));
2: using (MemoryStream stream = new MemoryStream(1000))
3: {
4: ds.WriteXml(stream);
5: Console.WriteLine(ASCIIEncoding.ASCII.GetString(stream.GetBuffer()).Trim());
6: }
從輸出的結果我們可以看出UnspecifiedBirthday和UnspecifiedLocalBirday之間的差別,后者有+8的偏移量,前者沒有。
1: <ContactDataSet xmlns="http://tempuri.org/ContactDataSet.xsd">
2: <Contact>
3: <Id>a1548a6b-9b8b-4799-bc10-31337e70c831</Id>
4: <Name>Foo</Name>
5: <UnspecifiedLocalBirthDay>1981-08-24T00:00:00+08:00</UnspecifiedLocalBirthDay>
6: <UnspecifiedBirthDay>1981-08-24T00:00:00</UnspecifiedBirthDay>
7: <UtcBirthDay>1981-08-24T00:00:00Z</UtcBirthDay>
8: <LocalBirthDay>1981-08-24T00:00:00+08:00</LocalBirthDay>
9: </Contact>
10: </ContactDataSet>
留言列表