2018年3月28日 星期三

關於Server時區的問題

工作上經常會接觸很多部Server,而每個Server雖然時間都是接近或者一樣的,但時區設定可能會各有不同,尤其是面對國際的Server更是。因此如果在開發Server程式而需要用電腦時間來執行某些工作的時候,就要小心時區兼容的問題。即使你的程式是以香港時間為準,千萬不要假設了你的程式一定會在香港時區的電腦運行

當你在程式內想取得電腦現在的時間時,需要小心你取得的是本地時區的時間 (Localtime),還是世界協調時間 (UTC/GMT,雖然在科學的定義上UTC和GMT有少許不一樣,但一般在寫程式上會視兩者為同一物)。寫程式很容易犯的錯誤就是沒有考慮到底取得了哪個時區的時間。如果運行程式的電腦(尤其是伺服器)有機會是處於不同時區,而你想取得的是準確的時間(尤其是要計算或顯示時、分、秒),那就千萬別拿本地時區時間。

假設我們身處香港,時區是UTC+8,有一個伺服器程式會在每天香港時間晚上六點執行一次任務。如果這個程式在一部UTC(+0)的電腦上執行,就會變成香港時間凌晨兩點才執行。也許你會覺得只要能夠自行設定這個時間而不是寫死(hardcode)在程式內,再看看目標電腦是哪個時區而更改設定,就能解決這個問題。但一來我們有時候並不知道目標電腦的時區是甚麼,二來有一些程式邏輯可能假定了執行任務時的日期,當出現跨日期的情況就更難處理。

其實最簡單的方法就是永遠只取得世界協調時間,再將其調整成你自己想要的時區。例如在香港的話,取得世界協調時間後直接加上八小時。這樣無論電腦是設置成哪個時區,我們都能取得正確的香港時間。當然視乎情況也可以一律只使用UTC時間作準

C++程式碼例子:
  1. //Get UTC+8 Time (Windows MFC library)
  2. CTime ctNow = CTime::GetCurrentTime();
  3. tm tmHongKongTime;
  4. (ctNow + CTimeSpan(0, 8, 0, 0)).GetGmtTm(tmHongKongTime);
  5. return tmHKT;
  6.  
  7. // Get UTC+8 Time (Standard library)
  8. time_t t = time(NULL);
  9. struct tm *tmHKT = gmtime(&t); //still GMT, not yet HKT
  10. tmHKT->tm_hour += 8; //add 8 to GMT
  11. mktime(tmHKT); //recompute the values to make it valid
  12. return tmHKT;
  13.  
  14. // Print time
  15. printf("Now is %d/%d/%d %02d:%02d:%02d HKT",
  16. tmHKT->tm_year + 1900, tmHKT->tm_mon + 1, tmHKT->tm_mday,
  17. tmHKT->tm_hour, tmHKT->tm_min, tmHKT->tm_sec);
c#程式碼例子:
C#的DateTime可以直接在上面設定時區,然後無論是Property還是Print出來的時間都會自動幫你調成該時區的時間:
  1. // Assume now is 2018:08:23 16:27:04 Hong Kong Time
  2.  
  3. // UTC Time: 2018-08-23 08:27:04 Utc 8 636706096247972300
  4. DateTime dtUTC = DateTime.UtcNow;
  5. Console.WriteLine("{0} {1} {2} {3}", dtUTC, dtUTC.Kind, dtUTC.Hour, dtUTC.Ticks);
  6.  
  7. // Local Time (now Hong Kong Time): 2018-08-23 16:27:04 Local 16 636706384247972300
  8. DateTime dtLocal = dtUTC.ToLocalTime();
  9. Console.WriteLine("{0} {1} {2} {3}", dtLocal, dtLocal.Kind, dtLocal.Hour, dtLocal.Ticks);
  10.  
  11. // Absolute Hong Kong Time (ignore machine timezone): 2018-08-23 16:27:04 Unspecified 16 636706384247972300
  12. DateTime dtHKT = TimeZoneInfo.ConvertTimeFromUtc(dtUTC, TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
  13. Console.WriteLine("{0} {1} {2} {3}", dtHKT, dtHKT.Kind, dtHKT.Hour, dtHKT.Ticks);
JavaScript程式碼例子:
  1. //JavaScript
  2. function GetAbsoluteHKTime()
  3. {
  4. var now = new Date();
  5. now.setUTCHours(now.getUTCHours() + 8);
  6. return {
  7. "year" : now.getUTCFullYear(),
  8. "month" : now.getUTCMonth(),
  9. "date" : now.getUTCDate(),
  10. "hour" : now.getUTCHours(),
  11. "minute" : now.getUTCMinutes(),
  12. "second" : now.getUTCSeconds()
  13. };
  14. }
  15.  
  16. var j = GetAbsoluteHKTime();
  17. console.log("Now is " + j.year + "/" + j.month + "/" + j.date + " "
  18. + ('0' + j.hour).slice(-2) + ":"
  19. + ('0' + j.minute).slice(-2) + ":"
  20. + ('0' + j.second).slice(-2) + " HKT");
  21.  


C++程式碼例子:(以下代碼嚴格來說並不正確,因為沒有跟標準,CTime的GetHour()之類的函數會回傳本地時區的時間,如果想要拿UTC時間就要用GetGmtTm)

  1. // Get UTC+8 Time (Windows MFC library)
  2. SYSTEMTIME st;
  3. GetSystemTime(&st);
  4. CTime ctHongKongTime = CTime(st) + CTimeSpan(0, 8, 0, 0)