先说结论:所有传递时间的地方都尽量使用13位精确到毫秒的数值类型的时间戳
前情概要
在软件开发过程中,时间类型是最常用的数据类型之一,经常出现在前后端接口调用的参数或返回值中、以及服务与中间件或其他服务交互的数据中,在这些交互中,如何传递时间类型的数据是一件十分重要的事情,一个好的传递方式可以减少交互中的错误,降低沟通成本,反之则会增加沟通成本以及错误发生的几率。
那到底什么样才算是一个好的传递方式呢,来看下面几个场景
常见场景
服务与前端之间的时间传递
这是最常见的场景,前端请求后端接口,传递数据、获取数据。时间传递发生在接口请求,传递参数以及接口响应返回数据。
先看接口请求,传递参数阶段
前端传递 时间类型的数据给 后端,一般有两种方式
- 时间格式的字符串:例如 "yyyy-MM-dd HH:mm:ss" 但是这种传递方式,需要后端将字符串转换时间类型的字段
- 时间戳(13位数值类型):前端传递时间戳给后端,后端直接使用数值类型接受,这种方式后端不需要转换操作。
因此,建议前端传递时间类型数据给后端时,优先考虑时间戳。
需要注意的是,时间戳是13位数值类型,精度为毫秒,但如果字段类型不需要精确到毫秒时,比如生日一般只到年月日,这时时间戳就应该将尾部的时分秒和毫秒值都置为0
接口返回数据阶段 返回时间类型的数据一般也是两种格式
- 时间格式的字符串:例如 "yyyy-MM-dd HH" 这种方式最常见,但也固定了前端的展示格式,如果前端想换另一种格式展示,就需要转换
- 时间戳(13位数值类型):前端可以根据展示需要,自己转换成需要的时间格式
建议后端返回数据类型给前端时也返回时间戳,前端想用什么格式自己去转换
服务与服务之前的时间传递
在微服务中不同服务使用的架构可能一致,A服务使用Spring,B服务使用node.js,无法使用某个特定语言的时间对象,比如 A 服务使用 Date 对象,想传递给B服务的话,必须进行序列化,B服务接收到序列化的值后,还需要进行解析,如果还使用特定语言的时间对象无疑会增加服务之间调用的复杂性,这个时候就应该统一使用13位数值类型的时间戳,这样各个服务都不需要转换,降低复杂性的同时,也减小了出错的概率。
服务与中间件之间的时间传递
常用的中间件通常分两类,消息队列(如 kafka、rabbitmq等)和 nosql 数据库(如 redis、mongodb等),既然是中间件,自然是多个服务之间共用的,既然是共用的,那么时间类的数据最好是统一的格式,方便其他服务使用。
一般有两种选择
- 时间格式的字符串,例如 "yyyy-MM-dd HH:mm:ss"
- 时间戳(13位数值类型)
显然这种情况下时间戳(13位数值类型)具有更好的兼容性,因为时间格式的字符串有可能是 "yyyy-MM-dd HH" ,有可能是 "yyyy-MM-dd HH:mm:ss" ,而时间戳长度都是固定的
总结
时间格式的字符串可读性最强,但是兼容性最差,需要转换后才能使用,而且还需要关心时区问题。而时间戳(13位数值类型)可读性最差,兼容性最好,而且不存在时区问题,因此建议
- 时间传递时使用时间戳(13位数值类型)
- 时间展示时(后端打log、前端展示等)使用时间格式的字符串
至于存储时一般还是使用数据库内置的时间类型,这样可以方便的使用内置时间函数
后记
上文中有提到使用时间戳(13位数值类型)表示出生日期时,需要把时分秒置为0。那像生日这种字段怎么表示呢,生日一般只有月日,这种类型的时间无法用时间戳表示。
像生日这种类型,不光无法用时间戳表示,几乎所有的中间件和数据库中也没有对应的时间类型,甚至很多语言都没有对应的时间类型,这种就只能使用时间格式字符串来表示了,如果这类字段还需要计算,可以考虑将时间拆分成数值存储,比如 生日,拆分成 月份和日期两字段
后记2
只是在 时间传递时 使用时间戳(13位数值类型),而程序内部依然使用时间对象的话,用起来会很不方便,每次接收传递的时间戳(13位数值类型)和传递时间戳时,都需要转换。
为了避免转换,我建议在全链路中都使用时间戳,除了以下情况
- 需要展示人类可读的时间时 :比如打印日志、前端展示
- 需要计算时