前段時間因為公司的一個 WebFrom 項目設計到跨時區的問題,處理了一段時間,終於解決了,寫個博客記錄一下,方便以後回顧以及給他人提供一個參考的方法。 本次的項目因為跨越了多個時區,在一些時間上會受到時區的影響,比如在美國分部使用系統插入了一條數據,在美國分部顯示的時間是“2019-09-25 0 ...
前段時間因為公司的一個 WebFrom 項目設計到跨時區的問題,處理了一段時間,終於解決了,寫個博客記錄一下,方便以後回顧以及給他人提供一個參考的方法。
本次的項目因為跨越了多個時區,在一些時間上會受到時區的影響,比如在美國分部使用系統插入了一條數據,在美國分部顯示的時間是“2019-09-25 00:00:00“,那麼在北京分部看到的時間就應該自動轉換成”2019-09-25 12:00:00“(美國比北京少十二個小時),因此系統要能實現自動獲取客戶端的時區並轉換成 UTC 時間,保存到資料庫,在取出來的時候再根據客戶端時區轉換為當地時間。
一開始是受部門運維的引導,知道了 oracle 自帶一種數據類型叫做 timestamp with local time zone ,即按照資料庫緩存的時區(Database Session Timezone),當用戶存取時間時按照用戶 Session 的時區對時間欄位轉換為當地時間。這個在我 CSDN 的博客中有提到這樣的東西,鏈接為:https://blog.csdn.net/Wujiakai123/article/details/95391436 。然而當我開始嘗試在項目中改進的時候才發現,發佈到 IIS 的項目,不管有多少個客戶連接進來,始終是以我的伺服器為客戶端去連接 oracle 的,這就導致了 oracle 中 Session 的時區一直是我的伺服器的時區。因此推翻了使用 oracle 自帶數據類型對時間欄位進行自動轉換的方法。
此路不通,所以就只能嘗試在項目裡面做改進了。經過與部門其他同事討論之後,決定在登錄時通過 JS 方法獲取到客戶端電腦的時區,保存到用戶的登錄信息中,這樣,在用戶進行操作時,涉及到時間的都按照該時區進行轉換。因為本人剛畢業不久,代碼技術水平不高,因此只想出了這樣的笨方法,下麵將具體實現方法列出來,如果有大佬有更好的解決辦法歡迎評論,我可以多學習學習。
Login.aspx:
<div class="newBox"> <asp:HiddenField ID="hidTimezone" runat="server" ClientIDMode="Static" /> // <asp:Button ID="btnLogin" CssClass="LoginBtn" runat="server" OnClientClick="Login()" Text="Sign In" OnClick="btnLogin_Click" /> </div>
hidTimezone 控制項用於保存當前用戶瀏覽器時區的值,btnLogin 按鈕點擊調用 Login() 方法
Login()方法:
<script type="text/javascript"> function Login() { //得到本地時間 var d = new Date(); //得到1970年一月一日到現在的秒數 var local = d.getTime(); //本地時間與GMT時間的時間偏移差 var offset = d.getTimezoneOffset() * 60000; //獲取本地時區,判斷如果是負的則相加得到格林尼治時間,正的則相減 var timezone = new Date().getTimezoneOffset() / 60 * (-1); document.getElementById("hidTimezone").value = timezone.toString(); return true; } </script>
通過 JS 方法將獲取到的時區的值賦給 hidTimezone 中,登錄的時候將該控制項的值跟著用戶的登錄信息一起放在 Session 中,大概就是這樣:
UserModel model = new UserModel(); model.Timezone = Convert.ToDouble(hidTimezone.value);
Session["UserModelInfo"] = model;
這樣子就能將用戶的瀏覽器時區寫入到 Session 中,方便後續做時間轉換的時候可使用。
因為系統中所有頁面都是繼承了模板頁 BasePage ,因此只要在模板頁獲取時區信息就可以了。
private UserLoginInfo _UserLoginInfo; protected override void OnLoad(EventArgs e) { _UserLoginInfo = (UserLoginInfo)Session["UserLoginInfo"]; base.OnLoad(e); } public class UserLoginInfo { /// <summary> /// 用戶所在時區 /// </summary> public double Timezone { get; set; } }
這樣一來,只要頁面繼承了 BasePage,就可以用 base.UserLoginInfo.Timezone 獲取到時區信息。
在Helper 類中新增時區轉換方法:
/// <summary> /// 根據時區獲取本地時間 /// </summary> /// <param name="timezone">時區</param> /// <returns></returns> public static DateTime GetLocalTime(double timezone) { try { double min = timezone * 60; var localtime = DateTime.UtcNow.AddMinutes(min); return localtime; } catch (Exception ex) { LogHelper.Write(ex.Message); throw new Exception(ex.Message); } } /// <summary> /// 根據時區將UTC時間轉換成本地時間 /// </summary> /// <param name="UTCTime">資料庫中保存的UTC時間</param> /// <param name="timezone">用戶本地電腦時區</param> /// <returns></returns> public static DateTime UTCToLocal(DateTime UTCTime, double timezone) { try { double min = timezone * 60; var localtime = UTCTime.AddMinutes(min); return localtime; } catch (Exception ex) { LogHelper.Write(ex.Message); throw new Exception(ex.Message); } } /// <summary> /// 根據時區將本地時間轉換成UTC時間 /// </summary> /// <param name="UTCTime">資料庫中保存的UTC時間</param> /// <param name="timezone">用戶本地電腦時區</param> /// <returns></returns> public static DateTime LocalToUTC(DateTime localtime, double timezone) { try { double min = timezone * 60 * (-1); var UTCTime = localtime.AddMinutes(min); return UTCTime; } catch (Exception ex) { LogHelper.Write(ex.Message); throw new Exception(ex.Message); } } /// <summary> /// 將時區信息轉換為分鐘偏移量 /// 例:GMT+09:00 轉換結果為:540 /// </summary> /// <param name="timezone">時區信息</param> /// <returns></returns> public static double GetTimezoneMinOffset(string timezone) { try { string strSub = timezone.Substring(timezone.Length - 6, 6); string[] timestr = strSub.Split(':'); double min; if (Convert.ToDouble(timestr[0]) < 0) { min = Convert.ToDouble(timestr[0]) * 60 - Convert.ToDouble(timestr[1]); } else { min = Convert.ToDouble(timestr[0]) * 60 + Convert.ToDouble(timestr[1]); } return min; } catch (Exception ex) { LogHelper.Write(ex.Message); throw new Exception(ex.Message); } }
這樣子就成功了一半了,接下來要做的,就是將原本系統中,執行 Insert 語句插入資料庫之前獲取數據中時間的 DateTime.Now 統一改成 DateTime.UtcNow,就能保證數據保存到資料庫中是以當前 UTC 時間進行保存的,若涉及到時間,可用以上方法進行轉換之後再保存到資料庫中。同理,當用戶查看數據時,涉及到時間的也根據以上方法,將資料庫中的 UTC 時間轉換成當地時間顯示。這樣一來就可以實現不同時區的人無論存數據或者查看數據都按照當地的時區。
那以上就是我對於 .NET WebFrom 項目跨時區的一個比較愚鈍的解決辦法,我相信肯定會有更方便更容易的方法,只是在當時項目趕著交付的情況下,我直接選取了這種比較笨的方法,優點就是用戶是無感知的,在登錄的時候就已經自動獲取到了用戶的時區信息,獲取數據時也自動轉換出來,對於用戶來說不用特地去維護自己的時區信息。當然缺點就是這樣一來,這套項目往後所有有關時間的信息都要按照這樣的轉換,若是有遺漏了,就會出現時間上不統一的問題,對應項目的再次開發以及以後交付給他人都是一個負擔。
如果各位大佬有其他更好的方法歡迎評論區留言,讓我這個菜鳥能多學習學習。這次也是第一次在博客園寫博客,怎麼說呢,自己本身基礎並不扎實,還在學習中,若有不對之處請盡請指出!多謝各位!