因为时区、夏令时的存在,游戏内的时间显示/计算都要考虑时区问题并进行相应处理。时间计算不用说,要排除玩家本地时区影响,只以服务器时区为准进行计算。时间显示有两种方案: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
针对国内上线游戏作以上的时区处理,基本就没问题了。真正作不一样区服国际化时,服务器与本地时区的夏令时因素都要考虑进来作处理,等之后有机会踩坑了再记录吧。
最后一句题外话,感谢国家统一了时区,感受国家废除了夏令时。