跨时区的时间设计

“苹果发布会将于北京时间凌晨1:00举办”,身在国外的我暗自高兴,几个小时时差让我不需要熬夜就可以一探究竟。转眼已经到北京时间凌晨1:00,兴冲冲打开某平台,网页上依然是倒计时:“苹果发布会将于x小时候开始”。没错,他计算的是我本地当前时间到我本地时间1:00之间的时差。那么,这里的时间应该如何计算?不同场景里应该使用哪些时间呢?

我们拥有哪些时间?

为了简单,这里首先假设服务器的时间是当前时区的标准时间(也就是说服务器的时间是“准的”,我们就不需要另外去查询标准时间)。

作为一款软件应用,我们能直接获取的时间无非三种:

  • 服务器当前时间
  • 数据时间(一个既定的时间,包括数据的生成、更新时间,某些约定的时间等)
  • 客户端当前时间

再加上服务器时间的时区与客户端时间的时区,我们还可以得到

  • 客户端所在时区的当前标准时间

有了这些时间,再加上对业务场景的理解,就组成了时间的设计。但在分析业务场景之前,首先感性的理解一下两个问题:

时间戳与时间字符串

时间,会分成时间戳(如 1602775913873)或者各式各样的时间字符串(描述时间的字符串,标准格式可以参见 w3c Date and Time Formats等),在实际的操作中,可以理解为时间戳是时区不相关的而时间字符串是时区相关的。而大部分由于时区产生的时间问题,都是错误的使用了时间字符串。

客户端时间与服务器时间

不要相信客户端时间,但可以使用客户端时间为用户提供便利。

时间业务场景

倒计时(x小时后)

倒计时实际分为两种,一种是大家约定好,在某个时间做某件事,如直播开始,抢购开始等,这是一种很简单的情形,因为倒计时是两个时间的差值,完全不涉及时区,这种倒计时计算的是一个“死”的时间,也不涉及用户“个人”的时间。本质上是“我”决定了一件事,一切安排都听“我”的。所以这里的倒计时说到底是服务器的倒计时:

倒计时结束的数据时间时间戳 - 服务器当前时间/标准时间时间戳

如果追求准确,可以每隔0.5s向服务器发送一个请求,让服务器告诉你究竟还剩下多少时间。当然,在实际操作中,一个此类倒计时是可以放在前端的,前端拿着服务器发来的一个差值(或者服务器当前时间时间戳和数据时间时间戳),不断在前端做减法。

还有一种是约定在一定时间做完某件事,如考试答题需要在1小时内完成等。这种场景看似也是一个差值,一样可以用服务器时间差值来解决,但需要考虑此倒计时到底是约束作用还是提示作用:假如是一个在线考试系统,1个小时之内必须做完,或者软件的试用期为30天,那这是约束。假如是一个离线的小管家应用,用户每玩30分钟电脑,就要提醒他该休息了,这是提示作用。二者的区别就在于是否使用客户端时间。

第一种情形,超过1小时之后,就应该停止考试,那如果用户改变了本地时间,就意味着他可以无限期考试,这显然是不合理的。因此,在考试开始前,就应该有一个请求发送到服务器,说用户开始了。此时服务器记录下这个请求接收到那一刻的服务器时间(数据时间1)(不要相信客户端时间),服务器也知道,用户的考试时长(数据时间2),客户端请求这个时间,做减法即可,服务器在倒计时结束后有相应的操作。第二种情形就可以直接使用客户端的时间。

时间展示

时间的展示一般是某些数据的时间展示,如微博信息流的时间、邮箱里邮件的发送接收时间、订单生成时间等。在没有时区的情况下,完全可以由服务端生成好时间字符串,但存在时区,就需要思考,这个时间究竟应该如何展示:展示服务器时间则需要检测客户端时区给用户相应的提示,或者展示客户端时间?或者还有一些别的要素会影响到时间的展示?

x小时前

在一些应用中的日期展示,分为短期时间和长期时间,短期时间通常使用x小时前的形式表示。这种时间差的形式也应该是无关时区的,直接用服务器时间戳的差值计算即可。或者,为了减少服务器压力,也可以拿到服务器的时间戳,在客户端做差值,长期时间使用这个时间戳在前端转换成完整的时间。而这个长期时间也有两种情况:时间是否与时区关联。比如:某健身的应用,张三在北京时间下午3点记录,”我跑步了“,李四在英国下午2点去看张三的健身记录,写着下午3点,“我跑步了”,这似乎没有什么问题。但如果是个朋友圈应用,张三在北京时间下午3点发朋友圈,李四在英国下午2点应该显示什么呢?这可能是一个时间是给别人看的还是给自己看的问题。

时区可变

在某些场景下,是允许用户改变时区的,如对于某些监控、报表系统,用户可以自行设置时区。这里的关键点是思考设置时区的目的是什么。对于私人账户或者一些to C应用的账户,这里的时区更倾向于是用户想要看到的时间。而对于多人账户或者某些to B应用的账户,时区更可能是设置的系统时间,也就是用户希望的服务器内的时间。比如,对于某次监控警告,某监控系统的服务器时间为t1,设置的时区时间t2,客户本地时间t3t1, t2, t3是同一时间戳下不同时区的时间。用户在3小时后,看到t时间有报警,那么这个t应该是t1, t2还是t3呢?

考虑与x小时前的一致(即假设系统设置3小时内用x小时前的形式展示,3小时外用完整时间展示),不会出现在下午2点,既看到2小时前的报警,又看到下午1点的报警,应该都是使用客户端当前时间/客户端所在时区的当前标准时间作为标准。这里的目的是用差值帮助用户计算时间。至于给用户之间的交流带来的麻烦,如A,B不在同一时区,A告诉B,10天前,下午三点有个报警这种情况,1. B可以自行计算,2. 除了三点这个信息,可能25分16秒更方便寻找。

既然使用t3,那t2存在的意义是什么呢?在使用仅以年月日计算的时间时,或以某个时间作为某些操作的触发时间时,使用t2更为合适。如C用户设置每月1号备份数据库,或者每年1月1日生成报表。由于用户在不同时区,且操作/报表内容应该一致,这里的备份时间和报表内的数据包含的时间,应该以t2为准。

那么,假设D用户,设置每天12点备份数据库,且备份数据库会触发告警呢?如果按照t2时区所在的时间,用户收到的可能是t3 - t2时间的告警。我觉得此类场景无解,应该给用户相应的提醒。

夏令时

夏令时的问题是,用户总能经历两段相同的时间。对于一定要使用时间字符串的应用,是一件痛苦的事。比如,某地冬令时,在2:00将时间回调到1:00,一个设定在1:30的定时任务,1. 是否要执行两遍?2. 按照时间间隔执行(按照时间戳,每24小时执行,第一次执行完,下一次执行在第二天的0:30)还是按照每天1:30执行?

个人感觉无论是在实现逻辑还是在用户逻辑,按照时间戳执行是更好的方案。但要相应的提醒用户夏令时带来的改变。

时间可变

我还真想不到,什么应用提倡时间可变的。想到再写吧。

结语

文章主要讨论了一些场景下,可能由时区带来时间的问题的设计方案。一条很基本的原则是,尽量使用时间戳来处理时间,对于时间戳不能处理的,要根据业务场景因地制宜,再佐以提示。其实在国外这几个月,在日常使用国内知名应用中,已经遇到了很多时间上的简单问题,而且也没有遇到一个应用可以明确自己的时间逻辑,甚是遗憾。

时间、国际化、可访问性都是一个成熟的软件不可或缺的部分,希望业界可以有更多的实践与更完善的标准。