我遇到了同样的问题,并且似乎没有在任何地方记录解决方案,但我得到了一个可以在几行代码中运行的解决方案。答案来自自定义 Django DB 后端。我们要做的是扩展现有的 MySQL DB 后端,并替换使 MySQL 日期时间时区感知的代码。
首先,让我描述一下我面临的问题。 2021 年 11 月 6 日,PST 时区发生了 DST 转换,时钟在凌晨 1:59 之后回滚到凌晨 1 点,而不是像正常情况下那样递增到凌晨 2 点。结果,任何带有 2021 年 11 月 6 日凌晨 1 点时间戳的东西对 Django 来说都是模棱两可的,因为它不确定是回滚之前还是之后的凌晨 1 点(即“在正常情况下应该是凌晨 2 点”) .从凌晨 1:00:00 到 1:59:59 的每一分每一秒都会出现此问题。
我深入研究了错误并注意到问题出在以下代码中,该代码在从数据库转换 DateTimeField 值时由 Django ORM 调用:
# in file django.db.backends.mysql.operations
class DatabaseOperations(BaseDatabaseOperations):
# other code here
def convert_datetimefield_value(self, value, expression, connection):
if value is not None:
value = timezone.make_aware(value, self.connection.timezone)
return value
特别是,问题在于以下行:
value = timezone.make_aware(value, self.connection.timezone)
由于它没有将is_dst 参数传递给timezone.make_aware 调用,因此在某些条件下我们必然会得到AmbiguousTimeError。一种简单的解决方案是将is_dst=True 传递给调用(或False,选择是任意的),但这意味着修改Django 的代码......还是会这样?您不需要修改 Django 的代码,您可以简单地扩展默认的 MySQL DB 后端并用您自己的实现覆盖该功能! (我一开始就已经剧透了,但你还是应该表现得感到惊讶,以获得额外的戏剧效果)。
Django 2、3 和 4 的解决方案
- 扩展默认的 Django MySQL DB 后端,并覆盖执行 DateTime 字段转换的代码部分。为此,请在 Django 项目的根目录中创建一个新包,并在其中包含一个名为
base.py 的文件。在该文件中,我们的自定义 Django DB 后端代码将生效。我调用了我的数据库后端illyadbengine,所以我将拥有以下文件夹结构:
my-django-app
my-django-app
- settings.py
illyadbengine
- __init__.py
- base.py
并将以下代码放入base.py:
"""
A custom MySQL DB engine that solves the ambiguous time error during DST transition, by assuming that it is DST
in case of an error.
"""
from django.db.backends.mysql import base
from django.utils import timezone
from django.db.backends.mysql.operations import DatabaseOperations
from pytz.exceptions import AmbiguousTimeError
class MySQLOperationsWithDSTConflictResolutionOperations(DatabaseOperations):
def convert_datetimefield_value(self, value, expression, connection):
try:
# attempt at performing the default conversion, and only fallback to DST conflict resolution if it fails
return super().convert_datetimefield_value(value, expression, connection)
except AmbiguousTimeError:
if value is not None:
# NOTE: this can cause an error by 1 hour, since it always assumes DST timezone in case of conflict
# if a precise time conversion is important for your use case, you should write that custom logic here
value = timezone.make_aware(value, self.connection.timezone, is_dst=True)
return value
class DatabaseWrapper(base.DatabaseWrapper):
ops_class = MySQLOperationsWithDSTConflictResolutionOperations
- 在
settings.py 中指示您的数据库使用您的自定义引擎。您可以通过多种方式执行此操作,具体取决于您在设置中定义数据库连接的方式。您正在做的是将数据库连接的ENGINE 参数设置为illyadbengine。
因此,如果您为 DB 使用连接字符串,您可以执行以下操作:
import environ
env = environ.Env()
DATABASES = {
"default": env.db_url("DATABASE_URL"),
}
DATABASES["default"]["ENGINE"] = "illyadbengine"
或者,如果您使用内联 dict 定义连接元素,您将拥有如下内容:
DATABASES = {
'default': {
# other settings here
'ENGINE': 'illyadbengine',
}
}
请注意,此解决方案假定 DST 时间在冲突期间有效,因此在夏季/冬季时间转换期间,您可能会比实际时间晚一个小时。如果该信息在您的应用程序中至关重要,那么您应该在覆盖 convert_datetimefield_value 的实现中包含该信息的逻辑。
此解决方案适用于 Django 2、Django 3 和 Django 4。
我找到了一些documentation on extending a database engine in Django's 3 Documentation,虽然它在 Django 2 中不存在,但我自己在 Django 2.2 中对其进行了测试,它可以工作。您在 Django 4 中应该没有问题,因为那里也有该文档。