Windows Service 與日期時間格式

如果你正在撰寫 Windows service 應用程式,裡面需要將 DateTime 轉換成字串,而在轉換時不指定日期時間格式,例如 DateTime.Now.ToString()。這樣的寫法,你認為會得到什麼格式的日期時間字串?我想大概在 99% 的情況下,答案都非常明顯:根據目前執行緒的 CurrentCulture 而定,預設就是使用控制台裡面的設定。可是我卻碰到了那 1% 的狀況,整個追蹤過程就是覺得不可思議,直到真相大白。

問題描述

在一個分散式架構系統中,兩個 Windows service 應用程式彼此透過網路互相傳遞訊息,其中會有將物件序列化和反序列化的動作。在執行用戶端應用程式的某項功能時,發現全無反應(也沒有看到 exception,這是另一個問題,這裡不談),再去伺服器端檢查事件檢視器,發現用來提供訊息傳遞服務的那個 Windows service 拋出例外:

System.FormatException: The string was not recognized as a valid DateTime. There is an unknown word starting at index 9.

追蹤過程

既然是日期時間格式錯誤,頭一個想到的,當然就是看看程式的寫法,有沒有用特定的格式化字串或改變了 Thread.CurrentThread.CurrentCulture。詢問相關開發人員,確認沒有上述情形,只有幾個地方有用到 "o" 來格式化日期時間。這個部分是為了保留時區,看起來應該沒有嫌疑。

接著檢查控制台的設定。從用戶端到伺服器端的每一台電腦都檢查過了,日期時間都一致設定成 English (United States)格式。

詭異的是,各台用戶端,包括我的機器上的語系國別、日期時間格式都設定成一樣,程式碼版本也相同,卻只有我的機器會引發這個錯誤。

看來似乎只有在本機進行單步除錯,才可能抓到兇手了。

透過 stack trace 提供的資訊,我發現是某個類別在進行反序列化時,把日期時間字串轉換成 DateTime 時發生了錯誤。但是由於某些原始碼無法取得,我無法追蹤到產生錯誤格式字串的源頭,而只能從「外圍程式碼」去推敲。(Just-in-time Debugger 是會提示你要不要除錯,但是最終還是得告訴 Visual Studio 你的原始碼檔案在哪裡,才有辦法追進去。)

所幸這個「外圍程式碼」也是個獨立的 DLL 專案,我在裡面加了點 try...catch 程式碼,然後在錯誤發生時,將有問題的日期格式字串輸出到 log 檔案。結果看到這樣的東西:

0001/1/1 上午 12:00:00

裡面有中文字!這就難怪反序列化時會出錯了呀!

可是,怎麼會呢?我的 Windows 環境,控制台的格式設定當中並沒有用到任何中文字:


那個日期時間字串中的「上午」究竟是怎麼來的呢?

陸續做了些嘗試和實驗,其中有個實驗提供了線索:把那個拋出例外的 Windows service 的啟動帳戶從預設的 Local System 帳戶改成 Administrator。

如此調整之後,程式竟然不出錯了!

那又是為什麼呢?

真相大白

我想起來了,當初安裝作業系統時,選擇的語系是繁體中文(於是日期時間格式也就是中文的格式)。等到裝好作業系統之後,才又安裝了英文的語言包,並且將控制台中的語系、格式都設定成英文。

問題在於,安裝作業系統時,Local System 這個 Windows 內建的帳戶就已經在 registry 裡面記住自己的語系和格式設定了。事後到控制台調整語系和格式,對它絲毫沒有影響。

找到問題的真正原因之後,解決方法就簡單了。打開 Register Editor,找到 HKEY_USERS \ .DEFAULT \ Control Panel \ Interntional,再修改相關的格式字串就行了。參考下圖:


2012-09-26 更新

從 Windows 7 和 Windows 2008 開始,作業系統已經有提供使用者介面來複製控制台中的區域設定。只要開啟控制台的 Region and Language,然後點選 Administrative 頁籤就能找到此複製功能。參考下圖:


將圖中紅色框內的核取方塊打勾就行了。(感謝 Michael Cheng 提供)

結論

陰錯陽差安裝了非慣用的語系,然後去調整控制台區域設定,再加上以 Local System 帳戶來啟動的 Windows Service,結果就是鬼打牆好幾個小時 Orz

延伸閱讀:How to: Round-trip Date and Time Values

Post Comments

技術提供:Blogger.