4

2019 年 4 月 8 日更新

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

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

(我们假设 mysql 后端)

我可以在 中设置TIME_ZONE多次settings.py,一个用于全局 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 中的时区表,因此结果应该是True在这种情况下,但可能False接近午夜。相关文档是https://dev.mysql.com/doc/refman/5.7/en/mysql-tzinfo-to-sql.html

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

确实来自 ref1 :

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

和 ref2 :

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

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

显式查询是:

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               |
+--------------------+---------------------+--------------------+
4

2 回答 2

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

数据库特定时区被忽略以谋取利益'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
于 2017-07-07T13:41:35.317 回答
1

文档-

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

因此,看起来(从您的“我看到”代码中)您的服务器时区(= db tz = 本地时间,因为旧版)是 UTC。因此,由于您的设置 (USE_TZ=True & TZ=Paris),正在发生从 UTC 到巴黎 TZ 的转换。

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

于 2017-07-06T15:19:30.080 回答