总是记错弄乱,自己整理一份。
基本概念
格林尼治标准时间(GMT)
英语:Greenwich Mean Time,简称 GMT。
是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
由于地球每天的自转是有些不规则的,而且正在缓慢减速,GMT 已经被原子钟报时的 UTC 顶替。
协调世界时(UTC)
英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称 UTC。
其以原子时秒长为基础,是最主要的世界时间标准。
关于缩写:
国际电信联盟希望协调世界时能够在所有语言有单一的缩写。英语和法语区的人同时希望各自的语言缩写-CUT 和 TUC 能够成为国际标准,结果最后妥协使用 UTC。
GMT 与 UTC
目前能看到 GMT 字眼的,常见于 JavaScript 的 Date()
和谷歌搜索「伦敦时间」时展示的。
我也不是很理解为什么显示 GMT,但是即便显示的是 GMT,也是经过校准的,与正确时间相差不到一秒。
网络时间协议(NTP)
英语:Network Time Protocol,缩写:NTP。
NTP 是一个在电脑系统之间进行时钟同步的网络协议,意图将所有参与电脑的协调世界时(UTC)时间同步到几毫秒的误差内。
自1985年以来,NTP是目前仍在使用的最古老的互联网协议之一。
时区与本地时间
一般说 UTC 时间也就是指零时区 +00:00(或写成 UTC+0、UTC±00:00) 时间,比北京时间早八个小时。
2021-01-01 08:00:00 +08:00
也就是北京时间的上午八点钟,而这时 UTC 时间为 2021-01-01 00:00:00 +00:00
,英国伦敦的午夜,这是同一个时刻。
时间戳
这里指的时间戳是指代码中的 timestamp 那一串长数字。
编程中大家讨论和使用的皆是 Unix 时间戳(Unix timestamp 或 POSIX timestamp),是从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的秒数,有的语言也支持了毫秒、微秒等等。
- 1 秒 = 1000 毫秒
- 1 毫秒 = 1000 微秒
很多编程语言都有生成当前时间戳的方法,目前的时间戳长度:
- 10 位长度,精确到秒
- 13 位长度,精确到毫秒
- 16 位长度,精确到微秒
在 2286 年 11 月 21 日 1 时 46 分 39 秒之后,时间戳会加长一位数。
时间戳有时区吗?
当我们讨论时间戳的时候,也就是前后端交换数据,数据库存储日期时间时,皆是 Unix 时间戳,时间戳是没有时区概念的。
或者可以理解为,时间戳是固定死零时区的。
2021-01-01 00:00:00+00:00
与 2021-01-01 08:00:00+08:00
的时间戳都是 1609459200
。
所以建议在数据库或数据传输时使用 Unix 时间戳,时间戳可以被简单地解析为 UTC 时间或任何时区的时间,不会被时区概念束缚和混淆。
ISO 8601
国际标准 ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。
简单来说,日期与时间的规定写法是这样的:
2000-01-23 # 年使用四位数,月、日、时、分、秒皆使用两位数,不够的前面补零。
2000-01-23T04:05:06 # 用 T 分割日期与时间。
2000-01-23T04:05:06Z # 用 Z 代表 UTC+0(零时区)
2000-01-23T04:05:06+00:00 # 也可以使用 +00:00 表示零时区
2000-01-23T04:05:06+08:00 # 东八区
2000-01-23T04:05:06.123456 # Python 中 datetime.now().isoformat() 的默认格式
实际上 ISO 8601 也允许了一些其他的写法。
RFC 3339 与 ISO 8601
Python 用的是 ISO 8601:
datetime.now().isoformat()
datetime.utcnow().isoformat()
Golang 用的是 RFC 3339:
time.Now().Format(time.RFC3339)
time.Now().UTC().Format(time.RFC3339)
What’s the difference between ISO 8601 and RFC 3339 Date Formats?
RFC 3339 被列为 ISO 8601 的配置文件。有一些很小的差异,比如 RFC 3339 不允许两位数的年份,RFC 3339 允许用空格代替
T
。就一般的使用情况来说,两者之间可视为没有差异。
获取时间戳
Python:
import time
from datetime import datetime
# 正确的
print(time.time())
print(datetime.now().timestamp())
# 错误的
print(datetime.utcnow().timestamp())
timestamp()
经过了 Python 内部处理的,将当前的系统时间(无论你在哪个时区)转换为正确的标准的 Unix 时间戳。
而最下面的那个为什么错了,因为 timestamp()
默认你给的 datetime
是当前系统的时区,如果你在零时区,那的确没有问题;如果不在零时区,timestamp()
会认为我给的是东八区,所以最后返回了错误的时间戳。
可以解决这个错误,只需要在 timestamp()
之前将 datetime
的时区确定即可。
from datetime import datetime, timedelta, timezone
d1 = datetime.now()
d1 = d1.replace(tzinfo=timezone(timedelta(hours=8))) # 绑定时区为东八区
ts1 = d1.timestamp()
print(ts1)
d2 = datetime.utcnow()
d2 = d2.replace(tzinfo=timezone.utc) # 绑定时区为零时区
ts2 = d2.timestamp()
print(ts2)
# ts1 和 ts2 基本相等,只有程序运行顺序的微秒级差距。
Golang:
Golang 中是一样的:
time.Now().Unix()
time.Now().UTC().Unix()
因为 Golang 的 time.Now()
中默认就带有时区信息。
Python 常用的日期与时间方法
datetime 转 str
import datetime
d = datetime.datetime.now()
s = d.strftime('%Y-%m-%d %H:%M:%S')
str 转 datetime
from datetime import datetime, timedelta, timezone, time
s = "2001-02-03 04:05:06"
d = datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
# 为得到的 datetime 增加时区
d_with_timezone = d.replace(tzinfo=timezone(timedelta(hours=8)))
获取当前 UTC 时间(带时区)
不加后面的 replace
就不带时区信息了,下同。
from datetime import datetime, timezone
datetime.utcnow().replace(tzinfo=timezone.utc)
# 2021-05-29 15:28:10.913530+00:00
获取当前系统时间(带时区)
from datetime import datetime, timezone
# 在中国时区电脑上运行:
datetime.now().replace(tzinfo=timezone(timedelta(hours=8)))
# 2021-05-29 23:38:21.006309+08:00
datetime 转 时间戳
from datetime import datetime, timezone, timedelta
dt = datetime.now()
print(dt, 'datetime.now() 当前系统时间(不含时区)')
print(dt.timestamp(), '不含时区的时间,会认为是系统时间,会自动转换为正确的 UTC 时间戳')
print(dt.replace(tzinfo=timezone(timedelta(hours=8))).timestamp(), '包含东八区时区信息的,同上')
# 2021-07-16 00:15:02.730997 datetime.now() 当前系统时间(不含时区)
# 1626365702.730997 不含时区的时间,会认为是系统时间,会自动转换为正确的 Unix 时间戳
# 1626365702.730997 包含东八区时区信息的,同上
时间戳 转 datetime
from datetime import datetime
datetime.utcfromtimestamp(1609459200)
# 2021-01-01 00:00:00
datetime.fromtimestamp(1609459200, timezone.utc)
# 2021-01-01 00:00:00+00:00
datetime.fromtimestamp(1609459200)
# 2021-01-01 08:00:00
datetime.fromtimestamp(1609459200, timezone(timedelta(hours=8)))
# 2021-01-01 08:00:00+08:00
datetime 转换时区
转换前需要先确定被转换的 datetime
对象的时区,拥有时区属性的 datetime
对象可以转换为任意一个时区的日期时间。
from datetime import datetime, timedelta, timezone
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt) # 2021-01-01 00:00:00+00:00
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt) # 2021-01-01 08:00:00+08:00
tokyo_dt = bj_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt) # 2021-01-01 09:00:00+09:00