Python中的夏令时

24

我正在编写一个需要处理时区和跨越时区的程序。最常涉及到的两件事是从“现在”创建一个日期时间对象,然后将一个无时区的日期时间对象本地化。

为了在太平洋时区从“现在”创建一个日期时间对象,我目前正在做以下操作(使用Python 2.7.2+):

from datetime import datetime
import pytz
la = pytz.timezone("America/Los_Angeles")
now = datetime.now(la)

如果考虑到夏令时,这样做是正确的吗?如果不是,我应该这样做:

now2 = la.localize(datetime.now())

我的问题是为什么?有人能展示一个第一种方法错误而第二种方法正确的案例吗?

至于我的第二个问题,假设我有一个来自用户输入的天真日期和时间,例如2012年9月1日早上8点,位于洛杉矶,加利福尼亚州。制作日期时间的正确方法是这样吗:

la.localize(datetime(2012, 9, 1, 8, 0))

如果不行,我应该如何构建这些日期时间?


美国参议院刚刚投票决定取消日光节约时间的转换,全年保持夏令时。我非常希望这能成为法律。 - Mark Ransom
完全同意@MarkRansom的观点。为了解决编程中的夏令时问题,无数个体投入了大量时间和精力,这似乎没有多大意义。 - cdraper
2
如果取消美国夏令时成为法律,它并不会真正节省太多工作。任何想要使用过去、其他国家或潜在未来(其中夏令时被重新引入)的时间戳的系统都必须正确编码。 - Jonathan Hartley
4个回答

38

根据 pytz 文档

处理时间的首选方式是始终使用 UTC,在生成供人类阅读的输出时才转换为本地时间。

因此,理想情况下应该使用 utcnow 而不是 now

假设由于某些原因你必须使用本地时间,如果在夏令时转换窗口期间进行本地化当前时间,仍可能遇到问题。同一 datetime 可能会在夏令时和标准时间之间重复出现,而 localize 方法无法解决冲突,除非你使用 is_dst 参数明确告诉它。

因此,要获取当前的 UTC 时间:

utc = pytz.timezone('UTC')
now = utc.localize(datetime.datetime.utcnow())

并且仅在必要时将其转换为本地时间:

la = pytz.timezone('America/Los_Angeles')
local_time = now.astimezone(la)

编辑:正如在评论中被@J.F. Sebastian指出的那样,你的第一个例子使用datetime.now(tz)将在所有情况下都能工作。就像我上面所述的那样,你的第二个例子会在秋季转换期间失败。我仍然主张除了显示之外,一切都使用协调世界时而不是本地时间。


1
获取给定时区的当前时间的首选方法是:datetime.now(tz) - jfs
@J.F.Sebastian 我怀疑这在使用pytz时区时不可靠,原因与datetime构造函数不适用于它们相同。 - Mark Ransom
@J.F.Sebastian 我已经尝试了一些有问题的时区,比如香港,令人惊讶的是它完美地解决了我的问题,我的担心是过度了。谢谢! - Mark Ransom
截至2022年3月15日,文档中指出:“由于许多datetime方法将naive datetime对象视为本地时间,因此最好使用aware datetimes来表示UTC时间。因此,创建表示当前UTC时间的对象的推荐方式是调用datetime.now(timezone.utc)。” - mike rodent
@mikerodent 在我写这个答案的时候,timezone.utc 不存在。 - Mark Ransom

10
第一个解决方案在考虑夏令时方面是正确的,而第二个方案不好。 我举个例子,在欧洲运行这段代码:
from datetime import datetime
import pytz # $ pip install pytz

la = pytz.timezone("America/Los_Angeles")
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
now = datetime.now(la)
now2 = la.localize(datetime.now())
now3 = datetime.now()
print(now.strftime(fmt))
print(now2.strftime(fmt))
print(now3.strftime(fmt))

我得到以下结果:

2012-08-30 12:34:06 PDT-0700
2012-08-30 21:34:06 PDT-0700
2012-08-30 21:34:06 

datetime.now(la) 会创建一个包含当前LA时间的datetime对象,并带有LA的时区信息。

la.localize(datetime.now()) 为一个没有时区信息的datetime对象添加时区信息,但是它不会进行时区转换;它只是假设该时间已经处于该时区。

datetime.now() 创建一个没有时区信息的本地时间的datetime对象。

只要您在LA,您就看不到区别,但如果您的代码在其他地方运行,则可能无法如您所愿。

除此之外,如果您需要严格处理时区问题,最好将所有时间都保存为UTC时间,这样可以避免夏令时(DST)带来的许多麻烦。


即使您在洛杉矶,now2 在夏令时结束(“回退”)期间可能会返回错误的结果(相差一小时)。datetime.now(la) 即使在 UTC 转换期间也能正常工作。 - jfs

5

这个有效:

# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45) 
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone

# Convert to UTC timezone aware datetime
d = utc.localize(d) 
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)

# show as in LA time zone (not converting here)
d.astimezone(pst) 
>>> datetime.datetime(2016, 11, 5, 9, 43, 45, 
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we get Pacific Daylight Time: PDT

# add 1 day to UTC date
d = d + datetime.timedelta(days=1) 
>>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>)

d.astimezone(pst) # now cast to LA time zone 
>>> datetime.datetime(2016, 11, 6, 8, 43, 45, 
tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) 
# Daylight saving is applied -> we get Pacific Standard Time PST

这种方法行不通:

# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45) 
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone

# convert to UTC timezone aware datetime
d = utc.localize(d) 
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)

# convert to 'America/Los_Angeles' timezone: DON'T DO THIS
d = d.astimezone(pst) 
>>> datetime.datetime(2016, 11, 5, 9, 43, 45, 
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) 
# we are in Pacific Daylight Time PDT

# add 1 day to LA local date: DON'T DO THAT
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 9, 43, 45, 
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) 
# Daylight Saving is NOT respected, we are still in PDT time, not PST

结论:

datetime.timedelta() 不会 考虑夏令时。

始终在UTC时区进行时间加减。仅在输出/显示时转换为本地时间。


查看此链接以获取更多详细信息:https://medium.com/@eleroy/10-things-you-need-to-know-about-date-and-time-in-python-with-datetime-pytz-dateutil-timedelta-309bfbafb3f7 - MrE

1

pytz网站称:

遗憾的是,对于许多时区来说,在标准datetime构造函数的tzinfo参数中使用pytz“不起作用”。

因此,您不应该使用 datetime.now(la)。我不知道具体情况,但有些时区采用比我们习惯的更奇特的规则,而Python的datetime代码无法处理它们。使用pytz的代码,它们应该被正确处理,因为这是pytz的目的。由于夏令时跳跃时间的缘故,在发生两次的时间方面也可能存在问题。

至于第二个问题,文档正是如此,所以您应该没问题。


如果使用datetime.now(tz),那么它就可以工作(这里不应该使用.localize())。 - jfs
1
你应该使用datetime.datetime.utcnow().astimezone(tz)——这会获取UTC时间,然后根据时区tz的规则进行偏移。(将pytz时区传递到构造函数中将会得到一些不是整数小时的偏移量。) - jobermark
@jobermark: 使用 now(tz) 替代 utcnow().astimezone(tz)它可以工作 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接