时间戳和日期转换的相关问题(牵扯时区)

每次时间计算一扯上“时区”,都会让我懵一下。
这个换算过程一定程度上依赖使用的库,lua里和C#里有所差别,做一下记录。

一、基础

1、“时间戳” (Unix时间戳)国际统一。就是指 格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。

2、“日期” 受时区影响。同一时刻下,“不同时区的日期” 要比 “0时区的日期” 快 “时区数 * 3600 秒”。

------------------------------------- NRatel割 -------------------------------------

***本文出现的变量表示的含义: 

localTimeZone本地(本系统)所在时区serverTimeZone服务器所在时区
localTimestamp从本地(本系统)获得的时间戳serverTimestamp从服务器获得的时间戳
localTimeDate本地(本系统)所在时区的日期serverTimeDate服务器所在时区的日期

二、客户端在运算和判断时遵守的原则

1、客户端总是基于 serverTimestamp 和 serverTimeDate 做运算 和 判断。

2、客户端总是信任 serverTimestamp,即使它与 “国际标准时间戳” 相去甚远。

3、客户端不信任 localTimestamp,因为它通常与 serverTimestamp 有差异,甚至能被人为地修改。

三、要解决的问题:

1、如何随时都能获得 serverTimestamp 

    长连游戏,每次tick同步一次即可。

    短连游戏,应该在每次交互时同步一次,其他时候自行推算。推算出的结果可以认为就是 serverTimestamp,推算中产生的微小误差可以忽略。推算办法如下,其实就是一个 “已知 A、B、C, 求D” 的问题。

某一次时间同步时某一次获取“从服务器获得的时间戳”(推算出的)时

A 从本地(本系统)获得的时间戳

例:1578060155

C 从本地(本系统)获得的时间戳

例:1578061155

C-A “获取时刻” 比 “同步时刻” 经过的时间 

1578061155-1578060155 = 1000

B 从服务器获得的时间戳(同步时刻)

例:1578060200

D 从服务器获得的时间戳(获取时刻)

例:???

D = B + (C - A)

1578060200 + 1000 = 1578061200

B-A “服务器时间戳” 与 “本地时间戳” 相差的时间

1578060200-1578060155=45

D = C + (B - A)

1578061155 + 45 = 1578061200

 两种算法结果完全一致

实际应用中,也常将 A 和 C 改为 使用 Time.realtimeSinceStartup,可以避免玩家运行时主动修改系统时间产生的错误。

2、“时间戳” 转 “服务器所在时区的日期”

   见下方代码及注释(lua)

3、“服务器所在时区的日期” => “时间戳”

   见下方代码及注释(lua)

4、客户端应该怎么计算和展示时间相关内容?

 1)、“时间差”

    不用考虑时区问题,但注意,应该直接使用服务器时间戳、服务器时区进行计算。

     ①、倒计时(天/时/分/秒)(未来某时间点-当前时间点)(如活动结束倒计时、种菜成熟倒计时、商店刷新倒计时...)

     ②、是否跨天(天)(每日X点到次日X点为1天,两个时间点A和B中间是否度过了至少1个X点)(如,每日5点到次日5点为1天,需要计算“当前”对于“上次某个触发点”是否跨天)(在服务器时区的基础上

     ③、累计时间(天/时/分/秒)(当前时间点-过去某时间点)(如挂机累计时间)

2)、“具体时间点” (日期相关) 

    在展示时要考虑时区问题。 (如:活动时间 2021/5/30 05:00~2021/6/30 05:00、竞技场赛季时间 05.27~06.27)。

    情况1、游戏在发行时 “一个服务器只服务一个地区”。 直接将服务器时区设为当地时区,很完美,展示时使用服务器时区即可,不考虑用户自行修改本地时间,不需要任何转换。

    情况2、游戏在发行时 “一个服务器服务多个地区”(全球同服)。可以采用两种方案。

        方案①、直接使用服务器所在时区显示。 优缺点:不用转换,程序复杂度很低,但要告知用户服务器采用的时区,且要让用户自行理解换算成客户端本地所在时区的日期,不太友好。

        方案②、使用客户端本地所在时区显示。优缺点:对用户很友好,游戏里看到的时间点与手机系统上的时间点一致。但要经过转换,程序复杂度高,且日期相关必须精确到小时,无法处理 “泛泛” 的日期(如5月27日、周X等)。因为时区相差一小时,就可能在天数上差一天。还要考虑用户在登录后修改时区、某些国家/地区采用夏令时等问题。

        建议①、可以尽量避免具体日期的出现,可以解决一些烦恼,但实际很难避免。

        建议②、可以让服务器采用0时区,客户端无论何种方式展示,计算和理解复杂度都可下降。

local TimeHelper = {}

TimeHelper.serverTimezone = 0   --服务器所在时区
TimeHelper.localTimezone = 0    --本地(本系统)所在时区
TimeHelper.deltaSeconds = 0     --“服务器时间戳” 与 “本地时间戳” 相差的时间

function TimeHelper:Sync(serverTimestamp, serverTimezone)
    self.serverTimezone = serverTimezone                                                        
    self.localTimezone = os.difftime(os.time(), os.time(os.date("!*t", os.time()))) / 3600      

    self.deltaSeconds = serverTimestamp - os.time()
end

--从服务器获得的时间戳(实际经过了推算模拟)
function TimeHelper:ServerTimestamp()
    return os.time() + self.deltaSeconds    --D=C+(B-A)的方式
end

--“此时此刻的时间戳” 对应的 “服务器所在时区的日期”
function TimeHelper:ServerDate()
    local serverTimestamp = self:ServerTimestamp()
    return self:Timestamp2ServerDate(serverTimestamp)
end

--时间戳转 “服务器所在时区的日期”
function TimeHelper:Timestamp2ServerDate(timestamp)
    --os.date([format [, time]])  如果format以"!"开头,则以0时区(格林尼治时间)进行格式化,否则以本地所在时区格式化。

    --方式一: “时间戳” 加上 “服务器所在时区与本地所在时区的秒数差”,并按本地所在时区格式化。
    -- return os.date("*t", timestamp + (self.serverTimezone - self.localTimezone) * 3600)

    --方式二: “时间戳” 加上 “服务器所在时区与0时区的秒数差”,并按0时区格式化。
    return os.date("!*t", timestamp + self.serverTimezone * 3600)
end

function TimeHelper:ServerDate2Timestamp(serverDate)
    --os.time([table]) 传入 “nil” 或 “此时此刻本地所在时区的日期”,都会返回一个 “标准时间戳”。
    --注意!日期是带时区信息的!但它却得到了一个 “标准时间戳”,而不是 “标准时间戳” 与 “本地所在时区相对0时区的秒数偏差” 之和 。  
    --这意味着它在内部自动减去了这份 “本地所在时区相对0时区的秒数偏差”。

    --思路一:传入一个 “本地所在时区的日期”,就能得到想要时间戳。但,将 serverDate 转为 localDate 难以处理。
    --这个思路放弃了。

    --思路二:先直接用 serverDate 计算,再把os.time()自动减去的 “本地所在时区相对0时区的秒数偏差”加回去,再减去实际应减去的 “服务器所在时区相对0时区的秒数偏差”。
    --return os.time(serverDate) + self.localTimezone * 3600 - self.serverTimezone * 3600;
    --即:先直接用 serverDate 计算,再减去 “服务器所在时区与本地所在时区的秒数差”。
    return os.time(serverDate) - (self.serverTimezone - self.localTimezone) * 3600
end

return TimeHelper;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NRatel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值