【问题标题】:MySQL convert timestamp to timezone for search queryMySQL将时间戳转换为时区以进行搜索查询
【发布时间】:2019-12-16 11:25:51
【问题描述】:

我有一个 MySQL 数据库,其中有一个名为 orders 的表和一个名为 timestamp 的列 created_at

所有时间戳都以 UTC 格式存储,并在前端转换回用户本地时间 - 目前设置为“欧洲/伦敦”

在伦敦,我们在 4 月和 11 月之间有 DST,时钟为 GMT+1,例如在 27-10-2019 00:00 本地时间创建的订单将保存为 26-10-2019 23:00 UTC

用户可以搜索在特定日期之间创建的所有订单。在 MySQL 中,我可以使用以下函数将日期转换回本地时区以实现此目的:

SELECT * 
  FROM orders 
 WHERE DATE(CONVERT_TZ(created_at, 'UTC','Europe/London')) >= '27-10-2019' 
   AND DATE(CONVERT_TZ(created_at, 'UTC','Europe/London')) <= '27-10-2019'

但是,我在共享主机上并且未加载 MySQL 时区表,因此无法使用命名时区。我只需要使用一个时区,您可能会想为什么不将所有时间戳存储为Europe/London 而不是UTC。我想将其保留为“UTC”以适应将来的其他时区。

是否有一种解决方法可以在不使用命名时区的情况下返回正确的结果?

注意查询实际上是使用 php 中的 laravel eloqeunt 查询构建器编写的,如下所示:

if (isset($queryString['from_date'])) {
    $query = $query->whereRaw('DATE(orders.created_at) >= ?', [Carbon::parse($queryString['from_date'])->format('Y-m-d')]);
}

if (isset($queryString['to_date'])) {
    $query = $query->whereRaw('DATE(orders.created_at) <= ?', [Carbon::parse($queryString['to_date'])->format('Y-m-d')]);
}

起初我认为解决方案是将用户输入的日期也转换为 UTC,但是当我在以下日期创建订单时,结果会丢失第二条记录:

created_at
-------------------------------------------------------------
27-10-2019 00:00 (localtime) - saved as 26-10-2019 23:00 (UTC)
27-10-2019 01:00 (localtime) - saved as 27-10-2019 00:00 (UTC)

任何帮助表示赞赏。

* 更新 *

通过增加结束日期(当 DST 无效时,使用 &lt; 而不是 &lt;= 检查的结果日期时间不起作用。在英国,DST 于今年 2019 年 10 月 27 日关闭。假设您在以下日期创建了订单:

created_at (Europe/London)                   created_at (UTC) as saved in DB
------------------------------------         --------------------------------
2019-10-26 23:00                             2019-10-26 22:00  (UTC)
2019-10-27 03:00                             2019-10-27 03:00  (UTC)

用户想要列出在2019-10-27 上创建的所有订单。使用2019-10-27作为用户选择的日期,开始日期变为2019-10-26使用php Carbon:

Carbon\Carbon::parse('27 Oct 2019', 'Europe/London')->timezone('UTC')->format('Y-m-d')
=> "2019-10-26"

结束日期变为2019-10-28

Carbon\Carbon::parse('27 Oct 2019', 'Europe/London')->addDay()->timezone('UTC')->format('Y-m-d')
=> "2019-10-28"

这意味着查询最终为:

WHERE created_at >= "2019-10-26"
  AND created_at < "2019-10-28"

因此,在上述场景中,您最终会得到一个结果集,其中包含在 2019-10-26 上创建的订单,这不是您想要的结果。

【问题讨论】:

  • 首先,不要使用 MySQL DATE() 函数进行搜索,因为在这种情况下,您将不会使用 created_at 上的最终索引(您确实有索引,对吗?)。第二件事:使用“= '2019-10-26 23:00:00' AND created_at
  • @AndreiKovacs it's easier do to searches for the end interval using the "&lt;" operator, with the "next day" as the parameter 仅在 DST 开启时有效。它不适用于 DST 关闭时创建的记录?
  • MySQL 中的数据是 UTC 格式的。那里没有夏令时。并且“本地”部分的转换添加或删除了 DST ......这就是为什么开始间隔有 23:00:00 但结束间隔没有改变。如果您使用 DateTime 将“2019-10-28 00:00:00”时区转换为 UTC,则会得到“2019-10-28 00:00:00”。
  • 为了让它发挥作用,你必须与日期时间进行比较,而不是与日期进行比较。因此,将 H:i:s 也添加到格式中。 Carbon\Carbon::parse('27 Oct 2019 00:00:00', 'Europe/London')-&gt;timezone('UTC')-&gt;format('Y-m-d H:i:s') ---&gt; 2019-10-26 23:00:00 . Carbon\Carbon::parse('28 Oct 2019 00:00:00', 'Europe/London')-&gt;timezone('UTC')-&gt;format('Y-m-d H:i:s') ---&gt; 2019-10-28 00:00:00.

标签: mysql laravel datetime timezone php-carbon


【解决方案1】:

您应该向您的托管服务提供商投诉。如果未安装时区表并保持最新状态,则实际上是 MySQL 安装损坏。

但即使它们在那里,您也应该在查询中将请求的日期范围转换为 utc,而不是相反:

WHERE created_at >= CONVERT_TZ('2019-10-27', 'Europe/London', 'UTC')
AND created_at < CONVERT_TZ(DATE_ADD('2019-10-27', INTERVAL 1 DAY), 'Europe/London', 'UTC')

这样效率更高,并且允许使用 created_at 上的索引。

如果没有时区表,您可以在复制上述 SQL 的 php 中执行开始日期和结束日期的转换。

请注意,您正在转换为日期时间,而不是日期,并且您的结束日期需要递增(并且使用&lt; 而不是&lt;= 检查生成的日期时间)以正确包含全天;我怀疑这可能是您尝试在代码中转换为 utc 时出现问题的根源。

【讨论】:

  • 上述end date needs to be incremented (and the resulting datetime checked with &lt; not &lt;=) 仅在夏令时开启时有效。在 DST 关闭时创建订单的月份内它不会起作用?
  • 不,它会工作得很好。用户提供和结束日期(例如 2019-07-01 或 2019-12-01)。你增加它,然后将它从欧洲/伦敦转换为 UTC,产生例如分别为 2019-07-01 23:00:00 和 2019-12-02 00:00:00。然后在查询中使用它来测试created_at &lt; ?。这有意义吗?
  • 请查看我更新的帖子,了解为什么这不起作用
  • 您需要将 BST 日期转换为 UTC 日期时间,而不仅仅是日期。重读我的回答和之前的评论
  • 啊,是的,我的错……你说得对,我需要删除 format('Y-m-d')。它现在正在工作。非常感谢。
猜你喜欢
  • 1970-01-01
  • 2018-08-06
  • 2011-05-10
  • 2013-08-13
  • 1970-01-01
  • 1970-01-01
  • 2014-09-19
  • 2013-05-20
  • 2013-06-30
相关资源
最近更新 更多