Side Project 有段程式從外部 API 取得一個 DateTimeKind.Unspecified 的時間值,彙整時用 ToUniversalTime() 轉換為 UTC 時間再轉 JSON 儲存,依據規格該日期時間以台北時間為準,開發及測試階段結果如預期,不覺得這寫法有什麼不對。

不過,當程式部署到雲端主機,儲存的時間值出現時差。這才想起,踩到 Unspecified DateTimeKind 的轉換陷阱了

Unspecified很有趣(但也可能變成陷阱),ToUniversalTime()時被視為本地時間(台北時區)減去8小時,ToLocalTime()時被視為UTC時間加上8小時。

開發跟測試機跟雲端主機的時區不同,本地時間基準不同,會得到不同的結果,用以下範例重現問題。

DateTimeKind.Unspecified 的 2022-8-14 08:00 跑 ToUniversalTime(),在台北時區得到 8-14 00:00,若主機設成東京時間則為 8-13 23:00 (比台北早一小時):

既然規格上該 DateTimeKind.Unspecified 時間應視為台北時間處理,正確做法應用 TimeZoneInfo.FindSystemTimeZoneById() 取得台北時區 TimeZoneInfo 再用 TimeZoneInfo.ConvertTimeToUtc 以指定時區為基準轉換成 UTC 時間。程式範例如下:

var dateFmt = "yyyy-MM-dd HH:mm";
Action<DateTime> dispTime = (dt) => {
	Console.WriteLine(dt.ToString(dateFmt) + "/" + dt.Kind);
};
var d = new DateTime(2022, 8, 14, 08, 00, 00);
var tpeTime = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");
var tpeTimeToUtc = TimeZoneInfo.ConvertTimeToUtc(d, tpeTime);
dispTime(tpeTimeToUtc);
Console.WriteLine(TimeZone.CurrentTimeZone);

這樣子,即使程式在夏威夷跑,也能得到正確結果:

FindSystemTimeZoneById 的 "Taipei Standard Time" 要怎麼查?在微軟文件 Default Time Zones 可找到完整清單。

【補充】

When unspecified kind DateTime.ToUniversalTime(), it's based on local time and affected by timezone setting. So we need to use TimeZoneInfo to get identical results.


Comments

# by demo

上雲以後總是會踩一下時區問題,我之前也踩了😅 https://demo.tc/post/%E9%82%84%E5%9C%A8%E7%94%A8%20DateTime%20%E5%97%8E%EF%BC%9F%E8%A9%A6%E8%A9%A6%20DateTimeOffset%20%E5%90%A7

# by Jeffrey

to demo, 感謝分享,已補充於本文。

# by Chris Torng

可參考 Noda Time https://nodatime.org/ https://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html

# by Jeffrey

to Chris Torng,Noda Time 好眼熟,原來是 Jon Skeet 的 Side Project。謝謝分享

Post a comment