使用 Quartz Cron Trigger 处理夏令时的方法

发布于 2024-10-20 17:33:44 字数 551 浏览 11 评论 0原文

我有一个石英 cron 触发器,如下所示:

<bean id="batchProcessCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="batchProcessJobDetail" />
    <property name="cronExpression" value="0 30 2 * * ?" />
</bean>

如果我在凌晨 2-3 点期间发生了多个配置,我应该如何解决这个问题?是否有公认的最佳实践?

相关链接:http://www.quartz-scheduler.org/docs/faq.html#FAQ -daylightSavings

基本上它说的是“处理它”。但我的问题是如何!

I have a quartz cron trigger that looks like so:

<bean id="batchProcessCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="batchProcessJobDetail" />
    <property name="cronExpression" value="0 30 2 * * ?" />
</bean>

How should I solve this, if I have several configurations that happen within the 2-3am period? Is there an accepted best practice?

Relevant link: http://www.quartz-scheduler.org/docs/faq.html#FAQ-daylightSavings

Basically it says "Deal with it." But my question is how!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

没企图 2024-10-27 17:33:44

我使用一个单独的触发器解决了这个问题,该触发器仅在 DST 开始日期触发(提前一小时),用于东部时间凌晨 2 点到 3 点之间发生的配置。

看起来很笨拙,但它确实有效......

I solved it using a separate trigger that only fires (an hour early) on the beginning date of DST for the configurations that happen between 2am and 3am Eastern.

Seems kludgey, but it works...

英雄似剑 2024-10-27 17:33:44

我们正在使用以下解决方案。为此,您还需要 joda 时间库。

public class MyCronExpression extends CronExpression
{
    CronExpression _orgCronExpression;

    public MyCronExpression(String cronExpression) throws ParseException
    {
        super(cronExpression);
        setTimeZone(TimeZone.getTimeZone("UTC"));
        _orgCronExpression = new CronExpression(cronExpression);
    }

    @Override
    public Date getTimeAfter(Date date)
    {

        Date date1 = super.getTimeAfter(new Date(date.getTime()-date.getTimezoneOffset()*60*1000));
        if (TimeZone.getDefault().inDaylightTime( date1 ) && !TimeZone.getDefault().inDaylightTime( date ))
        {
            DateTimeZone dtz = DateTimeZone.getDefault();
            Date dstEnd = new Date(dtz.nextTransition(date.getTime()));
            int dstEndHour = dstEnd.getHours();
            int dstDuration = (dtz.getOffset(date1.getTime()) - dtz.getStandardOffset(date1.getTime()))/(60*60*1000);
            int hour = date1.getHours()+date1.getTimezoneOffset()/60;
            if (hour < dstEndHour && hour >= dstEndHour-dstDuration)
                return dstEnd;
            else
                return _orgCronExpression.getTimeAfter(date);
        }
        else
            return _orgCronExpression.getTimeAfter(date);
    }

}

该类的使用方式如下:

        CronTriggerImpl trigger = new CronTriggerImpl();
    trigger.setCronExpression(new MyCronExpression("0 15 2 * * ?"));

这里是一些示例触发时间:

Tue Mar 25 02:15:00 CET 2014
Wed Mar 26 02:15:00 CET 2014
Thu Mar 27 02:15:00 CET 2014
Fri Mar 28 02:15:00 CET 2014
Sat Mar 29 02:15:00 CET 2014
**Sun Mar 30 03:00:00 CEST 2014**
Mon Mar 31 02:15:00 CEST 2014
Tue Apr 01 02:15:00 CEST 2014
Wed Apr 02 02:15:00 CEST 2014

如果您发现此解决方案有任何错误/问题,请发布。

We are using the following solution. For this you will also need the joda time library.

public class MyCronExpression extends CronExpression
{
    CronExpression _orgCronExpression;

    public MyCronExpression(String cronExpression) throws ParseException
    {
        super(cronExpression);
        setTimeZone(TimeZone.getTimeZone("UTC"));
        _orgCronExpression = new CronExpression(cronExpression);
    }

