创建时区感知日期时间对象的两种方法 (Django)。七分钟的差异?

Two ways to create timezone aware datetime objects (Django). Seven minutes difference?

到目前为止,我认为创建时区感知日期时间的两种方法是相同的。

但他们不是:

import datetime

from django.utils.timezone import make_aware, get_current_timezone

make_aware(datetime.datetime(1999, 1, 1, 0, 0, 0), get_current_timezone())

datetime.datetime(1999, 1, 1, 0, 0, 0, tzinfo=get_current_timezone())
datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)

datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' LMT+0:53:00 STD>)

在 Django Admin GUI 中,第二种方式创建了这个(德语日期格式 dd.mm.YYYY):

01.01.1999 00:07:00

为什么我用这个会有7分钟的差异:

datetime.datetime(1999, 1, 1, 0, 0, 0, tzinfo=get_current_timezone())

这发生在依赖于 pytz 库的 Django 3.2 及更低版本上。在 Django 4 中(除非您启用设置以使用已弃用的库),您给出的两个示例的输出是相同的。

在 Django 3.2 及以下版本中,差异的产生是因为本地化时间是以两种不同的方式构建的。使用 make_aware 时,它是通过 pytz 时区实例上的 calling the localize() 方法完成的。在第二个版本中,它是通过将 tzinfo 对象直接传递给 datetime 构造函数来完成的。

两者的区别在this blog post中有很好的说明:

The biggest mistake people make with pytz is simply attaching its time zones to the constructor, since that is the standard way to add a time zone to a datetime in Python. If you try and do that, the best case scenario is that you'll get something obviously absurd:

import pytz
from datetime import datetime

NYC = pytz.timezone('America/New_York')
dt = datetime(2018, 2, 14, 12, tzinfo=NYC)
print(dt)
# 2018-02-14 12:00:00-04:56

Why is the time offset -04:56 and not -05:00? Because that was the local solar mean time in New York before standardized time zones were adopted, and is thus the first entry in the America/New_York time zone. Why did pytz return that? Because unlike the standard library's model of lazily-computed time zone information, pytz takes an eager calculation approach.

Whenever you construct an aware datetime from a naive one, you need to call the localize function on it:

dt = NYC.localize(datetime(2018, 2, 14, 12))
print(dt)
# 2018-02-14 12:00:00-05:00

与您的 Europe/Berlin 示例完全相同。 pytz 正在急切地获取其数据库中的第一个条目,这是 1983 年之前的太阳时间,即 53 minutes and 28 seconds ahead 格林威治标准时间 (GMT)。考虑到日期,这显然是不合适的 - 但 tzinfo 不知道您使用的日期,除非您将其传递给 localize().

这就是你们两种方法的区别。使用 make_aware 正确调用对象上的 localize()。但是,将 tzinfo 直接分配给 datetime 对象并不会导致 pytz 使用(错误的)时区信息,因为它只是该时区的第一个条目它的数据库。

pytz 文档 obliquely refers 对此也是如此:

This library only supports two ways of building a localized time. The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information)... The second way of building a localized time is by converting an existing localized time using the standard astimezone() method... Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.

Django dropped it in favour of Python's built-in zoneinfo module.

实际上正是由于 pytz 实现中的这些错误和其他几个错误

该博客的更多内容 post:

At the time of its creation, pytz was cleverly designed to optimize for performance and correctness, but with the changes introduced by PEP 495 and the performance improvements to dateutil, the reasons to use it are dwindling. ... The biggest reason to use dateutil over pytz is the fact that dateutil uses the standard interface and pytz doesn't, and as a result it is very easy to use pytz incorrectly.

pytz tzinfo 对象直接传递给 datetime 构造函数是不正确的。您必须在 tzinfo class 上调用 localize(),并将日期传递给它。在第二个示例中初始化日期时间的正确方法是:

> berlin = get_current_timezone()
> berlin.localize(datetime.datetime(1999, 1, 1, 0, 0, 0))
datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)

... 匹配 make_aware 产生的结果。