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

gue*_*tli 8 python django datetime django-timezone

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

但他们不是:

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())
Run Code Online (Sandbox Code Playgroud)
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>)
Run Code Online (Sandbox Code Playgroud)

在 Django 管理 GUI 中,第二种方式创建此(德语日期格式 dd.mm.YYYY):

01.01.1999 00:07:00
Run Code Online (Sandbox Code Playgroud)

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

datetime.datetime(1999, 1, 1, 0, 0, 0, tzinfo=get_current_timezone())
Run Code Online (Sandbox Code Playgroud)

sol*_*oke 10

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

\n

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

\n

这篇博文很好地说明了两者之间的区别:

\n
\n

人们在使用 pytz 时犯的最大错误就是简单地将其时区附加到构造函数,因为这是在 Python 中向日期时间添加时区的标准方法。如果你尝试这样做,最好的情况是你会得到一些明显荒谬的东西:

\n
import pytz\nfrom datetime import datetime\n\nNYC = pytz.timezone(\'America/New_York\')\ndt = datetime(2018, 2, 14, 12, tzinfo=NYC)\nprint(dt)\n# 2018-02-14 12:00:00-04:56\n
Run Code Online (Sandbox Code Playgroud)\n

为什么时间偏移是 -04:56 而不是 -05:00?因为那是采用标准化时区之前纽约当地的太阳平均时间,因此是该时America/New_York区的第一个条目。pytz 为什么返回这个?因为与标准库的延迟计算时区信息模型不同,pytz 采用急切的计算方法。

\n

每当你从一个简单的日期时间构造一个感知的日期时间时,你需要调用它的 localize 函数:

\n
dt = NYC.localize(datetime(2018, 2, 14, 12))\nprint(dt)\n# 2018-02-14 12:00:00-05:00\n
Run Code Online (Sandbox Code Playgroud)\n
\n

你的例子也发生了完全相同的事情Europe/Berlinpytz正在急切地获取其数据库中的第一个条目,该条目是 1983 年之前的太阳时,比格林威治标准时间 (GMT)早 53 分 28 秒。考虑到日期,这显然是不合适的 - 但tzinfo除非您将其传递给 ,否则它不知道您正在使用的日期localize()

\n

这就是您的两种方法之间的区别。make_aware正确使用localize()对象上的调用。然而,直接将 分配tzinfodatetime对象则不会,并且会导致pytz使用(错误的)时区信息,因为它只是数据库中该区域的第一个条目。

\n

pytz 文档也间接提到了这一点:

\n
\n

该库仅支持两种构建本地化时间的方法。第一种是使用 pytz 库提供的 localize() 方法。这用于本地化原始日期时间(没有时区信息的日期时间)...构建本地化时间的第二种方法是使用标准 astimezone() 方法转换现有的本地化时间...不幸的是使用标准日期时间构造函数 \xe2\x80\x98\xe2\x80\x99 不适用于许多时区的 pytz。

\n
\n

实际上,正是由于这些以及实现中的其他几个错误pytz,Django放弃了它,转而使用 Python 的内置zoneinfo模块

\n

该博客文章的更多内容:

\n
\n

在其创建时,pytz它被巧妙地设计为优化性能和正确性,但随着 PEP 495 引入的更改和 dateutil 的性能改进,使用它的理由正在减少。\n... 使用它的最大原因dateutil 优于 pytz 的事实是,dateutil 使用标准接口,而 pytz 不使用,因此很容易错误地使用 pytz。

\n
\n

pytz tzinfo对象直接传递给datetime构造函数是不正确的。你必须拜访localize()班级tzinfo,并传递日期。在第二个示例中初始化日期时间的正确方法是:

\n
> berlin = get_current_timezone()\n> berlin.localize(datetime.datetime(1999, 1, 1, 0, 0, 0))\ndatetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo \'Europe/Berlin\' CET+1:00:00 STD>)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n