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

2 則留言:

  1. 我之前在 Windows 7 上行 ASP.NET DateTimePicker Control 都遇過這個問題﹐明明轉咗 Administrator 和 Local User 的 Date Format 去 US 的 mm-dd-yyyy﹐ 但 Display 出來仍然係 UK 的 dd-mm-yyyy。

    最後發現 "Regional and Language" 裏面是有一個位置給用戶 Copy Local Account 的 Date Format 到行 Service 的 Account。

    http://windows.microsoft.com/en-MY/windows-vista/Apply-regional-and-language-settings-to-reserved-accounts

    我想效果和你改 registry 應該一樣吧!

    回覆刪除
  2. 對耶,我一直沒去查看 Region and Language 對話窗最右邊的 Administrative 頁籤,原來裡面已經有提供複製的功能啦!我把這點補充至內文。多謝! ^^

    回覆刪除

技術提供:Blogger.
回頂端⬆️