聊到將時間從 UTC 轉到台灣時間,居然還是聽到朋友表示使用 +8 小時的做法,驚為天人。這種做法可能會造成後續的問題,例如時區並不會跟著變動,或是遇到日光節約等特殊狀況就容易出事。和西元民國轉換直接 -1911 一樣不穩定。

這篇就用來記錄一下之前看過比較優雅的時區轉換方式,順便將先前存著的時間處理相關資料整理一下,方便之後需要時可以馬上回來查詢。

TimeZoneInfo: 時區資訊

轉換方式主要參考自 [食譜好菜] DateTime 具有文化特性的格式化及時區的轉換在各時區間轉換時間,感謝前人的指引。

關於文化特性,也可以參考本站的 菜雞抓蟲: DateTime.ToString() 之我們不一樣 & CultureInfo 文化特性小筆記 呦。

// 假設現在是要從標準時區 +00:00 轉換到台灣時區,故這邊使用 UtcNow 先取標準世界協調時間
var nowDateTime = DateTime.UtcNow;

nowDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 15:56:05 +00:00

// ==================================================

// 傳統的 直接對時間做計算的方式…
var addedDateTime = nowDateTime.AddHours(8);

addedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +00:00
// 可以看到儘管時間變動了,時區仍然還在 +00:00

// ==================================================

// 使用 TimeZoneInfo 先取得台北時區
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");

// 再使用 TimeZoneInfo 來變更時間
var convertedDateTime = TimeZoneInfo.ConvertTime(nowDateTime, timeZone);

convertedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +08:00
// 可以看到除了時間變更以外,時區也切換到 +08:00 了!

上面取得台北時區的步驟,可以參照 Time Zone IDs 來查詢想要的時區。這樣的時區切換方式,不僅副作用少,不會因為時區沒轉雷到後續接手的人,也省卻了擔心日光節約等等問題,這種事就交給微軟去煩惱吧!

另外這邊也逐步放一些時區處理相關的參考資料:

TaiwanCalendar: 西元年轉民國年
TaiwanLunisolarCalendar: 國曆轉農曆

可以參考這篇 基本題 - C# 西元年轉換取得民國年格式字串 - mrkt 的程式學習筆記

內文示範了使用 System.Globalization.TaiwanCalendarSystem.Globalization.TaiwanLunisolarCalendar 來進行安全轉換的作法。

基本上來說就是指定文化特性中的時間格式(曆法)為農曆,至少依靠微軟爸爸,比自己加減 1911 來得安全多了囧。

var time = new DateTime(2021, 12, 01);

// 直接從 TaiwanCalendar 取民國年,自組字串時常用
var taiwanCalendar = new TaiwanCalendar();
taiwanCalendar.GetYear(time).Dump(); // 110

// 將文化特性的曆法改成民國年
var info = new CultureInfo("zh-TW");
info.DateTimeFormat.Calendar = new TaiwanCalendar();

// 西元年轉民國年(字串)
time.ToString(info).Dump(); // 110/12/1 00:00:00

// 民國年(字串)轉西元年
var timeString = "110/12/1";
DateTime.Parse(timeString, info).Dump(); // 2021/12/01 00:00:00

DateTimeFormatInfo: 月、週、時間的格式

不用再傻傻地手刻陣列「星期一」、「星期二」、「星期三」…之類的了,只要用 DateTimeFormatInfo.CurrentInfo 的就好啦:

var MonthCh = DateTimeFormatInfo.CurrentInfo.MonthNames;   // 中文月份名稱列表
var MonthEn = DateTimeFormatInfo.InvariantInfo.MonthNames; // 英文月份名稱列表

除了月份以外,DateTimeFormatInfo.CurrentInfo 也包含了當地的其他欄位與格式。

例如 AbbreviatedDayNames 能拿到 週一、週二、週三…;DayNames 則會拿到 星期一、星期二、星期三…

時間格式的話,FullDateTimePattern 就會拿到 yyyy'年'M'月'd'日' tt hh:mm:ss 等等

內容還有許多欄位,有興趣的可以用 LinqPad 來 Dump 看看,或是直接翻閱 DateTimeFormatInfo 囉!

參考資料