关于python:datetime.replace是否从根本上被打破了?

Is datetime.replace fundamentally broken?

本问题已经有最佳答案,请猛点这里访问。

将时区天真日期时间转换为特定时区会产生完全错误的结果。

1
2
3
import dateutil as du
import pytz    
du.parser.parse('2017-05-31T15:00:00').replace(tzinfo=pytz.timezone('Europe/London')).isoformat()

返回一分钟而不是一小时的偏移量与UTC相比

1
'2017-05-31T15:00:00-00:01'

我之前看到过一些日期时间的特点,但这一点令人叹为观止。


这里的主要问题是您使用pytz时区。 pytz区域不遵循tzinfo接口,不能简单地附加到datetime对象(通过构造函数或通过replace)。如果您想使用pytz时区,则应使用带有天真datetimepytz.timezone.localize。如果datetime已经知道时区,则可以使用datetime.astimezone在区域之间进行转换。

1
2
3
4
5
6
7
8
from dateutil import parser
import pytz

LON = pytz.timezone('Europe/London')
dt = parser.parse('2017-05-31T15:00:00')
dt = LON.localize(dt)

print(dt)   # 2017-05-31 15:00:00+01:00

这是因为pytz的接口使用localize将静态时区附加到datetime。出于同样的原因,如果对现在已本地化的datetime对象进行算术运算,它可能会产生类似的不正确结果,您必须使用pytz.timezone.normalize来修复它。这样做的原因在于,从历史上看,使用Pythonic tzinfo接口处理模糊日期时间是不可能的,这种接口在Python 3.6中使用PEP 495进行了更改,使得pytz的解决方法变得不那么必要了。

如果您想使用replace或构造函数将tzinfo传递给datetime,或者您希望使用pythonic接口,dateutil的时区套件实现符合PEP 495的tzinfo接口。使用dateutil区域的等价物是:

1
2
3
4
5
6
7
from dateutil import parser
from dateutil import tz

LON = tz.gettz('Europe/London')
dt = parser.parse('2017-05-31T15:00:00').replace(tzinfo=LON)

print(dt)   # 2017-05-31 15:00:00+01:00

我经常使用带有tzinfo对象的replace()运气不好。然而,我发现这个结构是可靠的:

码:

1
2
def naive_to_aware(ts, tz):
    return tz.localize(ts)

来自评论的更新:

来自(pytz DOCS)

Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.

It is safe for timezones without daylight saving transitions though, such as UTC

所以这不仅仅是运气不好,对于带有DST的时区的pytz对象来说也是有问题的。

测试代码:

1
2
3
4
5
import dateutil as du
import pytz

print(naive_to_aware(du.parser.parse('2017-05-31T15:00:00'),
                     pytz.timezone('Europe/London')).isoformat())

结果:

1
2017-05-31T15:00:00+01:00