Lua遊戲開發之時區問題

目前大部分遊戲都採用了Lua語言進行功能開發,在進行多語種發行的時候就會遇到時區顯示的問題。以韓國版本爲例,場景以下:安全

一、服務器處於固定的位置,好比放在首爾機房;服務器

二、玩家所處的位置不肯定,可能在韓國,或者是出差在其它國家或地區;函數

 

需求:測試

不管在哪一個國家或地區,統一顯示服務器的當前時間。在PC上查看,即使在國內測試的時候也顯示韓國首爾的時間(比北京時間快1個小時)。this

 

實現:lua

-- 北京時間
local serverTime = 1536722753 -- 2018/09/12 11:25

function getTimeZone()
    local now = os.time()
    return os.difftime(now, os.time(os.date("!*t", now)))
end

-- 8 hour * 3600 seconds = 28800 seconds
local timeZone = getTimeZone()/ 3600

print("timeZone : " .. timeZone)



local timeInterval = os.time(os.date("!*t", serverTime)) + timeZone * 3600 + (os.date("*t", time).isdst and -1 or 0) * 3600

local timeTable = os.date("*t", timeInterval)

--[[
for k, v in pairs(timeTable) do
    print(k .. ":" .. tostring(v))
end
]]

print(timeTable.year .. "/" .. timeTable.month .. "/" .. timeTable.day .. " " .. timeTable.hour .. ":" .. timeTable.min .. ":" .. timeTable.sec)

 

關注是這個方法: os.date("!*t", now),其中以!爲關鍵。spa

lua 源碼, loslib.c Line 283 行線程

static int os_date (lua_State *L) {
  size_t slen;
  const char *s = luaL_optlstring(L, 1, "%c", &slen);
  time_t t = luaL_opt(L, l_checktime, 2, time(NULL));
  const char *se = s + slen;  /* 's' end */
  struct tm tmr, *stm;
  if (*s == '!') {  /* UTC? */
    stm = l_gmtime(&t, &tmr);
    s++;  /* skip '!' */
  }
  else
    stm = l_localtime(&t, &tmr);
  if (stm == NULL)  /* invalid date? */
    luaL_error(L, "time result cannot be represented in this installation");
  if (strcmp(s, "*t") == 0) {
    lua_createtable(L, 0, 9);  /* 9 = number of fields */
    setallfields(L, stm);
  }
  else {
    char cc[4];  /* buffer for individual conversion specifiers */
    luaL_Buffer b;
    cc[0] = '%';
    luaL_buffinit(L, &b);
    while (s < se) {
      if (*s != '%')  /* not a conversion specifier? */
        luaL_addchar(&b, *s++);
      else {
        size_t reslen;
        char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT);
        s++;  /* skip '%' */
        s = checkoption(L, s, se - s, cc + 1);  /* copy specifier to 'cc' */
        reslen = strftime(buff, SIZETIMEFMT, cc, stm);
        luaL_addsize(&b, reslen);
      }
    }
    luaL_pushresult(&b);
  }
  return 1;
}

 

從源碼能夠看到 ! 調用了3d

#define l_gmtime(t,r)        gmtime_r(t,r)code

gmtime_r 函數是標準的POSIX函數,它是線程安全的,將日曆時間轉換爲用UTC時間表示的時間。

注:UTC —— 協調世界時,又稱世界統一時間、世界標準時間

也就是說 「!*t」 獲得的是一個 UTC 時間,爲0度的經線(子午線),亦稱本初子午線,一般將它與GMT視做等同(可是UTC更爲科學和精確)。

 

 

首爾位於東9區,因此實際的時間應該是 UTC + 9,9就是時區差 —— 9個小時。北京位於東8區,即 UTC + 8。

 

如何保證遊戲內所有統一爲服務器的時間呢?

服務器須要返回給客戶端當前的時區的差值,好比韓國就返回 9,國內就返回 8,越南返回 7,北美返回 –16,記爲 serverTimeZone。

服務端返回當前服務器時間serverTime(即首爾當前時間),咱們只須要將服務器時間轉爲 UTC 的時間,而後再加上 serverTimeZone便可。

os.time(os.date("!*t", serverTime)) + serverTimeZone * 3600

這樣不管在哪一個地區或國家,都將顯示首爾的時候,與服務器顯示的時間就同步上了。

 

爲何要一直顯示服務器的時間呢?

遊戲中有不少功能是有時間限制的,好比運營活動,或者功能開啓。若是用本地時間就很差控制,統一用服務器時間避免了不少問題。

但是也容易遇到一個坑,運營配置的活動時間都是針對當前服務器的時間,例如某個活動的截止時間是:2018-10-08 00:00:00,遊戲須要顯示活動截止倒計時。

 

一般的作法: ployEndTime – serverTime,獲得一個秒數,而後將秒轉成:xx天xx小時xx分xx秒

serverTime 是固定的,但是ployEndTime就容易出錯,爲何?

serverTime 是在東9區 —— 首爾的時間,而 os.time({year=…}) 是根據本地時間來算時間的,這中間就存在問題。有一個時差的問題,以前計算一直用的是serverTimeZone —— 一個固定值,而我當前處於地區或國家,它相對於UTC的時區不肯定的,怎麼辦?

用 (currTimeZone – serverTimeZone) * 3600 / 秒,os.time()以後再加上這個時區差就是首爾當前的時間戳了。國內東8 - 東9  = -1,也就是要減去一個1時區,最終將獲得首爾地區的時間戳,再減去 serverTime 就是剩下的秒數了,而後將它轉爲 xx 天 xx 小時 xx 分 xx 秒。

 

 

最後小結一下:

1)os.time({year=xx}),這個時間算出來的是針對當前所處時區的那個時間戳。

2)os.date(「!*t」, 時間戳) 獲得的是UTC(時區爲0)的時間戳。

3)獲取當前時區的值,能夠經過文章開頭的 getTimeZone 方法

4)想顯示固定時區的時間(例如不管在哪都顯示服務器的時間),只須要將(服務器)時間戳(秒),經過第2步的方法,獲得 UTC 再加上固定的時區差

5)計算倒計時的時候,須要考慮到 os.time 是取當前時區,須要再將當前時區減去目標時區,再計劃時間戳

6)夏令時,自己已經撥快了一個小時,當須要顯示爲固定時區的時間,則須要減去一個小時

相關文章
相關標籤/搜索