因爲時區、夏令時的存在,遊戲內的時間顯示/計算都要考慮時區問題並進行相應處理。時間計算不用說,要排除玩家本地時區影響,只以服務器時區爲準進行計算。時間顯示有兩種方案:api
os.time()
- 原型:os.time ([table])
- 解釋:按table的內容返回一個時間值(數字),若不帶參數則麼使用當前時間做爲table內容,其中table中能夠包含的字段有:year, month, day, hour, min, sec, isdst,其餘字段將會被忽略。
os.date()
原型:os.date ([format [, time]])
解釋:返回一個按format格式化日期、時間的字串或表。
參數格式:服務器
- 由原型能夠看出能夠省略第二個參數也能夠省略兩個參數,只省略第二個參數函數會使用當前時間做爲第二個參數,若是兩個參數都省略則按當前系統的設置返回格式化的字符串,作如下等價替換 os.date() <=> os.date("%c")。
- 若是format以「!」開頭,則按格林尼治時間進行格式化。
- 若是format是一個「*t」,將返一個帶year(4位),month(1-12), day (1--31), hour (0-23), min (0-59),sec (0-61),wday (星期幾, 星期天爲1), yday (年內天數)和isdst (是否爲日光節約時間true/false)的帶鍵名的表;
- 若是format不是「*t」,os.date會將日期格式化爲一個字符串
要以服務器時區進行時間計算,編碼思路就是要計算出本地與服務器的時區差,調用os.time()、os.date()時進行補償。函數
-- 服務器時區爲東八區 local ServerTimeZone = 3600 * 8 -- 獲取客戶端本地時區 function TimeUtils.GetLocalTimeZone() local now = os.time() local localTimeZone = os.difftime(now, os.time(os.date("!*t", now))) return localTimeZone end
服務器時區:對於國內服務器,服務器時區能夠直接硬編碼成東八區,若是考慮作國際化,能夠由服務器進行下發該值,根據地區設置不一樣服務器時區值。
本地時區:在lua裏沒有直接獲取本地時區的api,但經過os.date("!*t", os.time()),能夠獲取格林尼治的時間table,再以本地時區解析table獲取時間戳,該時間戳與os.time()時間戳相減即爲時區秒數差值。優化
假設如今遊戲內有個功能入口要在遊戲開服次日0點開啓,若是不考慮時區問題,編碼以下,當玩家修改本地時區時,計算得出的時間戳是不一樣的。這樣玩家就能夠經過修改本地時區,讓功能提早開啓。編碼
-- 獲取開服次日0點時間戳 local nextDayTable = os.date("*t", openServerTime + 86400) local nextDayZeroHourTime = os.time({year=nextDayTable.year, month=nextDayTable.month, day=nextDayTable.day, hour=0,min=0,sec=0})
所以能夠對os.date()、os.time()作一層封裝,傳入/返回的時間table都以服務器時區爲標準。本地時區就徹底不會影響時間計算邏輯了。lua
-- 替代os.date函數,忽略本地時區設置,按服務器時區格式化時間 -- @param format: 同os.date第一個參數 -- @param timestamp:服務器時間戳 function TimeUtils.Date(format, timestamp) local timeZoneDiff = ServerTimeZone - TimeUtils.GetLocalTimeZone() return os.date(format, timestamp + timeZoneDiff) end -- 替代os.time函數,忽略本地時區設置,返回服務器時區時間戳 -- @param timedata: 服務器時區timedate function TimeUtils.Time( timedate ) local timeZoneDiff = ServerTimeZone - TimeUtils.GetLocalTimeZone() return os.time(timedate) - timeZoneDiff end -- 獲取開服次日0點時間戳 local nextDayTable = TimeUtils.Date("*t", openServerTime + 86400) local nextDayZeroHourTime = TimeUtils.Time({year=nextDayTable.year, month=nextDayTable.month, day=nextDayTable.day, hour=0,min=0,sec=0})
經過TimeUtils.Date()、TimeUtils.Time()替代os.date()、os.time(),業務邏輯處理時間計算時,只需考慮服務器時區便可,即便往後遊戲進行國際化,只需根據地區修改ServerTimeZone便可,對業務層沒有影響。code
若是咱們生活在一個簡單美好的世界,時區問題就此解決了,而後勤勞智慧的人民們,爲了節能(sheng)減排(qian),又發明了夏令時,以上代碼在實行夏令時的國家地區裏,計算結果可能不對。orm
夏令時,又稱「日光節約時制」,英文全稱Daylight Saving Time,簡稱DST。大白話來講就是從前有人以爲你們夥晚睡晚起,致使晚上照明用電過久浪費錢,夏每天亮得早,就提倡你們夥夏天時一塊兒把時鐘調快1個小時,你不是習慣晚上12點才睡覺嗎?那都把表調快1小時,變相地讓你提早1小時睡覺,從而實現節省減排。夏令時制度是以國家爲單位來執行的,每一個國家一年裏夏令時生效的時段還不同,目前全世界有近110個國家每一年要實行夏令時。以英國倫敦爲例,英國倫敦位於零時區,與中國東八區相差8個時區:在不實行夏令時的日子裏,與中國確實是相差8小時;實行夏令時後,與中國只相差7小時了。遊戲
扯了不少夏令時的概念,回到時區處理問題,在計算時區差時,就須要判斷玩家本地設置時區是否正在實行夏令時,若是是則在原計算結果上再加3600秒。os.date()返回的時間table裏帶有isdst字段,isdst=true表示正在使用夏令時。所以前面代碼優化以下:圖片
-- 獲取客戶端本地時區 function TimeUtils.GetLocalTimeZone() local now = os.time() local localTimeZone = os.difftime(now, os.time(os.date("!*t", now))) local isdst = os.date("*t", now).isdst if isdst then localTimeZone = localTimeZone + 3600 end return localTimeZone end
針對國內上線遊戲作以上的時區處理,基本就沒問題了。真正作不一樣區服國際化時,服務器與本地時區的夏令時因素都要考慮進來作處理,等之後有機會踩坑了再記錄吧。
最後一句題外話,感謝國家統一了時區,感受國家廢除了夏令時。