为啥Django时区设置会影响纪元时间?

Posted

技术标签:

【中文标题】为啥Django时区设置会影响纪元时间?【英文标题】:Why does the Django time zone setting effect epoch time?为什么Django时区设置会影响纪元时间? 【发布时间】:2013-06-12 05:11:33 【问题描述】:

我有一个小型 Django 项目,可以将 MongoDB 中的数据转储导入 mysql。在这些 Mongo 转储中是存储在纪元时间中的日期。无论时区如何,我都希望纪元时间相同,但我看到的是 Django TIME_ZONE 设置对 MySQL 中创建的数据有影响。

我一直在使用 MySQL UNIX_TIMESTAMP 函数测试我的数据库输出。如果我插入一个纪元为1371131402880(包括毫秒)的日期,我将我的时区设置为'America/New_York',UNIX_TIMESTAMP 给我1371131402,这是相同的纪元时间,不包括毫秒。但是,如果我将时区设置为 'America/Chicago',我会得到 1371127802

这是我将纪元时间转换为 Python datetime 对象的代码,

from datetime import datetime
from django.utils.timezone import utc

secs = float(epochtime) / 1000.0
dt = datetime.fromtimestamp(secs)

我试图通过在 datetime 对象上放置一个明确的时区来解决此问题,

# epoch time is in UTC by default
dt = dt.replace(tzinfo=utc)

PythonFiddle for the code

我已经单独测试了这个 Python 代码,它给了我预期的结果。但是,通过 Django 模型 DateTimeField 字段将这些对象插入 MySQL 后,它并没有给出正确的结果。

这是我的 MySQL 查询,

SELECT id, `date`, UNIX_TIMESTAMP(`date`) FROM table

我通过将此查询结果中的 unix 时间戳列与 MongoDB JSON 转储进行比较来测试这一点,以查看时代是否匹配。

这里到底发生了什么?为什么时区会对纪元时间产生影响?

仅供参考,我使用的是 Django 1.5.1 和 MySQL-python 1.2.4。我还将 Django USE_TZ 标志设置为 true

【问题讨论】:

可能 MySQL 服务器的时区与您的应用程序的时区不匹配。问题在于 MySQL 将 UNIX_TIMESTAMP 的参数解释为其私有时区中的本地时间。 为获得最佳结果,请始终在所有 MySQL 连接上设置 SET TIME_ZONE="+00:00",并将每个日期解释为 UTC。 术语 - “纪元”只是我们设置零的参考日期。大多数时间是 1970 年 1 月 1 日 UTC,但这并不意味着基于此日期的整数应称为“纪元时间”。其他系统中使用了许多其他纪元日期。 @Celada - 这对我来说似乎不对,但我不是 MySQL 专家。你能用一些参考链接备份它吗?如果是这样,请输入答案。我所知道的关于这种格式的日期的一切都表明 UTC 始终是参考时区。它似乎更有可能在某处更改类型或调用引入本地时区的函数。 好的,我刚刚通读了these mysql docs,现在我明白了。 Celada 确实对该函数说“参数”,这是一个包含格式化日期时间值的字符串。是的 - 它将TIME_ZONE 设置应用于此值是有道理的。我的观点是基础整数然后转换为UTC。因此,当表示为整数时,无论本地时区如何,UTC 始终是参考点。我只是陷入了措辞。应用。 【参考方案1】:

我不是 python 或 Django 大师,所以也许有人能比我回答得更好。但无论如何我都会猜测一下。

您说您将其存储在 Django DateTimeField 中,根据 the documents you referenced,将其存储为 Python datetime

看the docs for datetime,我认为关键是理解“naive”和“aware”值的区别。

然后进一步研究,我遇到了this excellent reference。请务必阅读第二部分“朴素且有意识的日期时间对象”。这为 Django 控制了多少提供了一些背景信息。基本上,通过设置USE_TZ = true,您要求 Django 使用 aware 日期时间而不是 naive 日期时间。

然后我回头看了你的问题。您说您正在执行以下操作:

dt = datetime.fromtimestamp(secs)
dt = dt.replace(tzinfo=utc)

查看fromtimestamp函数文档,发现了这段文字:

如果可选参数tzNone或未指定,则timestamp转换为平台的本地日期和时间,返回的datetime对象是naive。

所以我认为你可以这样做:

dt = datetime.fromtimestamp(secs, tz=utc)

然后,在该函数的正下方,文档显示utcfromtimestamp 函数,所以它可能应该是:

dt = datetime.utcfromtimestamp(secs)

我对 python 的了解还不够,无法知道它们是否等效,但您可以尝试看看是否有区别。

希望其中之一会有所作为。如果没有,请告诉我。我非常熟悉 javascript 和 .Net 中的日期/时间,但我总是对这些细微差别如何在 Python 等其他平台中发挥不同的作用感兴趣。

更新

关于问题的 MySQL 部分,请查看this fiddle。

CREATE TABLE foo (`date` DATETIME);
INSERT INTO foo (`date`) VALUES (FROM_UNIXTIME(1371131402));

SET TIME_ZONE="+00:00";
select `date`, UNIX_TIMESTAMP(`date`) from foo;

SET TIME_ZONE="+01:00";
select `date`, UNIX_TIMESTAMP(`date`) from foo;

结果:

DATE                           UNIX_TIMESTAMP(`DATE`)
June, 13 2013 13:50:02+0000    1371131402
June, 13 2013 13:50:02+0000    1371127802

UNIX_TIMESTAMP 函数的行为似乎确实受到 MySQL TIME_ZONE 设置的影响。这并不奇怪,因为它在文档中。令人惊讶的是,无论设置如何,datetime 的字符串输出都具有相同的 UTC 值。

这就是我认为正在发生的事情。在UNIX_TIMESTAMP 函数的文档中,它说:

date 可以是DATE 字符串、DATETIME 字符串、TIMESTAMP 或格式为YYMMDDYYYYMMDD 的数字。

请注意,它并不是说它可以是DATETIME - 它说它可以是DATETIME 字符串。所以我认为实际值在传递给函数之前被隐式转换为字符串。

所以现在看看this updated fiddle 显式转换。

SET TIME_ZONE="+00:00";
select `date`, convert(`date`, char), UNIX_TIMESTAMP(convert(`date`, char)) from foo;

SET TIME_ZONE="+01:00";
select `date`, convert(`date`, char), UNIX_TIMESTAMP(convert(`date`, char)) from foo;

结果:

DATE                           CONVERT(`DATE`, CHAR)  UNIX_TIMESTAMP(CONVERT(`DATE`, CHAR))
June, 13 2013 13:50:02+0000    2013-06-13 13:50:02    1371131402
June, 13 2013 13:50:02+0000    2013-06-13 13:50:02    1371127802

您可以看到,当它转换为字符数据时,它会去除偏移量。因此,当然,现在当UNIX_TIMESTAMP 将此值作为输入时,它假设本地时区设置并因此获得不同的 UTC 时间戳是有意义的。

不确定这是否对您有帮助。您需要更深入地了解 Django 是如何调用 MySQL 进行读取和写入的。它真的使用UNIX_TIMESTAMP 函数吗?或者这正是你在测试中所做的?

【讨论】:

utcfromtimestamp() 是创建表示 UTC 时间的天真日期时间对象的正确函数。仅仅fromtimestamp() 会在本地时区返回一个幼稚的日期时间(在大多数情况下是错误的,除非您想立即在本地系统上显示它)。 感谢马特。我之前实际上是在玩utcfromtimestamp,但运气不佳。它正在将 UTC 时间插入数据库,而 unix 时间戳甚至更远。我稍后会尝试发布更多信息。另外,我将不得不尝试一下您对secs, tz=utc 的推荐,至少这将为我节省一行代码:) 是的,请显示您使用UNIX_TIMESTAMP 函数测试的代码。如 Celada 所述,该代码可能受时区影响。 UNIX_TIMESTAMP 是一个 MySQL 函数,但我将添加查询。我还添加了一个 PythonFiddle @pythonfiddle.com/tests-for-epoch-issue 呃,我错过了关于字符串转换的那部分,这真的很不直观。写得很好,这实际上是如何工作的,我的确认测试可能有缺陷。

以上是关于为啥Django时区设置会影响纪元时间?的主要内容,如果未能解决你的问题,请参考以下文章

Django 时间与时区设置问题

Django 时间与时区设置问题

django 默认时区设置

Django如何设置默认时区?

将 UTC 日期和时间转换为 UNIX 时间会给出错误的(时区不同)值。为啥?

关于django的时区设置