在不直接修改 Django 包的情况下处理 Django Util Timezone Ambiguous Time Error
Handle Django Util Timezone Ambiguous Time Error without directly modifying the Django Package
我知道要修复 make_aware 我可以拦截异常并将其提前一个小时,但问题是 Django 运行 make_aware 的日期是在查询集中被发现,我没有在代码中调用函数,它是否发生在 Django 库代码中,我无法编辑,而不必在代码库的每个版本中包含修改后的版本。有解决办法吗?
请注意:再次说明,为了清楚起见,我不是手动调用 make_aware,它是 运行 在查询集中的对象上进行评估时
我遇到了同样的问题,解决方案似乎没有在任何地方记录,但我得到了一个只需几行代码就可以解决的问题。答案来自自定义 Django 数据库后端。我们要做的是扩展现有的 MySQL 数据库后端,并替换使 MySQL 日期时间时区感知的代码。
首先,让我描述一下我遇到的问题。 2021 年 11 月 6 日,PST 时区进行了 DST 转换,时钟在 1:59AM 后回滚到凌晨 1 点,而不是像正常情况下那样递增到凌晨 2 点。因此,任何具有 2021 年 11 月 6 日凌晨 1 点时间戳的内容对 Django 来说都是模棱两可的,因为它不确定它是回滚之前还是之后的凌晨 1 点(即“在正常情况下应该是凌晨 2 点”) .从 1:00:00 到 1:59:59.
凌晨 1 点的每一分每一秒都会出现此问题
我深入研究了错误并注意到问题出在以下代码中,在从数据库转换 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 数据库后端并用你自己的实现覆盖那个函数! (一开始我已经剧透了,但你还是应该表现出惊讶的样子,这样才能产生额外的戏剧效果)。
Django 2、3 和 4 的解决方案
- 扩展默认的 Django MySQL 数据库后端,并覆盖执行 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
.
因此,如果您正在为您的数据库使用连接字符串,您可以执行以下操作:
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 时间在冲突期间有效,因此在 summer/winter 时间转换的那一小时内,您可能会比实际时间晚一小时。如果在您的应用程序中该信息的准确性至关重要,您应该在覆盖 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 中应该没有问题,因为那里也有那个文档。
我知道要修复 make_aware 我可以拦截异常并将其提前一个小时,但问题是 Django 运行 make_aware 的日期是在查询集中被发现,我没有在代码中调用函数,它是否发生在 Django 库代码中,我无法编辑,而不必在代码库的每个版本中包含修改后的版本。有解决办法吗?
请注意:再次说明,为了清楚起见,我不是手动调用 make_aware,它是 运行 在查询集中的对象上进行评估时
我遇到了同样的问题,解决方案似乎没有在任何地方记录,但我得到了一个只需几行代码就可以解决的问题。答案来自自定义 Django 数据库后端。我们要做的是扩展现有的 MySQL 数据库后端,并替换使 MySQL 日期时间时区感知的代码。
首先,让我描述一下我遇到的问题。 2021 年 11 月 6 日,PST 时区进行了 DST 转换,时钟在 1:59AM 后回滚到凌晨 1 点,而不是像正常情况下那样递增到凌晨 2 点。因此,任何具有 2021 年 11 月 6 日凌晨 1 点时间戳的内容对 Django 来说都是模棱两可的,因为它不确定它是回滚之前还是之后的凌晨 1 点(即“在正常情况下应该是凌晨 2 点”) .从 1:00:00 到 1:59:59.
凌晨 1 点的每一分每一秒都会出现此问题我深入研究了错误并注意到问题出在以下代码中,在从数据库转换 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 数据库后端并用你自己的实现覆盖那个函数! (一开始我已经剧透了,但你还是应该表现出惊讶的样子,这样才能产生额外的戏剧效果)。
Django 2、3 和 4 的解决方案
- 扩展默认的 Django MySQL 数据库后端,并覆盖执行 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
.
因此,如果您正在为您的数据库使用连接字符串,您可以执行以下操作:
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 时间在冲突期间有效,因此在 summer/winter 时间转换的那一小时内,您可能会比实际时间晚一小时。如果在您的应用程序中该信息的准确性至关重要,您应该在覆盖 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 中应该没有问题,因为那里也有那个文档。