【问题标题】:Django date comparison different on server服务器上的 Django 日期比较不同
【发布时间】:2015-08-13 15:59:23
【问题描述】:

我刚刚意识到我们的生产设置中存在一些非常奇怪的日期比较行为,我不明白。

上市模式

title (CharField)
from_date (DateTimeField)
to_date (DateTimeField)

查询集

now = arrow.now()
Listing.objects.filter(to_date__gte=now.datetime)

设置

  • Python 2.7
  • Django 1.7
  • SQLite / PostgreSQL
  • Ubuntu

问题

当列表对象的 to_date 设置为比现在早一小时时,无论是在开发服务器上还是在生产服务器上的 shell 上,查询集都会返回正确的结果(相关列表不包括在内)。但是,在生产服务器上,查询继续包含列表(未启用缓存)。我已经确定,如果我将列表的日期设置为前一天,则它会被正确排除,从而似乎只比较了几天(我什至将时间设置为 00:00 以排除时区问题,但这仍然包括列表- 如前所述,仅更改工作日)。请问为什么会出现这种情况,我该如何解决?

其他信息

  1. 我已使用“date”命令确认服务器时区与 settings.py 中指定的时区一致
  2. 我已确认 settings.py 中的 USE_TZ = True 并且 TIME_ZONE 已正确设置。
  3. 我已经登录到服务器上的 PostgreSQL 终端并运行 now 命令 - 这返回了与系统时区和 settings.py 中指定的时区相同的时区
  4. 我已确认 postgresql 中的 to_date 字段是“带有时区字段的时间戳”
  5. 传递给 db:WHERE ( "directory_listing"."to_date" >= 2015-08-22 14:01:56.663072) 的实际 sql 提取

【问题讨论】:

  • 系统上的时间是否正确?用“日期”命令检查。
  • @ChrisMontanaro 是的,我确实检查了日期,并且精确到分钟。我还在服务器上的 shell 上运行了查询,它返回正确的结果
  • 您可以检查/比较实际针对数据库抛出的 sql: print Listing.objects.filter(to_date__gte=now.datetime).query 这应该让您了解正在发生的事情。
  • 谢谢 - 这样做了 - 它正在使用预期的日期。无论如何,查询总是在 shell 中返回正确的结果
  • 如果两个查询相同,则查看(postgres?)查询日志。您应该在那里看到完整的 UTC(或其他时区)日期。在您的项目 setting.py 中仔细检查您的 USE_TZ 和 TIME_ZONE。或者:在视图中显示您的请求时区:docs.djangoproject.com/en/1.7/topics/i18n/timezones/…

标签: python django postgresql


【解决方案1】:

我相信您所看到的差异的关键在于您使用了两种不同的数据库引擎,一种在您的开发环境中,另一种在您的生产环境中,它们对日期和时间的处理非常不同。

SQLite does not have a specific date/time data type。日期和时间可以以文本格式、浮点数或整数存储,并且使用各种函数将输入数据转换为这些形式之一以用于存储或比较目的。此外,它没有任何特定的“带时区的日期时间”数据类型,甚至不完全支持任意时区之间的时区转换。

Postgresql has comprehensive support 用于带和不带时区的日期、时间、间隔和时间戳。它还具有专门定义的运算符,用于将它们相互比较。

我怀疑 Django 在使用 SQLite 作为数据库后端时使用的是文本表单。如果是这种情况,那么给出这样的 WHERE 子句:

WHERE (to_date >= '2015-08-22 14:01:56.663072')

它将使用纯文本比较 - 这似乎可以正常工作,因为您可以通过这种方式比较两个 ISO8601 字符串(只要您不关心时区)。 p>

在您的生产环境中,您使用的是带有“带时区的时间戳”类型的 postgresql。这意味着存储在数据库中的数据存储时附加了时区信息。

在这种情况下,有几个时区在起作用:

  • 你的 Django / python 环境的时区
  • 运行客户端 (Django) 软件的机器上的系统时区
  • 数据库的时区(通过timezone 配置参数配置,您可以在psql 提示符下使用命令SHOW timezone; 检查)。
  • 无论何时区信息都嵌入到与数据库之间发送的字符串中。

根据您的 cmets,您至少检查了其中一些设置。

一种可能性是,当记录插入数据库时​​,to_date 字段包含一个时区,它与 postgresql 数据库设置使用的时区不同。例如考虑一下:

ispdb_t2=> select '2015-08-22 00:00:00-10:00'::timestamp with time zone >= '2015-08-22 14:01:56.663072';
 ?column?
