CodeGym /課程 /C# SELF /時區操作: TimeZoneInfo

時區操作: TimeZoneInfo

C# SELF
等級 15 , 課堂 2
開放

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 的主要屬性

屬性 說明
Id
時區的系統識別碼(查找時用)
DisplayName
給用戶看的描述(有時差跟城市)
StandardName
「標準」時間的名稱
DaylightName
夏令時間的名稱
BaseUtcOffset
跟 UTC 的時差(例如柏林是 +01:00)
SupportsDaylightSavingTime
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 會出現兩次——但其實是「不同」的兩次。
  • 別忘了:時區列表會隨著作業系統更新而變!像有些國家會取消或新增夏令時間。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION