收到友人貢獻茶包一枚。

.NET DateTime.Parse() 有其好用之處,可以解析各種日期時間格式:

	Console.WriteLine(DateTime.Parse("2022-06-01"));
	Console.WriteLine(DateTime.Parse("2022/06/01"));
	Console.WriteLine(DateTime.Parse("06/2022"));
	Console.WriteLine(DateTime.Parse("2022/06/01 21:00"));
	Console.WriteLine(DateTime.Parse("6/1/2022 9:00:00 PM"));
	Console.WriteLine(DateTime.Parse("2022/6/1 下午 09:00:00"));
	Console.WriteLine(DateTime.Parse("Wed, 1 Jun 2022 13:00:00 GMT"));

之前有研究過 DateTime.Parse(),甚至踩過數字 3.1416 被 DateTime.Parse() 解析成 1416-03-01 的雷,對於輸入格式較固定的情境,我偏好 DateTime.ParseExact(),

今天的案例讓我復習到 DateTime.Parse() 決定時區的規則

一般來說,DateTime.Parse 傳回 DateTime 物件的 Kind 屬性為 DateTimeKind.Unspecified,但在以下情況則會轉為 Local 或 Utc:

  1. 日期時間字串包含時區資訊,例如:Z 或 +08:00 結尾、GMT 字樣
    轉換為本地時間,Kind = DateTimeKind.Local
  2. 日期時間字串包含時區資訊,DateTimeStyles 參數包含 AdjustToUniversal
    轉換成 UTC 時間,Kind = DateTimeKind.Utc
  3. 字串包含 Z 或 GMT,且 DateTimeStyles 參數包含 RoundtripKind
    解析為 UTC 時間,Kind = DateTimeKind.Utc

實測驗證如下:

void Main()
{
	test("2022-06-01");
	test("2022-06-01 00:00");
	Console.WriteLine("\n日期時間字串包含時區資訊");
	test("2022-06-01+08:00");
	test("2022-06-01+00:00");
	test("2022-06-01T00:00:00Z");
	test("2022-06-01Z");
	Console.WriteLine("\n日期時間字串包含時區資訊 + AdjustToUniversal");
	test("2022-06-01Z", DateTimeStyles.AdjustToUniversal);
	test("2022-06-01+08:00", DateTimeStyles.AdjustToUniversal);
	test("2022-06-01+08:00", DateTimeStyles.AdjustToUniversal);
	//不合條件(無時區資訊)
	test("2022-06-01", DateTimeStyles.AdjustToUniversal);
	Console.WriteLine("\n日期時間字串包含Z、GMT + RoundtripKind");
	test("2022-06-01 Z", DateTimeStyles.RoundtripKind);
	test("2022-06-01 GMT", DateTimeStyles.RoundtripKind);
	//不合條件(非 Z 或 GMT)
	test("2022-06-01+08:00", DateTimeStyles.RoundtripKind);
	test("2022-06-01+00:00", DateTimeStyles.RoundtripKind);
}
void test(string t, DateTimeStyles s = DateTimeStyles.None)
{
	var d = DateTime.Parse(t, null, s);
	Console.WriteLine(d.Kind + " | " + d.ToString());
}

回到茶包案例:程式用 DateTime.Parse() 解析外部傳入的 yyyy-MM-dd 字串轉為 DateTime 存入資料庫。資料來源意外改變了日期格式,在 yyyy-MM-dd 之外加上 +00:00 時區,由 2022-06-01 改為 2022-06-01+00:00,依上述規則,解析出來的 DateTime 原本是 Unspecified 2022-06-01 00:00:00,現在變成 Local 2022-06-01 08:00:00,程式沒出錯但時間差了八小時,費了點功夫追查才找出原因。

如果用 DateTime.ParseExact() 可以避免嗎?

如上所示,ParseExact 只要格式改變,解析時便會直接噴錯,遠比錯誤資料流入後端程序容易偵測及善後。因此,我認為對固定格式來源使用 ParseExact() 是較好的設計。

Introduce to the time zone rule while DateTime.Parse(), and compare the results of Parse() and ParseExact() when input format changed.


Comments

# by 赫達利 協理 JOJO徐

黑暗大大,您好: 在下時常拜讀您的文章, 有許多文章都協助在下處理掉難解的問題 為了希望之後本公司其他工程師也可以方便地找到您的文章增進知識 並能增加本公司網站的活絡度 因此在下想要將您的文章分享在在下任職的公司官網當知識分享文章 並統一會使用您的Logo與文末連結宣告出處為此部落格,以宣告文章作者為您 且保證不會用於商業用途 請問能否同意本公司分享您的文章?? 感謝您的回應 本公司的網址 https://www.gomoney.com.tw/ Email:jojo.hsu@gomoney.com.tw

# by Jeffrey

to JOJO徐,部落格文章歡迎推廣分享,但請避免採用全文轉載形式。身為創作者,我把配色、排版也視為作品的一部分,全文轉貼會破壞完整性。另一方面,我文章的修正或補充頻率還蠻高的,故建議採用簡要說明 + 連結的做法,讓大家儘可能看到最新版本。歡迎分享,但再麻煩您依前述原則處理,感謝。

# by ByTim

To Jeffrey: 告知一個問題給你,在RDLC(報表)上的DateTime的時區是西元的,若程式碼DateTime的時區是民國年的,將會有問題,我的作法是程式碼這邊,直接轉成要輸出的字串。 例如 程式碼 DateTime d1 =106/01/01 (2017/01/01),RDLC會成 XXXX/01/01 (0106/01/01)。

Post a comment