1. 前言
如果你覺得時區只是「加減幾個小時」這麼簡單,你很快就會體會到全世界大多數工程師的痛苦。時區、夏令/冬令時間的切換、想把時間「轉到另一個地區」時突然冒出的例外——這些都會讓你踩到很難 debug、也很難跟用戶解釋的 bug。
最慘的是:世界上根本沒有什麼「通用」的時間概念——像是兩個鄰近的國家,對夏令時間的規則可能完全不同,有時候甚至會在一年中途突然改規則(真的有這種事!)。舉例來說:2011 年薩摩亞(Samoa)直接「跳過」一天,整個國家換到國際換日線的另一邊。
不過 C# 跟 .NET 給了我們很強大的工具,至少可以讓我們有點反擊的能力。今天我們要認識救星——TimeZoneInfo 這個型別,還有它的功能,幫你跟你的 app 一起搞定時區。
什麼時候你會需要處理時區
- 當你的用戶或 server 在世界不同地區
- 當你把日期時間存在資料庫裡用 UTC,但要顯示給用戶時要轉成他們的本地時間
- 要算排程、提醒、線上活動(「網路研討會在柏林 19:00 開始——你那邊是幾點?」)
- 開發企業級/全球系統(像是國際企業的會計或 CRM)
TimeZoneInfo 型別簡介
TimeZoneInfo 是 .NET 裡專門設計來表示時區資訊的 class:它有時區對 UTC 的偏移、名稱、支不支援夏令時間等等。
跟舊的 TimeZone class 不一樣,只知道「本地」跟「UTC」時區,TimeZoneInfo 可以完整存取系統裡所有註冊的時區(甚至你可以自己手動加)。
2. 取得時區資訊
本地跟 UTC 時區
要拿到本地時區資訊超簡單:
// 本地(也就是你程式現在跑的地方)
TimeZoneInfo local = TimeZoneInfo.Local;
// 永遠是 UTC
TimeZoneInfo utc = TimeZoneInfo.Utc;
Console.WriteLine(local.DisplayName); // 例如: (UTC+01:00) Berlin, Vienna
Console.WriteLine(utc.DisplayName); // (UTC) Coordinated Universal Time
所有可用的時區
很常會需要給用戶看所有可選的時區列表(像是註冊服務時):
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{tz.Id} | {tz.DisplayName}");
}
這會印出像這樣的列表:
- Central European Standard Time | (UTC+01:00) Berlin, Vienna
- Pacific Standard Time | (UTC-08:00) Pacific Time (US & Canada)
- 等等。
視覺化:TimeZoneInfo 的主要屬性
| 屬性 | 說明 |
|---|---|
|
時區的系統識別碼(查找時用) |
|
給用戶看的描述(有時差跟城市) |
|
「標準」時間的名稱 |
|
夏令時間的名稱 |
|
跟 UTC 的時差(例如柏林是 +01:00) |
|
True 代表這個時區有夏令/冬令時間切換 |
3. 不同時區之間的時間轉換
這就是我們為什麼要搞這一切!你會需要:
- 把時間從一個時區轉到另一個(像 server 存的是 UTC,但要顯示給用戶時轉成他的時區)
- 正確處理夏令/冬令時間的切換
基本語法
DateTime utcNow = DateTime.UtcNow;
// 比方說,把這個時間轉成中歐時區
TimeZoneInfo europeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTime europeTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, europeZone);
Console.WriteLine($"UTC now: {utcNow}"); // 2024-06-20 10:30:00
Console.WriteLine($"Europe now: {europeTime}"); // 2024-06-20 11:30:00
重點:內部運算跟儲存時,永遠用 UTC,只有「要顯示」時才轉成需要的時區!
怎麼知道時區的識別碼?
在 Windows 上時區的 Id 識別碼是特有的(像 "Central European Standard Time"),Linux/Unix 上通常是 IANA/Olson 識別碼("Europe/Berlin"、"America/New_York")。
你可以用 TimeZoneInfo.GetSystemTimeZones() 拿到你系統上的所有識別碼。
4. 任意時區之間的轉換
假設你拿到倫敦(GMT/UTC+0)的時間,要轉成東京(UTC+9):
DateTime londonTime = new DateTime(2024, 6, 20, 12, 0, 0, DateTimeKind.Unspecified);
TimeZoneInfo londonZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
TimeZoneInfo tokyoZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
// 第一步:把時間轉成 UTC(如果它是本地時間)
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(londonTime, londonZone);
// 第二步:把 UTC 轉成東京時間
DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tokyoZone);
Console.WriteLine($"London: {londonTime} | UTC: {utc} | Tokyo: {tokyoTime}");
處理夏令/冬令時間切換
TimeZoneInfo 會自動考慮夏令/冬令時間的切換——只要那個時區有支援。
TimeZoneInfo eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dateWinter = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Unspecified);
DateTime dateSummer = new DateTime(2024, 7, 1, 12, 0, 0, DateTimeKind.Unspecified);
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateWinter, eastern)); // UTC-5 時差
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateSummer, eastern)); // UTC-4 時差——夏令時間!
就算美國(上面例子)哪天真的不玩夏令時間了(每年都有人提案!),你作業系統的時區資料庫還是會更新,你的 code 也會跟著正確運作。
5. 實用建議跟常見錯誤
例外狀況跟地雷
- 在調整時鐘的時刻(像夏令/冬令時間切換那天凌晨 2:30),有些時間可能「不存在」或「重複」:
比如說,調回來那天 2:30 會出現兩次——但其實是「不同」的兩次。 - 別忘了:時區列表會隨著作業系統更新而變!像有些國家會取消或新增夏令時間。
GO TO FULL VERSION