游戏时区问题小解

因为时区、夏令时的存在,游戏内的时间显示/计算都要考虑时区问题并进行相应处理。时间计算不用说,要排除玩家本地时区影响,只以服务器时区为准进行计算。时间显示有两种方案:api

  1. 根据服务器下发的utc时间戳,按玩家手机本地设置的时区进行适配显示,这样对于常常往返于不一样时区的玩家很友好(虽然这类玩家不多),玩家只要修改手机时区,游戏内的时间显示就以该时区为准了。然而这种方案一般会碰到问题,好比游戏内活动图片里写死了日期,时间,显然就没法根据玩家手机时区适配显示。
  2. 根据服务器时区进行统一显示是更好的方案,若是是国内上线游戏,能够统一显示东八区时间,这样就能够保证图片里的时间信息是正确的。这种方案也有个附带好处,当玩家不自知地将时区设为其余时区,时间却设成东八区时间时(咱们项目内有个策划的手机就是这样设置的-_-||),游戏内的时间显示"看起来"仍是正确的。
    简单总结,游戏内的时间显示/计算最好都以服务器时区为准,而各类语言关于时间函数的api,都是以本地时区计算返回结果的,以Lua为例,Lua标准库中提供的时间函数 os.time()和os.date(),这两个函数传入和返回的时间table就是以本地时区为准的。

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

针对国内上线游戏作以上的时区处理,基本就没问题了。真正作不一样区服国际化时,服务器与本地时区的夏令时因素都要考虑进来作处理,等之后有机会踩坑了再记录吧。

最后一句题外话,感谢国家统一了时区,感受国家废除了夏令时。

相关文章
相关标签/搜索