    @Override
    public Date getTimeAfter(Date date)
    {

        Date date1 = super.getTimeAfter(new Date(date.getTime()-date.getTimezoneOffset()*60*1000));
        if (TimeZone.getDefault().inDaylightTime( date1 ) && !TimeZone.getDefault().inDaylightTime( date ))
        {
            DateTimeZone dtz = DateTimeZone.getDefault();
            Date dstEnd = new Date(dtz.nextTransition(date.getTime()));
            int dstEndHour = dstEnd.getHours();
            int dstDuration = (dtz.getOffset(date1.getTime()) - dtz.getStandardOffset(date1.getTime()))/(60*60*1000);
            int hour = date1.getHours()+date1.getTimezoneOffset()/60;
            if (hour < dstEndHour && hour >= dstEndHour-dstDuration)
                return dstEnd;
            else
                return _orgCronExpression.getTimeAfter(date);
        }
        else
            return _orgCronExpression.getTimeAfter(date);
    }

}

The class is used as follows:

        CronTriggerImpl trigger = new CronTriggerImpl();
    trigger.setCronExpression(new MyCronExpression("0 15 2 * * ?"));

Here some sample trigger times:

Tue Mar 25 02:15:00 CET 2014
Wed Mar 26 02:15:00 CET 2014
Thu Mar 27 02:15:00 CET 2014
Fri Mar 28 02:15:00 CET 2014
Sat Mar 29 02:15:00 CET 2014
**Sun Mar 30 03:00:00 CEST 2014**
Mon Mar 31 02:15:00 CEST 2014
Tue Apr 01 02:15:00 CEST 2014
Wed Apr 02 02:15:00 CEST 2014

Please post if you find any bugs/issues with this solution.

深海少女心 2024-10-27 17:33:44

我采纳了 Ron 非常有趣的答案,并改进了 getTimeAfter 方法,以便将其调整为服务器 GMT 运行以及调度“一年一次”cron 表达式时可能存在的差异。

@Override
  public Date getTimeAfter(Date date) {

    Date nextDate = super.getTimeAfter(date);
    if(nextDate == null){
      return null;
    }
    DateTime date1 = new DateTime(nextDate);

    if (getTimeZone().inDaylightTime(date1.toDate()) && !getTimeZone().inDaylightTime(date)) {

      DateTimeZone dtz = DateTimeZone.forTimeZone(getTimeZone());
      DateTime dstEndDateTime = new DateTime(new Date(dtz.nextTransition(date.getTime())));

      int dstEndHour = dstEndDateTime.getHourOfDay();
      int dstDuration = (dtz.getOffset(date1.getMillis()) - dtz.getStandardOffset(date1.getMillis())) / (60 * 60 * 1000);
      int hour = date1.getHourOfDay();

      // Verifies if the scheduled hour is within a phantom hour (dissapears upon DST change)
      if (hour < dstEndHour && hour >= dstEndHour-dstDuration){       
        // Verify if the date is  a skip, otherwise it is a date in the future (like threads that run once a year)
        if(dstEndDateTime.getDayOfYear() == date1.minusDays(1).getDayOfYear()){
          return dstEndDateTime.toDate();
        }else{
          return nextDate;
        }

      }else{
        return nextDate;
      }

    } else{
      return nextDate;
    }

  }

请注意,我的服务器以 GMT 模式运行,因此我不使用 Ron 的答案中存在的一些偏移量转换。

我还发现了一个 Quartz 错误,如果您使用以下配置,它将失败,因为它无法正确处理 cron 表达式:

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

String cron = "0 15 2 8 3 ? 2015";
FailsafeCronExpression cronExpression = new FailsafeCronExpression(cron);
cronExpression.setTimeZone(DateTimeZone.forID("America/Vancouver"));

DateTime nextDate = new DateTime(cronExpression.getTimeAfter(sdf.parse("12/11/2014 10:15:00")));

这实际上似乎发生了,因为温哥华的 DST 更改发生在 3 月 9 日凌晨 2 点,并且似乎super.getTimeAfter(date) 方法的 Quartz 内部实现将始终发送 null。

我希望这些信息有用。

I took Ron's very interesting answer and improved the getTimeAfter Method, In order to adjust it to server GMT running and possible differences when scheduling 'Once a year' cron expressions.

@Override
  public Date getTimeAfter(Date date) {

    Date nextDate = super.getTimeAfter(date);
    if(nextDate == null){
      return null;
    }
    DateTime date1 = new DateTime(nextDate);

    if (getTimeZone().inDaylightTime(date1.toDate()) && !getTimeZone().inDaylightTime(date)) {

      DateTimeZone dtz = DateTimeZone.forTimeZone(getTimeZone());
      DateTime dstEndDateTime = new DateTime(new Date(dtz.nextTransition(date.getTime())));

      int dstEndHour = dstEndDateTime.getHourOfDay();
      int dstDuration = (dtz.getOffset(date1.getMillis()) - dtz.getStandardOffset(date1.getMillis())) / (60 * 60 * 1000);
      int hour = date1.getHourOfDay();

      // Verifies if the scheduled hour is within a phantom hour (dissapears upon DST change)
      if (hour < dstEndHour && hour >= dstEndHour-dstDuration){       
        // Verify if the date is  a skip, otherwise it is a date in the future (like threads that run once a year)
        if(dstEndDateTime.getDayOfYear() == date1.minusDays(1).getDayOfYear()){
          return dstEndDateTime.toDate();
        }else{
          return nextDate;
        }

      }else{
        return nextDate;
      }

    } else{
      return nextDate;
    }

  }

