7

我有一个石英 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

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

4

4 回答 4

5

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

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

于 2011-03-10T01:38:36.780 回答
3

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

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

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

于 2014-03-24T13:47:57.800 回答
2

我采用了 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。

我希望这些信息有用。

于 2014-05-23T02:11:46.897 回答
0

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

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

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 表达式中的年份 :( 所以它可能不适用于您的场景

于 2021-09-13T06:44:57.733 回答