----------
 t
(1 row)

它看起来好像认为2015-08-22 00:00:00 是 >= 2015-08-22 14:01:56.663072。这里实际发生的是左侧的时间戳具有时区-10,而右侧的时间戳没有附加任何时区,因此它在数据库会话的时区中进行解释(在我的情况下是'澳大利亚/维多利亚',目前 UTC+10 小时)。

要验证这是否是您的情况的一个因素,我建议采取以下步骤:

  1. 验证 'timezone' postgresql 配置参数的值。请注意,它可以在每个会话中设置 - 因此您还应该确认 Django 框架是否在连接后发送SET timezone 命令

  2. 检查用于在数据库中插入值的 SQL。确认时间值是否包含嵌入的时区。

  3. 确认在查询中发送的文字值,例如在 WHERE 子句中。确认它们是否包含嵌入的时区(根据您提供的信息,它们看起来不像)。

  4. 获取服务器正在执行的 SQL 的准确文本,包括任何占位符值。尝试使用 psql 命令行工具直接在数据库中运行相同的查询。这样做时,请确保将 timezone 设置为与 Django 所做的相同的值(如果有)。然后,您可以尝试更改各种值,直到获得有效的查询,然后从那里向后工作以使您的 Django 代码正常工作。

提示:您可以通过将配置参数log_statement 设置为all 来让postgresql 记录所有语句。

您可能不想在您的实际生产系统上进行此类测试。我建议您更改开发系统的配置,使其使用 Postgresql 作为后端,然后重现您在那里遇到的问题。

这给我带来了我的最后一个建议:尽可能在您的开发系统中使用与生产中使用的相同的数据库系统。即使允许框架对应用程序开发人员隐藏数据库的详细信息,不同 SQL 数据库的操作方式也常常存在很大差异。

【讨论】:

  • 我已将赏金授予您,因为它今天到期,您提供了最清晰、最全面的答案。我将在接下来的几天内完成您推荐的所有检查和测试,如果它解决了我的问题,我会将您的答案标记为已接受。对您的提示表示敬意,因为 dev/prod 没有不同的数据库 - 我不会再犯这个错误了。
  • 参考您上面的第 2 点 - 插入的项目是否应该有时区?我的理解是他们应该,但你在第 3 点的陈述使他们看起来不应该。顺便说一句,我在 python 中的 USE_TZ 参数是 True
  • @RunLoop 这取决于您对时间戳的操作。如果您的应用程序应该处理源自不同时区的时间戳,并且能够比较它们,那么您应该使用尊重时区的数据类型,并在输入数据中包含时区(包括任何 where 子句)。但是,您需要保持一致!回复:第 3 点,我的意思是您问题中的 WHERE 子句不包括时区,所以我假设您的应用程序不包括它们。抱歉,这句话措辞不当。
  • 在本地和 dev 上花费了几个小时测试、记录原始 sql、进行 shell 测试等无济于事后,我终于重新启动了 gunicorn,并返回了正确的结果。似乎 django 或 postgresql 正在做一些 cahcing(虽然我没有启用任何)
【解决方案2】:

arrow.now().datetime 添加一些 tzdata。我的想法是您在不同的环境中设置了不同的时区。

>>> datetime.datetime.now()
datetime.datetime(2015, 8, 13, 11, 31, 39, 443054)
>>> arrow.now().datetime
datetime.datetime(2015, 8, 13, 11, 31, 39, 445013, tzinfo=tzlocal())

在 Ubuntu 中重新配置您的时区:

dpkg-reconfigure tzdata

还要检查您的 TIME_ZONE 设置。

【讨论】:

  • 感谢您的回答,但我已检查服务器上的时区和 settings.py 中的时区是否相同。正如我所指出的,在服务器上的 shell 中运行测试会给出正确的结果,在我看来,这可以确认时区设置是否匹配。但是,为了仔细检查,我将 to_date 的时间组件设置为 00:00 以避免时区差异,但这仍然没有效果。只有更改日期,查询才会返回正确的结果
  • 只有将日期作为字符串而不是实际的日期时间对象进行比较时才有意义。曾经发生在我身上,sqlite 中的日期字段是一个文本字段——你能把列出列数据类型的命令的输出粘贴到这里吗?
  • 你有一个未定义的时间戳。 postgresql.org/docs/9.1/static/datatype-datetime.html
  • @chromano 我已经确认这是一个“带时区的时间戳”字段
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-12
  • 2019-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多