升级之路
首发于升级之路
互联网上的日期和时间

互联网上的日期和时间

最近参加公司Hackthon,自己写的项目,同事写的项目,都踩了时间的坑。以前升级Celery到4.1.0之后定时任务同样出了时区的BUG。时间/时区这块,大家经常用,但是却没有系统的整理总结,遂写此文。

日期(Date)、时间(Time)和时间戳(Timestamp)

互联网这么大,覆盖了全球这么多时区。如果没有统一的标准来规定怎么表示时间,那么一定是混乱的,无法正常运行的。我们需要一个统一的标准,来规范统一不同语言、不同时区对同一个时刻的表示。

国际标准组织(ISO)制定了ISO 8601,我们的中华人民共和国国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》与ISO 8601:2000等效采用。

上面两个标准,对开发者而言,太过于严肃。其实有一份更加简单易读的文件 RFC3339 Date and Time on the Internet: Timestamps。本文之后的讨论,都基于 RFC3339。

RFC3339 比 ISO 8601 有一个很一个明显的限制,这里提一下:ISO允许24点,而 RFC3339 为了减少混淆,限制小时必须在0至23之间。23:59过1分钟,是第二天的0:00。

阅读RFC3339,我主要明确下面三个定义:

  • 时间戳
  • 本地时间
  • 标准时间

时间戳

时间戳是一个数字,定义为格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。注意,同一时刻,不同时区获得的时间戳是相同的。以前很多用来记录时间的字段,在数据库中往往不会存储为Datetime类型,而是直接存储为无符号整形,存放时间戳的值。

Python获取时间戳的代码为

import time
int(time.time())

本地时间

当前时区的本地时间

import datetime
datetime.datetime.now()

上面的输出值为

2017-12-08 11:50:03.537506

标准时间

本地时间只包括当前的时间,不包含任何时区信息。同一时刻,东八区的本地时间比零时区的本地时间快了8个小时。在不同时区之间交换时间数据,除了用纯数字的时间戳,还有一种更方便人类阅读的表示方式:标准时间的偏移量表示方法。

RFC3339详细定义了互联网上日期/时间的偏移量表示:

2017-12-08T00:00:00.00Z

这个代表了UTC时间的2017年12月08日零时

2017-12-08T00:08:00.00+08:00

这个代表了同一时刻的,东八区北京时间(CST)表示的方法

上面两个时间的时间戳是等价的。两个的区别,就是在本地时间后面增加了时区信息。Z表示零时区。+08:00表示UTC时间增加8小时。

这种表示方式容易让人疑惑的点是从标准时间换算UTC时间。以CST转换UTC为例,没有看文档的情况下,根据 +08:00 的结尾,很容易根据直觉在本地时间再加上8小时。正确的计算方法是本地时间减去多增加的8小时。+08:00减去8小时才是UTC时间,-08:00加上8小时才是UTC时间。

为什么我们会踩坑

了解了本地时间和标准时间之后,很容易想到为什么我们会在这里出问题。

很自然的,开发者编写代码处理用户输入的时间类型数据,处理的都是本地时间的格式。而计算机系统处理逻辑,依赖标准时间。从用户输入的本地时间,转化成系统能处理的标准时间,这个过程是系统隐式的自动转换。当系统的时区设置和用户所处的时区不一致的时候,时间经过一次输入,处理之后再输出,就有了差异。

为了避免在时间处理上出现问题,核心就是统一输入输出时间的转化。因此解决的思路也是两个:

  1. 强制用户输入的时间类型是标准的,使用时间戳/标准时间。如果你的系统是给全球用户使用的,应该采用这种功能方法。这是最准确,最不容易出错的。
  2. 系统的时区和用户的时区严格一致。项目规模小,全部都是一个时区的用户。采用这种方案,一旦用户跨时区了,还是会存在问题。不是很推荐。

时区的名称可以查维基百科,也可以查官方的数据库 Time Zone Database

踩过的坑和解决方法

避免踩时间坑,总的改进方式:

  • 系统最好都配好NTP,保证时间准确。
  • 所有的输入数据,最好转换成标准时间,再交给业务逻辑处理。
  • 在天朝,还是把系统时区设置成北京时间比较好。

下面是一些踩过的坑:

Docker 设置时区

很多Docker镜像都是UTC时区,在上海使用镜像启用容器,容器里面获取的本地时间都会慢8个小时。可以在Dockerfile里面设置时区。参考这个问题的回答

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Ubuntu 设置时区

Docker里面大部分镜像是基于Debian的,Docker这么解决,Ubuntu上思路类似,直接执行命令就好了。

TZ=Asia/Shanghai
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

可以执行date命令查看时间,CST是中国标准时间。

➜ ~ date
Fri Dec 8 14:12:46 CST 2017

Linux 和 Windows 双系统时间差

以前写过 修复Ubuntu和Win10双系统时间差

Celery 4.1.0 时区计算错误

这个PR Correct calculation of application current time with timezone 已经修复了,但是现在还没发布新版本。要么回退4.0.2,要么等下个版本。

Python 怎么从本地时间转换成标准时间

Python 里面的 datetime.datetime.now() 拿到的是本地时间。如果用这个保存时间,请务必确保系统的时区设置正确,统一。

Python中间的时间戳、datetime之间的转换,最好强制写清楚时区。这是个例子

# 时间戳转换到CST时间
timestamp = int(time.time())
datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone(datetime.timedelta(hours=8)))
原文链接 作者 @柳纯
本文版权属于再惠研发团队,欢迎转载,转载请保留出处。
编辑于 2019-11-22

文章被以下专栏收录

    没错,我们就是想招人。公司主页上没啥东西,有兴趣的同学可以去拉钩看我们的招聘职位:https://www.lagou.com/gongsi/j86312.html