DateTime和DateTimeOffset是同胞兄弟嗎?

2023-07-28 06:02:06

小編在日常開發中,用得最多的時間型別就是DateTime,直到一次偶然的邂逅,讓小編遇見了DateTimeOffset。當時小編也是一臉迷茫,因為在小編的C#程式設計字典裡就沒出現過DateTimeOffset的字樣,實屬慚愧。於是心中立馬產生疑惑:DateTimeOffset是用來幹嘛的?它和DateTime之間又是什麼關係?帶著種種疑問和不解,小編今天就帶你一起一窺究竟!

開場前,先了解下GMT、UTC、TimeZone這三個時間概念

GMT:Greenwich Mean Time,格林威治平時(也稱格林威治時間)。它規定太陽每天經過位於英國倫敦郊區的皇家格林威治天文臺的時間為中午12點。1972年之前,格林威治時間(GMT)一直是世界時間的標準。1972年之後,GMT 不再是一個時間標準了,取而代之的是UTC。

UTC:Coodinated Universal Time,協調世界時,又稱世界統一時間、世界標準時間、國際協調時間。UTC 是現在全球通用的時間標準,全球各地都同意將各自的時間進行同步協調。

TimeZone: 時間區域,簡稱時區。按照規定從格林威治本初子午線起,經度每向東或者向西間隔15°,就劃分一個時區,在這個區域內,大家使用同樣的標準時間。全球共分為24個標準時區,相鄰時區的時間相差一個小時。

舉個例子:北京位於東八區,和倫敦時間(UTC時間)相差+8個小時。當小編每天早上8點出門上班的時候 ,大洋彼岸的英國人民正處於睡夢中,因為那兒正值午夜零點,這就是我們常說的時差。順帶提一句,雖然我國地域遼闊橫跨5個時區,但統一採用北京時間作為標準時間,目的就是為了方便時間管理。

首先登場的是:DateTime

DateTime表示的是一個日期和時間值。這裡我們先思考一個問題:假設有個陌生人電話告訴你現在時間是某月某日某時某分,但你又不知道對方在哪裡,那麼這個時間對你來說是不清晰的,它可能是當地時間(東京時間、悉尼時間...),也有可能是UTC時間。因此DateTime有個唯讀屬性Kind,它是一個DateTimeKind列舉,用來表示時間型別。定義如下:

1 public enum DateTimeKind
2 {
3     // 未指明是UTC時間,還是本地時間
4     Unspecified = 0, 
5     // 表示UTC時間
6     Utc = 1, 
7     // 表示本地時間
8     Local = 2
9  }

 通過DateTime建構函式,可以看出Kind屬性值是由建構函式傳入的。下面來看DateTime的幾種常見用法:

 1 DateTime dt1 = new DateTime(2023, 7, 27, 8, 40, 25); 
 2 Console.WriteLine($"DateTime:{dt1}, 時間型別:{dt1.Kind}, 轉本地時間:{dt1.ToLocalTime()},轉UTC時間:{dt1.ToUniversalTime()}");
 3 
 4 DateTime dt2 = DateTime.Now; 
 5 Console.WriteLine($"DateTime:{dt2}, 時間型別:{dt2.Kind}, 轉本地時間:{dt2.ToLocalTime()},轉UTC時間:{dt2.ToUniversalTime()}");
 6 
 7 DateTime dt3 = DateTime.UtcNow; 
 8 Console.WriteLine($"DateTime:{dt3}, 時間型別:{dt3.Kind}, 轉本地時間:{dt3.ToLocalTime()},轉UTC時間:{dt3.ToUniversalTime()}");
 9 
10 DateTime dt4 = new DateTime(2023, 7, 27, 8, 40, 25, DateTimeKind.Local);
11 Console.WriteLine($"DateTime:{dt4}, 時間型別:{dt4.Kind}, 轉本地時間:{dt4.ToLocalTime()},轉UTC時間:{dt4.ToUniversalTime()}");
12 
13 DateTime dt5 = new DateTime(2023, 7, 27, 8, 40, 25, DateTimeKind.Utc);
14 Console.WriteLine($"DateTime:{dt5}, 時間型別:{dt5.Kind}, 轉本地時間:{dt5.ToLocalTime()},轉UTC時間:{dt5.ToUniversalTime()}");

 輸出結果:

DateTime: 2023 / 7 / 27 8:40:25, 時間型別: Unspecified, 轉本地時間: 2023 / 7 / 27 16:40:25,轉UTC時間: 2023 / 7 / 27 0:40:25
DateTime: 2023 / 7 / 27 14:04:00, 時間型別: Local, 轉本地時間: 2023 / 7 / 27 14:04:00,轉UTC時間: 2023 / 7 / 27 6:04:00
DateTime: 2023 / 7 / 27 6:04:00, 時間型別: Utc, 轉本地時間: 2023 / 7 / 27 14:04:00,轉UTC時間: 2023 / 7 / 27 6:04:00
DateTime: 2023 / 7 / 27 8:40:25, 時間型別: Local, 轉本地時間: 2023 / 7 / 27 8:40:25,轉UTC時間: 2023 / 7 / 27 0:40:25
DateTime: 2023 / 7 / 27 8:40:25, 時間型別: Utc, 轉本地時間: 2023 / 7 / 27 16:40:25,轉UTC時間: 2023 / 7 / 27 8:40:25

