【问题标题】:Retrieve dates at the correct time irrespective of DST无论 DST 如何,都可以在正确的时间检索日期
【发布时间】:2021-02-15 14:42:50
【问题描述】:
  • 语言:Java
  • 数据库:mySQL

我必须为将在特定时间发送通知的用户建立一个警报系统。例如,用户可以指定时间上午 7 点和日期 2020 年 5 月 1 日,这意味着必须在 2020 年 5 月 1 日上午 7 点发送警报。用户可以在世界的任何地方,所以我们必须考虑不同的时区。我最初认为我们必须将时间存储在 UTC 中。因此,如果我是 EST 时区的用户,例如,数据库将存储 2020 年 5 月 1 日作为日期,将 12 点作为时间(我计划将日期和时间存储在单独的列中)。但是,现在我很困惑,因为如果我存储了上周的日期,即在夏令时之前存储的 UTC 时间将是 2020 年 5 月 1 日上午 11 点。因此,当 DST 发生时,用户不会在正确的时间收到警报。如何存储时间以便用户始终在早上 7 点收到警报?我必须将日期存储在用户本地时区吗?顺便说一下,我有一个存储用户时区信息的表。

【问题讨论】:

  • 您将值以 UTC 格式存储在数据库中,但您使用 ZonedDateTime 和用户的时区计算该值。
  • @Khazana 对于ZonedDateTime,没有“DST 之前”和“DST 之后”之分,因为ZonedDateTime 知道任何给定的日期/时间是在夏令时还是标准时间,例如它知道 2020 年 5 月 1 日早上 7 点是夏令时。 就是你应该使用ZonedDateTime的原因。
  • “将本地时间转换为 UTC 是一种有损操作” -- 如果您从本地日期/时间开始,转换为 UTC,然后再转换回来,这是不正确的使用相同的时区规范。让我们调用本地日期/时间t。两种情况下使用的 DST 偏移将是在时间 t 生效的偏移,即使您在不同时间使用不同的当前 DST 偏移执行转换。
  • 假设您的活动发生在 2020 年 10 月 31 日当地时间 07:00。您将其转换为应用用户当地时区的 Instant。假设它是美国太平洋时间。在那个时刻,PDT 应用,所以偏移量是 -0700,UTC 是2020-10-31 14:00Z。无论您何时进行转换,这永远不会改变。现在你想计算一周后的时间。您获取该日期/时间,转换为 LocalDateTime,添加星期,然后转换回 UTC。添加一周后,新的本地值为2020-11-06 07:00,但由于 DST 未生效,偏移量为 -0800...
  • ... UTC 是 2020-11-06 15:00Z。同样,这永远不会改变。

标签: java mysql datetime timezone


【解决方案1】:

使用LocalTime

检查以下introduction of LocalTime

这个类对于表示一天中基于人类的时间很有用,例如 电影时间,或当地图书馆的开闭时间。

带有时区或时区偏移的日期时间是此用例的错误选择。我假设您的数据库中有用户的时区,并且您已经安排了一些玉米作业来发送通知。当作业运行以发送通知时,只需检查当时哪些用户的当地时间为上午 7:00(加/减几分钟,例如 5 分钟,根据您的要求和玉米作业的频率),例如

import java.time.LocalTime;
import java.time.ZoneId;

public class Main {
    public static void main(String[] args) {
        LocalTime time = LocalTime.now(ZoneId.of("Europe/London"));// Your server's timezone
        System.out.println(isLocalTimeSameAsNotificationTime(ZoneId.of("Asia/Calcutta"), time)); // User's timezone
    }

    static boolean isLocalTimeSameAsNotificationTime(ZoneId zone, LocalTime notificationTime) {
        LocalTime time = LocalTime.now(zone);// Local time at the param zone

        // True if +/- 5 minutes notificationTime
        return !(time.isBefore(notificationTime.minusMinutes(5)) || time.isAfter(notificationTime.plusMinutes(5)));
    }
}

corn 作业中的查询应该类似于SELECT userId, user FROM tableName WHERE notificationTime = now plus/minus 5 minutes,现在您必须遍历这个结果集。对于结果集中的每条记录,您需要调用函数isLocalTimeSameAsNotificationTime,将时区和服务器的本地时间作为参数传递,如果返回true,则触发通知。

