Ways to deal with Daylight Savings time with Quartz Cron Trigger

倾然丶 夕夏残阳落幕 提交于 2020-02-13 09:26:31

问题


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!


回答1:


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...




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/5223631/ways-to-deal-with-daylight-savings-time-with-quartz-cron-trigger

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!