根据用户的本地时区在特定时间执行日常操作

Tau*_*ren 5 java timezone datetime hibernate jodatime

我的Java应用程序需要每天向所有用户发送一次电子邮件.我想在大约凌晨2点根据每个用户的当地时间提供.因此,纽约的用户将在2AM America/New_York时间收到消息.洛杉矶的用户将在2AM America/Los_Angeles时间收到消息.我的邮件流程每天都会运行一次.

我计划使用HTML5地理位置功能或IP地址定位来在注册期间自动确定每个用户的时区.当然,如果这些值不正确,用户可以手动修改时区.我打算将选定的时区存储在User对象中作为时区ID字符串,例如"America/New_York".这将作为字符串持久保存到数据库,但如有必要,可以更改.

需要考虑的事项:

  • 我需要考虑夏令时,因此用户总是在凌晨2点左右收到电子邮件.这意味着我不能只在用户对象中存储GMT-8或类似的UTC偏移量.洛杉矶的部分时间是GMT-7,其他部分则是GMT-8.
  • 另一个SO问题的答案建议存储偏移信息,但我不知道这将如何工作,因为不同地方的时间可能会在一年中发生变化.每当世界某处发生时区变更事件时,我都不会更新所有用户对象的时区.
  • 我在我的应用程序中使用JodaTime,所以如果它有用,我可以利用它.
  • 我正在使用Hibernate进行数据库查询,并希望找到一个可以在hibernate查询中处理的解决方案,而无需在Java中处理数十万个用户记录.

我正在使用Spring Scheduling cron功能(通过Quartz)在当天每小时2分钟运行一个方法.现在我的大多数用户都在America/Los_Angeles,所以我手动强制所有邮件都在太平洋时间凌晨2:02发送.以下方法每小时运行一次:02小时,但它会手动检查时间,并且仅在洛杉矶当前时间为2:00小时时才会进行.这意味着其他地方的用户将在凌晨2点以外的时间收到电子邮件.

@Scheduled(cron="0 2 * * * *")
public void sendDailyReportEmail()  {
    DateTime now = new DateTime(DateTimeZone.forID("America/Los_Angeles"));
    if (now.getHourOfDay() != 2) {
        log.info("Not 2AM pacific, so skipping email reports");
        return;
    }
    // send email reports
}
Run Code Online (Sandbox Code Playgroud)

因此,我需要的是执行一个数据库查询,该查询将根据用户对象中的时区为我提供在凌晨2:00时有本地时间的所有用户.有关如何实现这一点的任何想法?

Tau*_*ren 3

我对此进行了更多思考并找到了可能的解决方案。我还没有实现它,但我认为它应该有效。基本上,它涉及执行以下查询(注意:这是 MySQL 特定的):

select * from members where hour(convert_tz(now(),'UTC',timezone)) = 3;
Run Code Online (Sandbox Code Playgroud)

此查询通过执行以下操作来选择当前当地时间在凌晨 3 点到凌晨 3:59 之间的所有成员:

  1. 它使用 获取当前服务器时间now(),这是 UTC 时间,因为数据库服务器配置为 UTC 时间。
  2. 然后,它使用该函数将该时间转换为成员表列convert_tz()中指定的成员时区。timezone请注意,Member 类包含格式为“America/Los_Angeles”的时区字段。
  3. 接下来,它使用该函数检查现在是几点hour(),看看它是否为3, 3AM。
  4. 然后,查询返回满足这些要求的所有成员并向每个成员发送电子邮件。

我有点担心每小时为系统中的每个成员进行时区转换和计算。我必须进行一些测试,看看它会给数据库带来什么样的负载,但它可能会因为数百万甚至上万的用户而窒息。

另一种可以显着减轻负载但不太优雅的方法是执行以下操作(注意此代码未经测试!):

@Scheduled(cron="0 2 * * * *")
public void sendDailyReportEmail()  {

    Query query = session.createQuery(
        "select distinct m.timezone from Member m");
    List<String> timezones = query.list();

    for (String timezone : timezones) {
        DateTime now = new DateTime(DateTimeZone.forID(timezone));
        if (now.getHourOfDay() == 3) {
            Query query2 = session.createQuery(
                "from Member where timezone = :zone");
            query2.setParameter("zone", timezone);
            List<Member> members = query2.list();
            // send email reports
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码执行以下操作:

  1. 每天每小时整点 2 分钟执行方法
  2. 查询数据库以获取members 表中所有唯一时区的列表。
  3. 对于找到的每个时区,确定当前是否处于凌晨 3 点。
  4. 如果时区位于凌晨 3 点,请选择该时区的所有成员并向他们发送电子邮件。

这会在每小时开始时增加一点额外的开销,但不需要每个用户的时区处理。系统实际使用的唯一时区很可能只有几十个。我不太可能在所有 500 多个时区都拥有会员。

最后一件事。我没有在凌晨 2 点发送电子邮件,而是将其移至凌晨 3 点。这是因为当春季夏令时发生变化时,时间会从凌晨 2 点变为凌晨 3 点。因此,不存在“凌晨 2:02”这样的事情,这意味着当天不会发送电子邮件。此外,在秋季,系统可以向每个用户发送 2 封电子邮件,因为一小时会重复。我需要查明非美国时区是否在不同时间更改时间,因为凌晨 3 点可能在其他地方出现问题。