注意:我在这里提到的 SQL 查询是一个示例,说明您必须如何编写查询;它在语法上不正确。

【讨论】:

  • 谢谢。你说得对,我计划设置一个 cron 作业,它会每分钟检查一次时间。我认为这个 cron 作业会检查它的时间(以 UTC 为单位)并在用户数据库中查找匹配的时间(也应该是 UTC 时间)以发送警报(因此不会循环遍历每个用户)。但是使用您的代码,我们似乎每分钟都遍历所有用户,然后检查LocalTime.now(zone) 是否等于他们的警报时间。我想知道我们是否可以将LocalTime.now(zone) 的检查限制在一个地方,或者换句话说,统一时区以避免处理期间的开销。
  • @Khazana - 您不必遍历所有用户。玉米作业中的查询应该类似于SELECT userId, userTz FROM tableName WHERE notificationTime = now plus/minus 5 minutes,现在您必须遍历此结果集。对于结果集中的每条记录,您需要调用此函数,isLocalTimeSameAsNotificationTime 将时区和服务器的本地时间作为参数传递,如果返回true,则触发通知。 注意: 我在这里提到的 SQL 查询是用于说明如何编写查询的示例;它在语法上不正确。
  • 谢谢!我认为我们可以像这样将整个逻辑移动到 sql 查询中:SELECT userId where notification_time = CONVERT_TZ(now() , 'UTC', tz);。但我认为这次讨论的关键要点是我们不应该将时间存储在 UTC 中。
【解决方案2】:

MySQL TIMESTAMP 数据类型非常适合这个问题。它们始终存储在UTC 中(只要您的服务器的操作系统没有设置奇怪的时区)。只要操作系统的时钟是 UTC,您就会处于良好状态。

然后当你给 MySQL 命令时:

   SET time_zone = 'America/New_York';
   INSERT INTO notification (deliver_timestamp, '2020-11-20 11:30:00')

在存储TIMESTAMP 的过程中,MySQL 将其从当前时间转换为 UTC。在出去的路上,它会将其转换回当地时间。因此,例如,如果您这样做。

   SET time_zone = 'America/Los_Angeles';
   SELECT timestamp FROM notifications;

您将收到1010-11-20 08:30:00,这是同一时间,但晚了三个小时。 America/New_YorkAsia/Kolkata 这样的字符串是时区的名称。 MySQL 和其他使用这种zoneinfo timezone name 的软件知道所有关于何时各个地方从白天切换到常规的电子政治知识。更不用说这些规则的历史变化可以追溯到半个多世纪以前。这是一个复杂的系统。

为了让它工作

要使其在全球范围内运行,您必须为每个使用您系统的人设置一个时区用户偏好设置。您必须设置某种下拉菜单,显示来自zoneinfo data basetimezone names。您需要 MySQL 的 SET time_zone='zoneinfoname' 命令的 zoneinfo 名称。您可以将这些相同的文本字符串与 java.time.ZonedDateTime 类一起使用。

然后,每次您代表用户访问数据库时,您都要先设置时区,然后再执行其他任何操作。在典型的网络应用中,每个用户的时区偏好都是其用户配置文件的一部分。

注意:只有TIMESTAMP 列以这种方式工作。 DATETIME 列存储和检索您提供给它们的任何时间数据,而不会通知时区。

而且,如果您的 Java 代码需要 UTC 时间,请执行此操作。

   SET time_zone = 'UTC';
   SELECT timestamp FROM notifications;

可能有点难以理解这一点。 MySQL 使用 zoneinfo 规则进行这些转换。 Zoneinfo 知道什么时候是 DST,什么时候不是。

【讨论】:

  • 谢谢!但是,如果您有时插入SET time_zone = 'America/New_York'; INSERT INTO notification (deliver_timestamp, '2020-11-20 11:30:00')(想想日期创建时间)有和没有夏令时,存储在数据库中的值是否会由于不同的偏移量而有所不同?当我说时间时,我指的不是“2020-11-20 11:30:00”,而是插入的时间。
猜你喜欢
  • 1970-01-01
  • 2011-09-09
  • 1970-01-01
  • 1970-01-01
  • 2012-02-20
  • 1970-01-01
  • 2017-11-29
  • 1970-01-01
  • 2011-12-20
相关资源
最近更新 更多