Lua中的时区问题

Lua 中的 os.time() 获取的时间是当前设备的时间,但是当前设备是可以修改时间的,所以这个接口并不可靠。另外如果设备修改了时区,这个接口也没有提供获取指定时区当前时间的功能。

这样在各种需要显示时间以及根据时间判断是否开放系统的地方很可能因此出现问题。所以需要自己封装一些接口来获取服务器时区对应的时间。因为游戏时间肯定是以服务器所在时区显示为准。

获取客户端时区

1
2
3
4
5
6
-- 获取客户端时区
function CommonTools.GetClientTimeZone()
local now = os.time()
local difftime = os.difftime(now, os.time(os.date("!*t", now)))
return difftime / 3600
end

即使客户端的时间是不准确的,我们也可以算出客户端当前设置的时区是什么,也就是 UTC +几。

计算方式很简单。是用 从设备获取的UTC+0的时间 和 从设备获取的当前的时间 做差。

而服务器时区可以写个固定的,比如 8 就代表北京时间。

时间戳与日期转换

服务器传来一个时间戳之后,客户端如果要显示,Lua中需要用 os.date 进行转换,然而 os.date 转换出来的是按照客户端时区转的,并且没有提供参数指定时区,这就难受了。那就自己实现一个 os.date 吧。

1
2
3
4
5
6
7
8
9
10
11
12
-- 服务器或者配置表的时间戳转换为服务器对应时区的时间
function CommonTools.OSDate(format, serverTime)
local clientTimeZone = CommonTools.GetClientTimeZone()
local serverTimeZone = CommonTools.GetServerTimeZone()
local clientTime = os.date("!*t", serverTime)
local offset = (serverTimeZone - clientTimeZone) * 3600 + (clientTime.isdst and 1 or 0) * 3600 -- 时差包括时区差距以及夏令时
local tempTime = serverTime + offset
if string.IsNullOrEmpty(format) then
format = "*t"
end
return os.date(format, tempTime)
end

既然 os.date 是按照客户端时区转换,那么比如服务器是UTC+8,客户端是UTC+9,那么服务器的时间戳在客户端转换之后产生的日期就会多一个小时——比如实际是UTC+8 2021年1月1日5点的时间戳,放到客户端转换就会把它当作UTC+9的时间戳去做转换,转换出来就变成了UTC+9 2021年1月1日2点,快了一个小时。

所以,要让最终算出正确的结果,需要先用服务器时区减去客户端时区计算出偏移,如果客户端时区更大,偏移就是负的,反之就是正的。这样算出来的日期才是正确的。

同理,os.time 也需要做类似的转换,不过偏移要反过来:

1
2
3
4
5
6
7
8
-- 客户端的时间table转换为服务器的时间戳 仅限转换客户端本地时间
function CommonTools.OSTime(table)
local clientTimeZone = CommonTools.GetClientTimeZone()
local serverTimeZone = CommonTools.GetServerTimeZone()
local offset = clientTimeZone - serverTimeZone
table.hour = table.hour + offset
return os.time(table)
end