pytz本地化 vs datetime替换

74

我在使用pytz的.localize()函数时遇到一些奇怪的问题。有时它不会对本地化的日期时间进行调整:

.localize行为:

>>> tz
<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD> 
>>> d
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421)

>>> tz.localize(d)
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421, 
                  tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
>>> tz.normalize(tz.localize(d))
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421,
                  tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)

正如您所见,本地化/标准化操作并没有改变时间。 然而,如果使用.replace:

>>> d.replace(tzinfo=tz)
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421, 
                  tzinfo=<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD>)
>>> tz.normalize(d.replace(tzinfo=tz))
datetime.datetime(2009, 9, 2, 15, 1, 42, 91421,
                  tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)

看起来是在将时间进行调整。

问题是 - 哪一个是正确的,为什么其他的是错误的?


4个回答

59

localize 假设您传递的本地时间是“正确”的(除了时区不知道!),因此只设置时区,没有其他调整。

建议在内部使用UTC(而不是使用naive datetimes),并且在需要以本地化方式执行日期和时间输入/输出时使用 replacenormalize将处理DST和类似问题)。


2
引用Alex的建议,在I/O操作期间使用UTC并进行本地化/去本地化。我可以建议这不仅仅是可行的,而且强烈推荐(甚至是必须的)! - DrFalk3n
8
楼主询问了“本地化(localize)”和“替换(replace)”之间的区别。“替换”是否只是设置时区,而不做其他调整?如果是这样,为什么两种方法的结果会有差异? - Michael Waterfall
10
pytz.timezone() 可对应多个时区信息(同一地点,不同UTC偏移和时区缩写)。 tz.localize(d) 尝试查找给定的d本地时间的正确时区信息(某些本地时间无法明确或不存在)。 replace() 只是根据 pytz 时区提供的默认信息(在最近版本中为 LMT)设置任何(随机的)信息,而不考虑给定的日期。如果d是一个不存在的本地时间,例如春季(北半球)DST转换期间的时间,则 tz.normalize() 可以调整时间,否则在这种情况下它不会执行任何操作。 - jfs
2
因此,replace(tzinfo = ...) 似乎是无用的。最好避免使用它。 - paolov
@paolov replace 并不是完全无用的,它只是对于 tzinfo 对象的工作方式做出了某些假设,而这些假设并不适用于 pytz。例如,我认为它适用于 Python 3.9 中引入的新的 ZoneInfo 对象 - Mark Ransom

35

localize是用于创建具有初始固定日期时间值的datetime对象的正确函数。生成的datetime对象将具有原始日期时间值。在我看来,这是非常常见的用法,也许pytz可以更好地记录。

replace(tzinfo = ...)的命名不幸。它是一个行为随机的函数。我建议避免使用此功能来设置时区,除非你喜欢自我折磨。我已经因使用此功能而受够了痛苦。


3
没什么好反驳的。 - wrgrs
完全一致。目前正在处理一个问题,replace()根本不起作用,但没有引发任何错误。什么也没做。需要更好的方法来强制将naive datetime对象转换为UTC。 - Hephaestus
1
我也曾经历过replace(tzinfo=...)。我能理解你的痛苦。 - mcella
2
对于那些在“post-pytz”世界(其中localize()不再存在)阅读的人们:我以前也有过使用datetime.replace(tz=...)的类似糟糕经历。然而,在使用符合PEP 495标准的时区(例如ZoneInfo)的情况下,datetime.replace(tz=...)表现得足够好,以至于社区已经接受了再次使用它。例如,Django的make_aware()只是简单地包装了datetime.replace()(但如果它发现您仍在使用pytz,则仍然使用localize())。这里也推荐使用:https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html - Kye
pytz.localize 实际上只是 datetime.replace() 的一个简单封装。https://github.com/stub42/pytz/blob/master/src/pytz/__init__.py#L423 - undefined

13

DstTzInfo类用于表示那些在某些时间点与UTC偏移量发生变化的时区。例如(您可能已经知道),许多地点在夏季初转换为“夏令时”,然后在夏末回到“标准时间”。每个DstTzInfo实例仅代表其中一个时区,但“localize”和“normalize”方法可帮助您获取正确的实例。

对于阿比让而言,根据pytz的记录,只发生过一次转换,即在1912年。

>>> tz = pytz.timezone('Africa/Abidjan')
>>> tz._utc_transition_times
[datetime.datetime(1, 1, 1, 0, 0), datetime.datetime(1912, 1, 1, 0, 16, 8)]

从pytz获取的tz对象代表了1912年之前的时区:

>>> tz
<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD>

看一下你提供的两个例子,当你调用 tz.localize(d) 时,不会将这个1912年之前的时区添加到你的原始时间对象中。它假设你提供的时间对象代表正确时区的本地时间,即后1912年的时区。

然而在第二个例子中,使用 d.replace(tzinfo=tz),它将你的时间对象表示为1912年之前的时区的时间。这可能不是你想要的。然后当你调用 dt.normalize 时,它将其转换为正确的时区,即后1912年的时区。


8

我知道我有点晚了……但是我发现以下方法很有效。如Alex所说,使用UTC时间:

tz = pytz.timezone('Africa/Abidjan')
now = datetime.datetime.utcnow()

然后进行本地化:
tzoffset = tz.utcoffset(now)
mynow = now+tzoffset

而且这种方法可以完美处理夏令时


2
如果.utcoffset()方法期望的本地时区与UTC不同,则会出现错误。要获取当前本地时区时间:now = datetime.now(tz)。要将UTC时间转换为本地时区时间:dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(tz)(这里不需要使用.localize().normalize())。 - jfs
1
如果偏移量跨越了夏令时边界,也可能是不正确的。 - paolov
这是误导性的。使用aware datetime来表示特定时区的日期和时间。要在特定时区获取当前时间,请向datetime.now提供tz。要获取当前UTC时间,请提供tz = UTC。另一方面,Naive datetime将始终被解释为本地时间(操作系统的tz设置)。而且,在Python 3.9中,您可以使用zoneinfo来处理时区 - 不需要担心本地化与替换。 - FObersteiner

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