2018年3月28日 星期三

關於Server時區的問題

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

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

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

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

C++程式碼例子:
//Get UTC+8 Time (Windows MFC library)
CTime ctNow = CTime::GetCurrentTime();
tm tmHongKongTime;
(ctNow  + CTimeSpan(0, 8, 0, 0)).GetGmtTm(tmHongKongTime);
return tmHKT;

// Get UTC+8 Time (Standard library)
time_t t = time(NULL);
struct tm *tmHKT = gmtime(&t); //still GMT, not yet HKT
tmHKT->tm_hour += 8; //add 8 to GMT
mktime(tmHKT); //recompute the values to make it valid
return tmHKT;

// Print time
printf("Now is %d/%d/%d %02d:%02d:%02d HKT",
 tmHKT->tm_year + 1900, tmHKT->tm_mon + 1, tmHKT->tm_mday,
 tmHKT->tm_hour, tmHKT->tm_min, tmHKT->tm_sec);
c#程式碼例子:
C#的DateTime可以直接在上面設定時區,然後無論是Property還是Print出來的時間都會自動幫你調成該時區的時間:
// Assume now is 2018:08:23 16:27:04 Hong Kong Time

// UTC Time: 2018-08-23 08:27:04 Utc 8 636706096247972300
DateTime dtUTC = DateTime.UtcNow;
Console.WriteLine("{0} {1} {2} {3}", dtUTC, dtUTC.Kind, dtUTC.Hour, dtUTC.Ticks);

// Local Time (now Hong Kong Time): 2018-08-23 16:27:04 Local 16 636706384247972300
DateTime dtLocal = dtUTC.ToLocalTime();
Console.WriteLine("{0} {1} {2} {3}", dtLocal, dtLocal.Kind, dtLocal.Hour, dtLocal.Ticks);

// Absolute Hong Kong Time (ignore machine timezone): 2018-08-23 16:27:04 Unspecified 16 636706384247972300
DateTime dtHKT = TimeZoneInfo.ConvertTimeFromUtc(dtUTC, TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
Console.WriteLine("{0} {1} {2} {3}", dtHKT, dtHKT.Kind, dtHKT.Hour, dtHKT.Ticks);
JavaScript程式碼例子:
//JavaScript
function GetAbsoluteHKTime()
{
    var now = new Date();
    now.setUTCHours(now.getUTCHours() + 8);
    return {
        "year" : now.getUTCFullYear(),
        "month" : now.getUTCMonth(),
        "date" : now.getUTCDate(),
        "hour" : now.getUTCHours(),
        "minute" : now.getUTCMinutes(),
        "second" : now.getUTCSeconds()
    };
}

var j = GetAbsoluteHKTime();
console.log("Now is " + j.year + "/" + j.month + "/" + j.date + " "
+ ('0' + j.hour).slice(-2) + ":" 
+ ('0' + j.minute).slice(-2) + ":" 
+ ('0' + j.second).slice(-2) + " HKT");



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

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

沒有留言:

張貼留言