数据库中的 Django 设置 TIME_ZONE 对 'date' 查找没有影响

Django setting TIME_ZONE in DATABASES has no effect with 'date' lookup

2019 年 4 月 8 日更新

这是 django<=2.2 的已知错误,已修复 PR

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

(我们假设 mysql 后端)

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

典型用法是用于日期时间未以 UTC 格式存储的遗留数据库。

没有日期查询

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

settings.py

USE_TZ = True
TIME_ZONE = 'Europe/Paris' # tz1
DATABASES = {
    'legacy': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '....cnf',
        },
        'TIME_ZONE': 'Europe/Paris', # tz2
    },
    'default' : {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '....cnf',
        },
    }
}

manage.py shell

>>> dt = timezone.make_aware(datetime.datetime(2017, 7, 6, 20, 50))
>>> dt
datetime.datetime(2017, 7, 6, 20, 50, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)
>>> MyModel.objects.filter(my_datetime_field=dt).exists()
True

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

带日期查找

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

但这行不通,而逻辑上应该

>>> MyModel.objects.filter(my_datetime_field__date=dt.date()).exists()
False*

来自 DEBUG 的相关 SQL 查询是:

SELECT (1) AS `a` FROM `my_model` WHERE DATE(CONVERT_TZ(`my_model`.`my_datetime_field`, 'UTC', 'Europe/Paris')) = '2017-07-06' LIMIT 1;

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

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

确实来自 ref1 :

When USE_TZ is True and the database doesn’t support time zones (e.g. SQLite, MySQL, Oracle), Django reads and writes datetimes in local time according to this option if it is set and in UTC if it isn’t.

和 ref2 :

When USE_TZ is True, fields are converted to the current time zone before filtering

其次,当 tz1 == tz2 时,不需要使用 CONVERT_TZ 并且查询将在 MySQL.

中没有时区 tables 的情况下工作

显式查询是:

mysql> SELECT (1) AS `a` FROM `my_model` WHERE `my_model`.`my_datetime_field` = '2017-07-06 20:50:00' LIMIT 1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> SELECT (1) AS `a` FROM `my_model` WHERE DATE(`my_model`.`my_datetime_field`) = '2017-07-06' LIMIT 1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

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

我是不是误解了文档中的内容,还是错误?

谢谢。

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

mysql> SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;
+--------------------+---------------------+--------------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM             | SYSTEM              | CEST               |
+--------------------+---------------------+--------------------+

来自documentation-

When USE_TZ is True and the database doesn’t support time zones (e.g. SQLite, MySQL, Oracle), Django reads and writes datetimes in local time according to this option if it is set and in UTC if it isn’t.

所以,看起来(从您的 "I see" 代码)您的服务器时区(= db tz = 本地时间,因为遗留)是 UTC。因此,由于您的设置 (USE_TZ=True & TZ=Paris),正在从 UTC 转换为 Paris TZ。

因此,您误解了文档,这不是错误。

此行为是预期的,因为 Django's code reads

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

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

为了它的价值,我开了一个 ticket on djangoproject and it's related pull request

替换为:

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