1
时区
时间差
在每个地区,中午 12 点都对应着正午,但是每个地区的 12 点是统一时刻吗?显然不是,要不然也不会有时差概念了。下图最右边的图显示着火车穿过两个时区,那么记录的时间应该是处在时区的那个时间,因此区分时区很重要。
世界上不同地区显示的时间不同,北京时间就比美国东部时间快 13 个小时,看下图:
UTC
为了方便比较不同时区的时间,我们用协调世界时间当作基准。
协调世界时间 (Coordinated Universal Time, UTC) 是最主要的世界时间标准,在在时刻上尽量接近于格林威治标准时间 (Greenwich Mean Time, GMT)。UTC 可以视为一个世界统一的时间,其他时区的时间都是在这个基础上增加或减少的,比如
这样看北京时间比美东时间快 13 个小时,因为 UTC + 8 - (UTC - 5) = 13。有了标准 UTC,时间就可以比较了。
当用 datetime()
对象创建时间式,如果不设定时区,那么这个时间被称为不考虑时区 (UTC-naive) 的日期时间;如果设定时区,那么这个时间被称为考虑时区 (UTC-aware) 的日期时间。
from datetime import datetime, timedelta, timezone
创建一个不考虑时区的日期时间,如果你处理的问题不需要考虑多个时区,那么这个时间可看做是你处理问题所在地区的时间;如果你处理的问题需要考虑多个时区,那么这个时间可看做是 UTC。
dt = datetime(2020, 6, 27, 21, 30)
print(dt)
2020-06-27 21:30:00
已知美国东部时区时间比 UTC 慢 5 个小时,因此可用 timedelta()
对象定义一个负 5 个小时的时间差,并传入 timezone()
对象中定义美东时区 ET
。
ET = timezone(timedelta(hours=-5))
dt = datetime(2020, 6, 27, 21, 30, tzinfo=ET)
print(dt)
2020-06-27 21:30:00-05:00
打印出来的日期时间都是当地时间 (此时是美东时间),而最后有 -05:00
的字样,它叫做 UTC offset,负号代表比 UTC 慢 5 个小时。
已知北京时间比 UTC 快 8 个小时,因此可用 timedelta()
对象定义一个正 8 个小时的时间差,并传入 timezone()
对象中定义北京时区 BJ
。
BJ = timezone(timedelta(hours=8))
dt = datetime(2020, 6, 27, 21, 30, tzinfo=BJ)
print(dt)
2020-06-27 21:30:00+08:00
astimezone()
打印出来的都是当地时间 (此时是北京时间),而最后有 +08:00
的字样,它叫做 UTC offset,正号代表比 UTC 快 8 个小时。
现在定义一个美东时间 dt
为 2020-06-27 早上 9 点 30 分,用 astimezone()
对象显示出对应的北京时间是多少,结果是 2020-06-27 晚上 22 点 30 分。但两者的绝对差异是零。这个现实被称作相同时刻,不同时间 (same moment, different time)。
dt = datetime(2020, 6, 27, 9, 30, tzinfo=ET)
print(f'美东时间:{dt}')
print(f'北京时间:{dt.astimezone(BJ)}')
print(dt.astimezone(BJ)-dt)
美东时间:2020-06-27 09:30:00-05:00
北京时间:2020-06-27 22:30:00+08:00
0:00:00
同理,定义一个北京时间 dt
为 2020-06-27 晚上 22 点 30 分,用 astimezone()
对象显示出对应的美东时间是多少,结果是 2020-06-27 早上 9 点 30 分。两者的绝对差异是零。相同时刻,不同时间。
dt = datetime(2020, 6, 27, 22, 30, tzinfo=BJ)
print(f'北京时间:{dt}')
print(f'美东时间:{dt.astimezone(ET)}')
print(dt.astimezone(ET)-dt)
北京时间:2020-06-27 22:30:00+08:00
美东时间:2020-06-27 09:30:00-05:00
0:00:00
replace()
dt.replace(some_tz)
函数返回一个具有同样值的日期,但是在不同时区,即 dt 的时区和 some_tz 时区不同,这个叫做相同时间,不同时刻 (same time, different moment)。
dt = datetime(2020, 6, 27, 9, 30, tzinfo=ET)
dt_to_utc = dt.replace(tzinfo=timezone.utc)
dt_as_utc = dt.astimezone(timezone.utc)
print(dt)
print(dt_to_utc)
print(dt_as_utc)
print(dt - dt_to_utc)
print(dt - dt_as_utc)
2020-06-27 09:30:00-04:00
2020-06-27 09:30:00+00:00
2020-06-27 13:30:00+00:00
4:00:00
0:00:00
从上面结果可看出两点:
dt_to_utc
和 dt
是相同时间 (都是 2020-06-27 09:30:00),不同时刻 (从它俩的 UTC offset 或者它俩之差 4:00:00 看出来)dt_as_utc
和 dt
是不同时间 (前者是 14:30:00 后者是 09:30:00),相同时刻 (从它俩之差是 0:00:00 看出来)dateutil.tz
在实际操作做很难记住每个时区的时间和 UTC 差多少,幸运的是 dateutil
包里的 tz
对象可以帮我们解决这个难题。只需用 '区域/城市'
来设定时区就可以了,比如
'America/New_York'
来设定'China/Bei_Jing'
来设定tz
是 timezone 的缩写,可看成是时区的数据库,首先从 dateutil
引入它,然后用 gettz()
函数加上设定的字符串时区来获取时区对象。
from dateutil import tz
ET = tz.gettz('America/New_York')
BJ = tz.gettz('China/Bei_Jing')
以下结果复制了上面结果,只不过现在用 tz.gettz()
加字符串来设置时区,而上面用 timezone(timedelta())
加时间差来设置时区。对普通人来说,记住形象的字符串比记住枯燥的时差容易多了吧。
dt = datetime(2020, 6, 27, 9, 30, tzinfo=ET)
dt_to_bj = dt.replace(tzinfo=BJ)
dt_as_bj = dt.astimezone(BJ)
print(dt)
print(dt_to_bj)
print(dt_as_bj)
print(dt - dt_to_bj)
print(dt - dt_as_bj)
2020-06-27 09:30:00-04:00
2020-06-27 09:30:00+08:00
2020-06-27 21:30:00+08:00
12:00:00
0:00:00
提示:注意力放在 dt - dt_to_bj
的结果 12:00:00
上,美东时间比北京时间慢了 12 个小时。
现在突发奇想换个日期 2020-01-11 看看有什么变化?
dt = datetime(2020, 1, 11, 9, 30, tzinfo=ET)
dt_to_bj = dt.replace(tzinfo=BJ)
dt_as_bj = dt.astimezone(BJ)
print(dt)
print(dt_to_bj)
print(dt_as_bj)
print(dt - dt_to_bj)
print(dt - dt_as_bj)
2020-01-11 09:30:00-05:00
2020-01-11 09:30:00+08:00
2020-01-11 22:30:00+08:00
13:00:00
0:00:00
这时 dt - dt_to_bj
的结果是 13:00:00
上,表示美东时间比北京时间慢了 13 个小时 (之前只慢了 12 个小时)。此外更明显的是,
为什么不同日期上同样的美东时间对应的北京时间会不同呢?时间差还能随日期变?你说对了,夏令时了解一下。
2
夏令时
夏令时 (daylight saving time, DST) 则是为了充分利用夏天日照长的特点,充分利用光照节约能源而人为调整时间的一种机制。通过在夏天将时间向前加一小时,使人们早睡早起节约能源。虽然很多西方国家都采用了DST,但是中国不采用 DST。
夏令时 (daylight saving time) 是由“创始人”本杰明·富兰克林提出,他在 1784 年发表《节约日光成本的经济工程》, 原因是注意到人们 10 点后才起床,夜生活过到深夜。所以建议大家早睡早起,每年可节省不少蜡烛。但他没能目睹这天到来。在第一次世界大战期间,美国首次实施日光节约,以此来节省燃料。1966年,约翰逊总统签署了统一时间法案,才使其成为法律。
美股开盘时间在中国的晚上,因为美国有夏令时间 , 因此夏天的交易时间与冬天相比会提前一小时:
在 2020 年,夏令时是从 3 月 8 日早上 2 点开始,到 11 月 1 日早上 2 点结束。
夏令时的起点 (将表前拨)
在 3 月 8 日早上 2 点,大家把表往前调 1 个小时到早上 3 点,感觉是 2 点到 3 点这一段的时间突然没有了,如下图所示:
但是对应到 UTC 上,这段时间根本没有消失,只不过美东时间的 UTC offset 变了,由原来的 UTC-5 变成了 UTC-4,即从原来正常的慢 5 个小时到现在慢 4 个小时,如下图所示:
ET = tz.gettz('US/Eastern')
spring_159am = datetime(2020, 3, 8, 1, 59, 59, tzinfo=ET)
spring_3am = datetime(2020, 3, 8, 3, 0, 0, tzinfo=ET)
print(spring_159am)
print(spring_3am)
(spring_3am - spring_159am).total_seconds()
2020-03-08 01:59:59-05:00
2020-03-08 03:00:00-04:00
3601.0
spring_159am = spring_159am.astimezone(tz.UTC)
spring_3am = spring_3am.astimezone(tz.UTC)
print(spring_159am)
print(spring_3am)
(spring_3am - spring_159am).total_seconds()
2020-03-08 06:59:59+00:00
2020-03-08 07:00:00+00:00
1.0
夏令时的终点 (将表后拨)
在 11 月 1 日早上 2 点,大家把表往后调 1 个小时到早上 1 点,把之前“丢失的那一个小时找回来了”,回归正常。注意在调时间这个动作点 (夏令时终点) 的前后从“1 点到 2 点”的时间段有歧义,它们既可以指夏令时结束之前的时间段,也可以指夏令时结束之后的时间段。为了偏于说明,用两个时间轴来区分,如下图所示:
但是对应到 UTC 上,丢失的那一个小时找回来,使得对应的美东时间的 UTC offset 变了,由原来的 UTC-4 变成了 UTC-5,即从原来慢 4 个小时又回到正常情况的慢 5 个小时,如下图所示:
ET = tz.gettz('US/Eastern')
首先用 datetime_ambiugous()
函数来验证在早上 1 点到 2 点这段时间段中的时间是否有歧义:
first_1am = datetime(2020, 11, 1, 1, 0, 0, tzinfo=ET)
tz.datetime_ambiguous(first_1am)
first_159am = datetime(2020, 11, 1, 1, 59, 59, tzinfo=ET)
tz.datetime_ambiguous(first_159am)
first_2am = datetime(2020, 11, 1, 2, 2, 0, tzinfo=ET)
tz.datetime_ambiguous(first_2am)
True
True
False
由于 1:00:00 这个时点有歧义,我们先创建两个日期时间对象 first_1am
和 second_1am
,发现两者在 ET 时区和 UTC 的时间差都为零。
first_1am = datetime(2020, 11, 1, 1, 0, 0, tzinfo=ET)
second_1am = datetime(2020, 11, 1, 1, 0, 0, tzinfo=ET)
(second_1am - first_1am).total_seconds()
0.0
first_1am = first_1am.astimezone(tz.UTC)
second_1am = second_1am.astimezone(tz.UTC)
(second_1am - first_1am).total_seconds()
0.0
那这样就无法实现夏令时结束“时间回调”这个现象了,好在我们用 enfold()
函数,它将有歧义的时间“折叠”起来,使得转换成 UTC 时能考虑到“时间回调”。从 first_1am
和 second_1am
之间的时间差为 3600 秒可以看出 enfold()
函数的作用了。
first_1am = datetime(2020, 11, 1, 1, 0, 0, tzinfo=ET)
second_1am = datetime(2020, 11, 1, 1, 0, 0, tzinfo=ET)
second_1am = tz.enfold(second_1am)
(second_1am - first_1am).total_seconds()
0.0
first_1am = first_1am.astimezone(tz.UTC)
second_1am = second_1am.astimezone(tz.UTC)
(second_1am - first_1am).total_seconds()
3600.0
将两者表示成美东时间,发现 first_1am
是夏令时结束前的早上 1 点钟,比 UTC 慢 4 小时,而 second_1am
是夏令时结束后的早上 1 点钟,比 UTC 慢 5 小时。
print(first_1am.astimezone(ET))
print(second_1am.astimezone(ET))
2020-11-01 01:00:00-04:00
2020-11-01 01:00:00-05:00
3
总结
这么清楚的帖子还需要总结吗?需要:
astimezone()
不同时间,相同时刻。replace()
相同时间,不同时刻。dateutil.tz
可以方便设定时区