Please note my server runs in GMT mode, therefore I do not use some of the offset conversions present in Ron's answer.

Also I discovered a Quartz bug, in which if you use the following configuration, it will fail because it is not capable of processing the cron expression correctly:

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

String cron = "0 15 2 8 3 ? 2015";
FailsafeCronExpression cronExpression = new FailsafeCronExpression(cron);
cronExpression.setTimeZone(DateTimeZone.forID("America/Vancouver"));

DateTime nextDate = new DateTime(cronExpression.getTimeAfter(sdf.parse("12/11/2014 10:15:00")));

This actually seems to happen because DST change takes place during 9th of March 2am for Vancouver and seems the Quartz internal implementation of the super.getTimeAfter(date) method will always send null.

I hope this information is useful.

溺深海 2024-10-27 17:33:44

我知道这个问题很老了,但它似乎仍然有效。我相信我已经找到了解决这个问题的方法,我将其留在这里,以防其他人偶然发现它并发现它很方便

Spring 5.3 改进了调度,使用 java.time API 重写。它还支持 cron 表达式的特定于quartz 的扩展。
示例计算代码:

public Instant calculateNextExecution(String cronExpression, Instant lastExecutionInstant, ZoneId executionZoneId) {
    LocalDateTime lastExecutionDateTimeInExecutionZone lastExecutionInstant.atZone(executionZoneId)
        .toLocalDateTime();
    
    LocalDateTime nextExecutionDateInExecutionZone = CronExpression.parse(cronExpression).next(lastExecutionDateTimeInExecutionZone);
    // skipped checking and handling nonexistant next execution
    ZoneOffsetTransition transition = executionZoneId.getRules().getTransition(nextExecutionDateInExecutionZone);
    if (transition == null) {
        // next execution didn't occur during time transition
        return nextExecutionDateInExecutionZone.atZone(executionZoneId)
            .toInstant();
    } else {
        // next execution occured during time transition, one might check if transition was a gap or overlap and do sth with it
        return doSthWithIt(transition, nextExecutionDateInExecutionZone);
    }
}

相关的spring类是org.springframework.scheduling.support.CronExpression。
详细说明 https:// spring.io/blog/2020/11/10/new-in-spring-5-3-improved-cron-expressions

@update:Spring 调度程序不支持 cron 表达式中的年份:( 所以它可能不支持在你的场景中工作

I'm aware this question is quite old, but it still seems valid. I believe i've found a way to solve this problem, i'l leave it here in case someone else sumbles upon it and finds it handy

With spring 5.3 comes improved scheduling, rewritten using java.time API. It also supports quartz-specific extensions to cron expressions.
Example computation code:

public Instant calculateNextExecution(String cronExpression, Instant lastExecutionInstant, ZoneId executionZoneId) {
    LocalDateTime lastExecutionDateTimeInExecutionZone lastExecutionInstant.atZone(executionZoneId)
        .toLocalDateTime();
    
    LocalDateTime nextExecutionDateInExecutionZone = CronExpression.parse(cronExpression).next(lastExecutionDateTimeInExecutionZone);
    // skipped checking and handling nonexistant next execution
    ZoneOffsetTransition transition = executionZoneId.getRules().getTransition(nextExecutionDateInExecutionZone);
    if (transition == null) {
        // next execution didn't occur during time transition
        return nextExecutionDateInExecutionZone.atZone(executionZoneId)
            .toInstant();
    } else {
        // next execution occured during time transition, one might check if transition was a gap or overlap and do sth with it
        return doSthWithIt(transition, nextExecutionDateInExecutionZone);
    }
}

Relevant spring class is org.springframework.scheduling.support.CronExpression.
Detailed description https://spring.io/blog/2020/11/10/new-in-spring-5-3-improved-cron-expressions

@update: Spring scheduler doesn't support years in cron expressions :( so it might not work in you scenario

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文