由此可見,DateTime是通過Kind屬性來區分是本地時間還是UTC時間,同時本地時間和UTC時間之間可以相互轉換,轉換條件就是根據你電腦當前所設定的時區。不足之處就是DateTime提供了非常有限的時區資訊,如果不看Kind屬性,你無法判斷它是來自本地時間還是UTC時間,因此在跨時區系統間的資料移植性較差(除非指定是UTC時間)。

接著出場的就是:DateTimeOffset

DateTimeOffset表示一個時間點,通常以相對於協調世界時 (UTC) 的日期和時間來表示。它既包含了DateTime屬性,又包含了一個TimeSpan型別的Offset屬性(表示相對於UTC時間的偏移量).

通過DateTimeOffset建構函式,可以看出Offset屬性值是由建構函式傳入的。下面來看DateTimeOffset的幾種常見用法:

 1 DateTimeOffset dto1 = new DateTimeOffset(2023, 7, 27, 8, 40, 25, TimeSpan.Zero);
 2 Console.WriteLine($"DateTimeOffset:{dto1}, 時間偏移:{dto1.Offset}, 轉本地時間:{dto1.ToLocalTime()},轉UTC時間:{dto1.ToUniversalTime()}");
 3 
 4 DateTimeOffset dto2 = DateTimeOffset.Now;
 5 Console.WriteLine($"DateTimeOffset:{dto2}, 時間偏移:{dto2.Offset}, 轉本地時間:{dto2.ToLocalTime()},轉UTC時間:{dto2.ToUniversalTime()}");
 6 
 7 DateTimeOffset dto3 = DateTimeOffset.UtcNow;
 8 Console.WriteLine($"DateTimeOffset:{dto3}, 時間偏移:{dto3.Offset}, 轉本地時間:{dto3.ToLocalTime()},轉UTC時間:{dto3.ToUniversalTime()}");
 9 
10 DateTimeOffset dto4 = new DateTimeOffset(2023, 7, 27, 8, 40, 25, new TimeSpan(8, 0, 0));
11 Console.WriteLine($"DateTimeOffset:{dto4}, 時間偏移:{dto4.Offset}, 轉本地時間:{dto4.ToLocalTime()},轉UTC時間:{dto4.ToUniversalTime()}");
12 
13 DateTimeOffset dto5 = new DateTimeOffset(new DateTime(2023, 7, 27, 8, 40, 25), new TimeSpan(-8, 0, 0));
14 Console.WriteLine($"DateTimeOffset:{dto5}, 時間偏移:{dto5.Offset}, 轉本地時間:{dto5.ToLocalTime()},轉UTC時間:{dto5.ToUniversalTime()}");

 輸出結果:

DateTimeOffset:2023/7/27 8:40:25 +00:00, 偏移量:00:00:00, 轉本地時間:2023/7/27 16:40:25 +08:00,轉UTC時間:2023/7/27 8:40:25 +00:00
DateTimeOffset:2023/7/27 15:46:14 +08:00, 偏移量:08:00:00, 轉本地時間:2023/7/27 15:46:14 +08:00,轉UTC時間:2023/7/27 7:46:14 +00:00
DateTimeOffset:2023/7/27 7:46:14 +00:00, 偏移量:00:00:00, 轉本地時間:2023/7/27 15:46:14 +08:00,轉UTC時間:2023/7/27 7:46:14 +00:00
DateTimeOffset:2023/7/27 8:40:25 +08:00, 偏移量:08:00:00, 轉本地時間:2023/7/27 8:40:25 +08:00,轉UTC時間:2023/7/27 0:40:25 +00:00
DateTimeOffset:2023/7/27 8:40:25 -08:00, 偏移量:-08:00:00, 轉本地時間:2023/7/28 0:40:25 +08:00,轉UTC時間:2023/7/27 16:40:25 +00:00

可以看出,DateTimeOffset除了包含日期和時間之外,還包含相對於UTC時間的偏移量,因此不再需要Kind屬性,就能區分出是UTC時間(如:dto1和dto3)還是本地時間(如:dto2、dto4和dto5),同時還能反饋時間所在的時區(如:dto2和dto4是東八區,dto5是西八區)。

最後小編得出結論:

DateTimeOffset和DateTime之間的確存在血緣關係,DateTimeOffset相當於是DateTime的一個包裝,它除了具有DateTime的所有功能之外,還包含時區資訊。如果你的系統不需要考慮時區問題或者只用UTC時間,那麼DateTime就夠用了。如果你的系統是全球化的,客戶來自不同時區的不同國家,那麼伺服器端就需要用DateTimeOffset來儲存時間,這樣使用者端很容易將帶有時區的時間字串(如:"2023/7/27 8:40:25 +08:00")轉成本地時間來顯示。總而言之,小編認為DateTimeOffset功能比DateTime更強大、也更加靈活。微軟官方建議用DateTimeOffset作為應用開發的預設時間型別

參考資料:

 徹底弄懂GMT、UTC、時區和夏令時 - 知乎 (zhihu.com)

c#:細說時區、DateTime和DateTimeOffset在國際化中的應用 - 知乎 (zhihu.com)

談談你最熟悉的System.DateTime[上篇] - Artech - 部落格園 (cnblogs.com)

Compare types related to date and time | Microsoft Learn