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

时间:2010-09-14 07:44:59

标签: java hibernate datetime timezone 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。
  • Another SO question有答案建议存储偏移信息,但我不知道这会如何起作用,因为不同地方的时间可能会在一年中发生变化。每当世界某个地方发生时区变更事件时,我都不会更新所有用户对象的时区。
  • 我在我的应用程序中使用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
}

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

1 个答案:

答案 0 :(得分:2)

我已经给出了更多的想法,并有一个可能的解决方案。我还没有实现它,但我认为它应该工作。基本上,它涉及执行以下查询(注意:这是MySQL特定的):

select * from members where hour(convert_tz(now(),'UTC',timezone)) = 3;

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

  1. 使用now()获取当前服务器时间,这是UTC时间,因为数据库服务器配置为UTC时间。
  2. 然后使用convert_tz()函数将该时间转换为成员表的timezone列中指定的成员的时区。请注意,Member类包含“America / Los_Angeles”格式的时区字段。
  3. 接下来,它会检查hour()函数的小时数,以查看它是否为3,持续3 AM。
  4. 然后,查询将返回满足这些要求的所有成员,并向每个成员发送电子邮件。
  5. 我有点担心每小时对系统中的每个成员进行时区转换和计算。我将不得不测试一些东西,看看它对数据库造成了什么样的负载,但它可能会淹没数百万甚至10万的用户。

    应该显着减轻负载的替代方案,但不是那么优雅,请执行以下操作(注意此代码未经测试!):

    @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
            }
        }
    }
    

    此代码执行此操作:

    1. 每小时2分钟,每天每小时执行方法
    2. 在数据库中查询成员表中所有唯一时区的列表。
    3. 对于找到的每个时区,确定它当前是否在凌晨3点。
    4. 如果时区位于凌晨3点,请选择该时区内的所有成员并向其发送电子邮件。
    5. 这会在每小时开始时增加一些额外开销,但不需要按用户时区处理。有可能系统实际只会使用几十个独特的时区。我不太可能在所有500多个时区中拥有会员。

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