Django 在 DATABASES 中设置 TIME_ZONE 对“日期”查找没有影响

Vic*_*r T 5 python django django-database django-settings django-1.11

2019 年 4 月 8 日更新

\n\n

这是 django<=2.2 的一个已知错误,自此PR起已修复

\n\n

===================================

\n\n

(我们假设mysql后端)

\n\n

我可以在 中设置TIME_ZONE多次settings.py,一次用于全局 django 应用程序,一次用于每个数据库(请参阅https://docs.djangoproject.com/en/1.11/ref/settings/#time-zone (ref1))

\n\n

典型用法是用于日期时间不以 UTC 存储的旧数据库。

\n\n

没有日期查找

\n\n

查询我的数据库会考虑此设置,例如:

\n\n

settings.py

\n\n
USE_TZ = True\nTIME_ZONE = \'Europe/Paris\' # tz1\nDATABASES = {\n    \'legacy\': {\n        \'ENGINE\': \'django.db.backends.mysql\',\n        \'OPTIONS\': {\n            \'read_default_file\': \'....cnf\',\n        },\n        \'TIME_ZONE\': \'Europe/Paris\', # tz2\n    },\n    \'default\' : {\n        \'ENGINE\': \'django.db.backends.mysql\',\n        \'OPTIONS\': {\n            \'read_default_file\': \'....cnf\',\n        },\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在里面manage.py shell

\n\n
>>> dt = timezone.make_aware(datetime.datetime(2017, 7, 6, 20, 50))\n>>> dt\ndatetime.datetime(2017, 7, 6, 20, 50, tzinfo=<DstTzInfo \'Europe/Paris\' CEST+2:00:00 DST>)\n>>> MyModel.objects.filter(my_datetime_field=dt).exists()\nTrue\n
Run Code Online (Sandbox Code Playgroud)\n\n

这有效,因为我的数据库读取\'2017-07-06 20:50:00\'

\n\n

带日期查找功能

\n\n

相关文档https://docs.djangoproject.com/en/1.11/ref/models/querysets/#date (ref2)

\n\n

但这是行不通的,虽然从逻辑上来说应该如此

\n\n
>>> MyModel.objects.filter(my_datetime_field__date=dt.date()).exists()\nFalse*\n
Run Code Online (Sandbox Code Playgroud)\n\n

DEBUG 的相关 SQL 查询是:

\n\n
SELECT (1) AS `a` FROM `my_model` WHERE DATE(CONVERT_TZ(`my_model`.`my_datetime_field`, \'UTC\', \'Europe/Paris\')) = \'2017-07-06\' LIMIT 1;\n
Run Code Online (Sandbox Code Playgroud)\n\n

(*) 请注意,我还没有在 MySQL 中填写时区表,因此结果应该是True这种情况,但可能False接近午夜。\n相关文档是https://dev.mysql.com/doc/refman /5.7/en/mysql-tzinfo-to-sql.html

\n\n

有两件事是错误的。首先,转换应该是从巴黎到巴黎,而不是UTC到巴黎。转换应该从数据库时区 tz2 到 django 应用程序一 tz1。

\n\n

确实来自 ref1 :

\n\n
\n

当 USE_TZ 为 True 并且数据库不支持时区(例如 SQLite、MySQL、Oracle)时,如果设置了此选项,Django 将根据本地时间读取和写入日期时间;如果设置了,则以 UTC 读取和写入日期时间\x80\x99t。

\n
\n\n

和参考2:

\n\n
\n

当 USE_TZ 为 True 时,字段在过滤之前会转换为当前时区

\n
\n\n

其次,当tz1 == tz2时,应该不需要使用CONVERT_TZ,查询将在MySQL中没有时区表的情况下工作。

\n\n

显式查询是:

\n\n
mysql> SELECT (1) AS `a` FROM `my_model` WHERE `my_model`.`my_datetime_field` = \'2017-07-06 20:50:00\' LIMIT 1;\n+---+\n| a |\n+---+\n| 1 |\n+---+\n1 row in set (0.00 sec)\n\nmysql> SELECT (1) AS `a` FROM `my_model` WHERE DATE(`my_model`.`my_datetime_field`) = \'2017-07-06\' LIMIT 1;\n+---+\n| a |\n+---+\n| 1 |\n+---+\n1 row in set (0.00 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

为什么\'UTC\'出现在查询中?不应该吗\'Europe/Paris\'

\n\n

我是否误解了文档中的某些内容,或者这是一个错误?

\n\n

谢谢。

\n\n

编辑:我的系统 tz 不是 UTC,如果这有帮助的话

\n\n
mysql> SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;\n+--------------------+---------------------+--------------------+\n| @@global.time_zone | @@session.time_zone | @@system_time_zone |\n+--------------------+---------------------+--------------------+\n| SYSTEM             | SYSTEM              | CEST               |\n+--------------------+---------------------+--------------------+\n
Run Code Online (Sandbox Code Playgroud)\n

Vic*_*r T 3

这种行为是可以预料的,因为Django 的代码如下

django/db/backends/mysql/operations.py

def _convert_field_to_tz(self, field_name, tzname):
    if settings.USE_TZ:
        field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name
        params = [tzname]
    else:
        params = []
    return field_name, params
Run Code Online (Sandbox Code Playgroud)

为了 的利益,数据库特定时区被忽略'UTC'

对于它的价值,我已经在 djangoproject 上开了一张票,它是相关的拉取请求

将其替换为:

def _convert_field_to_tz(self, field_name, tzname):
    if settings.USE_TZ and self.connection.timezone_name != tzname:
        field_name = "CONVERT_TZ(%s, '%s', %%s)" % (field_name, self.connection.timezone_name)
        params = [tzname]
    else:
        params = []
    return field_name, params
Run Code Online (Sandbox Code Playgroud)