為了讓大家本文介紹的主題有一個比較直覺的認識,我們給出一個具體的應用場景。一個跨國公司開發一套統一的辦公系統,供遍布全球的所有分公司使用。用戶端的UI采用Smart Client (Windows Forms應用),而主要的業務邏輯均通過WCF服務的形式提供。我們将承載業務服務的伺服器成為應用伺服器,如右圖(點選看大圖)所示,應用伺服器部屬于中國境内(東8區)。主要的用戶端(分公司)分布于三個主要的國家和地區:北美、歐州和澳洲。
不論用戶端和伺服器之間,還是不同的用戶端之間所處的時區均不相同,在進行時間處理的時候就會遇到一些麻煩:某個用戶端通過服務調用擷取的時間值應該基于哪個時區?對于這個問題,不同的場景可能有不同的要求。在大部分情況下,我們希望擷取的時間值就是基于用戶端的本地時區。不過也有些場景我們希望擷取的時間值對應的時區是描述對象基于的那個時區。比如說,美國分公司于當地時間9月1号早8點舉行開業典禮,歐洲分公司員工讀取這條資訊就沒有必要将時間轉換成基于本地時區的時間。
不過,本文不考慮這種情況,我們的最終要求是:用戶端應用根本不用考慮時區問題,就像是一個單純的本地應用一樣。用戶端調用服務傳入的時間是DateTimeKind.Local時間或者DateTimeKind.Unspecified時間,同理通過服務調用傳回的時間也應該是基于用戶端所在時區的時間。
現在我們就來談談如何解決上面提出的問題。既然時區的處理不能在用戶端做,換言之就必須在服務端實作。我們的一個前提是:在資料庫中不存儲時區的任何資訊。在這樣一個前提下實作上述的目标,需要解決兩個問題:時間的儲存和時間擷取。

在時間的儲存方面,既然資料庫中能儲存任何時區偏移之類的資訊。在這種情況下,我們必須讓所有儲存在資料庫中的時間都是基于同一個時區。我們可以選擇應用伺服器所在的時區,也可以直接采用UTC時間。我們的方案采用後者,即資料庫所有時間儲存為UTC時間 。
時間在資料庫中的存儲形式确定了,現在又出現一個問題:用戶端傳來的時間為用戶端所在時區的當地時間,服務端接收到用戶端發送的時間後,需要基于用戶端相應時區轉換成UTC時間才能儲存到資料庫。那麼,服務端如何擷取用戶端所在的時區資訊呢?将其作為服務操作的參數肯定是不可取的。
下面的代碼示範了通過上述的這兩個方法對TimeZoneInfo的序列化和反序列化的實作:
下面是輸出結果,從中我們看出最終被序列化後的文本的内容。此外,輸出結果也反映兩個另一個資訊:兩個包含時區資訊的TimeZoneInfo對象,調用Equals方法和使用==操作符得到不一樣的結果。個人覺得這是微軟作得不太到位的地方。
關于這個分布式系統中跨時區問題的讨論暫時就到這裡,在下篇中我将給出一個完整的例子,相信會使你對本文給出的解決方案有一個深刻的認識。
[相關閱讀]