org.apache.openejb.quartz.impl.triggers.CalendarIntervalTriggerImpl Maven / Gradle / Ivy
/*
* Copyright 2001-2009 Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.impl.triggers;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.ScheduleBuilder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
/**
* A concrete {@link Trigger}
that is used to fire a {@link org.quartz.JobDetail}
* based upon repeating calendar time intervals.
*
* The trigger will fire every N (see {@link #setRepeatInterval(int)} ) units of calendar time
* (see {@link #setRepeatIntervalUnit(org.quartz.DateBuilder.IntervalUnit)}) as specified in the trigger's definition.
* This trigger can achieve schedules that are not possible with {@link SimpleTrigger} (e.g
* because months are not a fixed number of seconds) or {@link CronTrigger} (e.g. because
* "every 5 months" is not an even divisor of 12).
*
* If you use an interval unit of MONTH
then care should be taken when setting
* a startTime
value that is on a day near the end of the month. For example,
* if you choose a start time that occurs on January 31st, and have a trigger with unit
* MONTH
and interval 1
, then the next fire time will be February 28th,
* and the next time after that will be March 28th - and essentially each subsequent firing will
* occur on the 28th of the month, even if a 31st day exists. If you want a trigger that always
* fires on the last day of the month - regardless of the number of days in the month,
* you should use CronTrigger
.
*
* @see Trigger
* @see CronTrigger
* @see SimpleTrigger
* @see TriggerUtils
*
* @since 1.7
*
* @author James House
*/
public class CalendarIntervalTriggerImpl extends AbstractTrigger implements CalendarIntervalTrigger, CoreTrigger {
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constants.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
private static final long serialVersionUID = -2635982274232850343L;
private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR) + 100;
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Data members.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
private Date startTime = null;
private Date endTime = null;
private Date nextFireTime = null;
private Date previousFireTime = null;
private int repeatInterval = 0;
private IntervalUnit repeatIntervalUnit = IntervalUnit.DAY;
private TimeZone timeZone;
private boolean preserveHourOfDayAcrossDaylightSavings = false; // false is backward-compatible with behavior
private boolean skipDayIfHourDoesNotExist = false;
private int timesTriggered = 0;
private boolean complete = false;
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constructors.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
* Create a DateIntervalTrigger
with no settings.
*
*/
public CalendarIntervalTriggerImpl() {
super();
}
/**
*
* Create a DateIntervalTrigger
that will occur immediately, and
* repeat at the the given interval.
*
*/
public CalendarIntervalTriggerImpl(String name, IntervalUnit intervalUnit, int repeatInterval) {
this(name, null, intervalUnit, repeatInterval);
}
/**
*
* Create a DateIntervalTrigger
that will occur immediately, and
* repeat at the the given interval.
*
*/
public CalendarIntervalTriggerImpl(String name, String group, IntervalUnit intervalUnit,
int repeatInterval) {
this(name, group, new Date(), null, intervalUnit, repeatInterval);
}
/**
*
* Create a DateIntervalTrigger
that will occur at the given time,
* and repeat at the the given interval until the given end time.
*
*
* @param startTime
* A Date
set to the time for the Trigger
* to fire.
* @param endTime
* A Date
set to the time for the Trigger
* to quit repeat firing.
* @param intervalUnit
* The repeat interval unit (minutes, days, months, etc).
* @param repeatInterval
* The number of milliseconds to pause between the repeat firing.
*/
public CalendarIntervalTriggerImpl(String name, Date startTime,
Date endTime, IntervalUnit intervalUnit, int repeatInterval) {
this(name, null, startTime, endTime, intervalUnit, repeatInterval);
}
/**
*
* Create a DateIntervalTrigger
that will occur at the given time,
* and repeat at the the given interval until the given end time.
*
*
* @param startTime
* A Date
set to the time for the Trigger
* to fire.
* @param endTime
* A Date
set to the time for the Trigger
* to quit repeat firing.
* @param intervalUnit
* The repeat interval unit (minutes, days, months, etc).
* @param repeatInterval
* The number of milliseconds to pause between the repeat firing.
*/
public CalendarIntervalTriggerImpl(String name, String group, Date startTime,
Date endTime, IntervalUnit intervalUnit, int repeatInterval) {
super(name, group);
setStartTime(startTime);
setEndTime(endTime);
setRepeatIntervalUnit(intervalUnit);
setRepeatInterval(repeatInterval);
}
/**
*
* Create a DateIntervalTrigger
that will occur at the given time,
* fire the identified Job
and repeat at the the given
* interval until the given end time.
*
*
* @param startTime
* A Date
set to the time for the Trigger
* to fire.
* @param endTime
* A Date
set to the time for the Trigger
* to quit repeat firing.
* @param intervalUnit
* The repeat interval unit (minutes, days, months, etc).
* @param repeatInterval
* The number of milliseconds to pause between the repeat firing.
*/
public CalendarIntervalTriggerImpl(String name, String group, String jobName,
String jobGroup, Date startTime, Date endTime,
IntervalUnit intervalUnit, int repeatInterval) {
super(name, group, jobName, jobGroup);
setStartTime(startTime);
setEndTime(endTime);
setRepeatIntervalUnit(intervalUnit);
setRepeatInterval(repeatInterval);
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
* Get the time at which the DateIntervalTrigger
should occur.
*
*/
@Override
public Date getStartTime() {
if(startTime == null)
startTime = new Date();
return startTime;
}
/**
*
* Set the time at which the DateIntervalTrigger
should occur.
*
*
* @exception IllegalArgumentException
* if startTime is null
.
*/
@Override
public void setStartTime(Date startTime) {
if (startTime == null) {
throw new IllegalArgumentException("Start time cannot be null");
}
Date eTime = getEndTime();
if (eTime != null && eTime.before(startTime)) {
throw new IllegalArgumentException(
"End time cannot be before start time");
}
this.startTime = startTime;
}
/**
*
* Get the time at which the DateIntervalTrigger
should quit
* repeating.
*
*
* @see #getFinalFireTime()
*/
@Override
public Date getEndTime() {
return endTime;
}
/**
*
* Set the time at which the DateIntervalTrigger
should quit
* repeating (and be automatically deleted).
*
*
* @exception IllegalArgumentException
* if endTime is before start time.
*/
@Override
public void setEndTime(Date endTime) {
Date sTime = getStartTime();
if (sTime != null && endTime != null && sTime.after(endTime)) {
throw new IllegalArgumentException(
"End time cannot be before start time");
}
this.endTime = endTime;
}
/* (non-Javadoc)
* @see org.quartz.DateIntervalTriggerI#getRepeatIntervalUnit()
*/
public IntervalUnit getRepeatIntervalUnit() {
return repeatIntervalUnit;
}
/**
* Set the interval unit - the time unit on with the interval applies.
*/
public void setRepeatIntervalUnit(IntervalUnit intervalUnit) {
this.repeatIntervalUnit = intervalUnit;
}
/* (non-Javadoc)
* @see org.quartz.DateIntervalTriggerI#getRepeatInterval()
*/
public int getRepeatInterval() {
return repeatInterval;
}
/**
*
* set the the time interval that will be added to the DateIntervalTrigger
's
* fire time (in the set repeat interval unit) in order to calculate the time of the
* next trigger repeat.
*
*
* @exception IllegalArgumentException
* if repeatInterval is < 1
*/
public void setRepeatInterval( int repeatInterval) {
if (repeatInterval < 0) {
throw new IllegalArgumentException(
"Repeat interval must be >= 1");
}
this.repeatInterval = repeatInterval;
}
/* (non-Javadoc)
* @see org.quartz.CalendarIntervalTriggerI#getTimeZone()
*/
public TimeZone getTimeZone() {
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
return timeZone;
}
/**
*
* Sets the time zone within which time calculations related to this
* trigger will be performed.
*
*
* @param timeZone the desired TimeZone, or null for the system default.
*/
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
/**
* If intervals are a day or greater, this property (set to true) will
* cause the firing of the trigger to always occur at the same time of day,
* (the time of day of the startTime) regardless of daylight saving time
* transitions. Default value is false.
*
*
* For example, without the property set, your trigger may have a start
* time of 9:00 am on March 1st, and a repeat interval of 2 days. But
* after the daylight saving transition occurs, the trigger may start
* firing at 8:00 am every other day.
*
*
*
* If however, the time of day does not exist on a given day to fire
* (e.g. 2:00 am in the United States on the days of daylight saving
* transition), the trigger will go ahead and fire one hour off on
* that day, and then resume the normal hour on other days. If
* you wish for the trigger to never fire at the "wrong" hour, then
* you should set the property skipDayIfHourDoesNotExist.
*
*
* @see #isSkipDayIfHourDoesNotExist()
* @see #getStartTime()
* @see #getTimeZone()
*/
public boolean isPreserveHourOfDayAcrossDaylightSavings() {
return preserveHourOfDayAcrossDaylightSavings;
}
public void setPreserveHourOfDayAcrossDaylightSavings(boolean preserveHourOfDayAcrossDaylightSavings) {
this.preserveHourOfDayAcrossDaylightSavings = preserveHourOfDayAcrossDaylightSavings;
}
/**
* If intervals are a day or greater, and
* preserveHourOfDayAcrossDaylightSavings property is set to true, and the
* hour of the day does not exist on a given day for which the trigger
* would fire, the day will be skipped and the trigger advanced a second
* interval if this property is set to true. Defaults to false.
*
*
* CAUTION! If you enable this property, and your hour of day happens
* to be that of daylight savings transition (e.g. 2:00 am in the United
* States) and the trigger's interval would have had the trigger fire on
* that day, then you may actually completely miss a firing on the day of
* transition if that hour of day does not exist on that day! In such a
* case the next fire time of the trigger will be computed as double (if
* the interval is 2 days, then a span of 4 days between firings will
* occur).
*
*
* @see #isPreserveHourOfDayAcrossDaylightSavings()
*/
public boolean isSkipDayIfHourDoesNotExist() {
return skipDayIfHourDoesNotExist;
}
public void setSkipDayIfHourDoesNotExist(boolean skipDayIfHourDoesNotExist) {
this.skipDayIfHourDoesNotExist = skipDayIfHourDoesNotExist;
}
/* (non-Javadoc)
* @see org.quartz.DateIntervalTriggerI#getTimesTriggered()
*/
public int getTimesTriggered() {
return timesTriggered;
}
/**
*
* Set the number of times the DateIntervalTrigger
has already
* fired.
*
*/
public void setTimesTriggered(int timesTriggered) {
this.timesTriggered = timesTriggered;
}
@Override
protected boolean validateMisfireInstruction(int misfireInstruction) {
if (misfireInstruction < MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) {
return false;
}
return misfireInstruction <= MISFIRE_INSTRUCTION_DO_NOTHING;
}
/**
*
* Updates the DateIntervalTrigger
's state based on the
* MISFIRE_INSTRUCTION_XXX that was selected when the DateIntervalTrigger
* was created.
*
*
*
* If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY,
* then the following scheme will be used:
*
* - The instruction will be interpreted as
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
*
*
*/
@Override
public void updateAfterMisfire(org.quartz.Calendar cal) {
int instr = getMisfireInstruction();
if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)
return;
if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) {
instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW;
}
if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {
Date newFireTime = getFireTimeAfter(new Date());
while (newFireTime != null && cal != null
&& !cal.isTimeIncluded(newFireTime.getTime())) {
newFireTime = getFireTimeAfter(newFireTime);
}
setNextFireTime(newFireTime);
} else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {
// fire once now...
setNextFireTime(new Date());
// the new fire time afterward will magically preserve the original
// time of day for firing for day/week/month interval triggers,
// because of the way getFireTimeAfter() works - in its always restarting
// computation from the start time.
}
}
/**
*
* Called when the {@link Scheduler}
has decided to 'fire'
* the trigger (execute the associated Job
), in order to
* give the Trigger
a chance to update itself for its next
* triggering (if any).
*
*
* @see #executionComplete(JobExecutionContext, JobExecutionException)
*/
@Override
public void triggered(org.quartz.Calendar calendar) {
timesTriggered++;
previousFireTime = nextFireTime;
nextFireTime = getFireTimeAfter(nextFireTime);
while (nextFireTime != null && calendar != null
&& !calendar.isTimeIncluded(nextFireTime.getTime())) {
nextFireTime = getFireTimeAfter(nextFireTime);
if(nextFireTime == null)
break;
//avoid infinite loop
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(nextFireTime);
if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
nextFireTime = null;
}
}
}
/**
*
* @see org.quartz.spi.OperableTrigger#updateWithNewCalendar(org.quartz.Calendar, long)
*/
@Override
public void updateWithNewCalendar(org.quartz.Calendar calendar, long misfireThreshold)
{
nextFireTime = getFireTimeAfter(previousFireTime);
if (nextFireTime == null || calendar == null) {
return;
}
Date now = new Date();
while (nextFireTime != null && !calendar.isTimeIncluded(nextFireTime.getTime())) {
nextFireTime = getFireTimeAfter(nextFireTime);
if(nextFireTime == null)
break;
//avoid infinite loop
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(nextFireTime);
if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
nextFireTime = null;
}
if(nextFireTime != null && nextFireTime.before(now)) {
long diff = now.getTime() - nextFireTime.getTime();
if(diff >= misfireThreshold) {
nextFireTime = getFireTimeAfter(nextFireTime);
}
}
}
}
/**
*
* Called by the scheduler at the time a Trigger
is first
* added to the scheduler, in order to have the Trigger
* compute its first fire time, based on any associated calendar.
*
*
*
* After this method has been called, getNextFireTime()
* should return a valid answer.
*
*
* @return the first time at which the Trigger
will be fired
* by the scheduler, which is also the same value getNextFireTime()
* will return (until after the first firing of the Trigger
).
*
*/
@Override
public Date computeFirstFireTime(org.quartz.Calendar calendar) {
nextFireTime = getStartTime();
while (nextFireTime != null && calendar != null
&& !calendar.isTimeIncluded(nextFireTime.getTime())) {
nextFireTime = getFireTimeAfter(nextFireTime);
if(nextFireTime == null)
break;
//avoid infinite loop
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(nextFireTime);
if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
return null;
}
}
return nextFireTime;
}
/**
*
* Returns the next time at which the Trigger
is scheduled to fire. If
* the trigger will not fire again, null
will be returned. Note that
* the time returned can possibly be in the past, if the time that was computed
* for the trigger to next fire has already arrived, but the scheduler has not yet
* been able to fire the trigger (which would likely be due to lack of resources
* e.g. threads).
*
*
* The value returned is not guaranteed to be valid until after the Trigger
* has been added to the scheduler.
*
*/
@Override
public Date getNextFireTime() {
return nextFireTime;
}
/**
*
* Returns the previous time at which the DateIntervalTrigger
* fired. If the trigger has not yet fired, null
will be
* returned.
*/
@Override
public Date getPreviousFireTime() {
return previousFireTime;
}
/**
*
* Set the next time at which the DateIntervalTrigger
should fire.
*
*
*
* This method should not be invoked by client code.
*
*/
public void setNextFireTime(Date nextFireTime) {
this.nextFireTime = nextFireTime;
}
/**
*
* Set the previous time at which the DateIntervalTrigger
fired.
*
*
*
* This method should not be invoked by client code.
*
*/
public void setPreviousFireTime(Date previousFireTime) {
this.previousFireTime = previousFireTime;
}
/**
*
* Returns the next time at which the DateIntervalTrigger
will
* fire, after the given time. If the trigger will not fire after the given
* time, null
will be returned.
*
*/
@Override
public Date getFireTimeAfter(Date afterTime) {
return getFireTimeAfter(afterTime, false);
}
protected Date getFireTimeAfter(Date afterTime, boolean ignoreEndTime) {
if (complete) {
return null;
}
// increment afterTme by a second, so that we are
// comparing against a time after it!
if (afterTime == null) {
afterTime = new Date();
}
long startMillis = getStartTime().getTime();
long afterMillis = afterTime.getTime();
long endMillis = (getEndTime() == null) ? Long.MAX_VALUE : getEndTime()
.getTime();
if (!ignoreEndTime && (endMillis <= afterMillis)) {
return null;
}
if (afterMillis < startMillis) {
return new Date(startMillis);
}
long secondsAfterStart = 1 + (afterMillis - startMillis) / 1000L;
Date time = null;
long repeatLong = getRepeatInterval();
Calendar aTime = Calendar.getInstance();
aTime.setTime(afterTime);
Calendar sTime = Calendar.getInstance();
if(timeZone != null)
sTime.setTimeZone(timeZone);
sTime.setTime(getStartTime());
sTime.setLenient(true);
if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) {
long jumpCount = secondsAfterStart / repeatLong;
if(secondsAfterStart % repeatLong != 0)
jumpCount++;
sTime.add(Calendar.SECOND, getRepeatInterval() * (int)jumpCount);
time = sTime.getTime();
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) {
long jumpCount = secondsAfterStart / (repeatLong * 60L);
if(secondsAfterStart % (repeatLong * 60L) != 0)
jumpCount++;
sTime.add(Calendar.MINUTE, getRepeatInterval() * (int)jumpCount);
time = sTime.getTime();
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) {
long jumpCount = secondsAfterStart / (repeatLong * 60L * 60L);
if(secondsAfterStart % (repeatLong * 60L * 60L) != 0)
jumpCount++;
sTime.add(Calendar.HOUR_OF_DAY, getRepeatInterval() * (int)jumpCount);
time = sTime.getTime();
}
else { // intervals a day or greater ...
int initialHourOfDay = sTime.get(Calendar.HOUR_OF_DAY);
if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) {
sTime.setLenient(true);
// Because intervals greater than an hour have an non-fixed number
// of seconds in them (due to daylight savings, variation number of
// days in each month, leap year, etc. ) we can't jump forward an
// exact number of seconds to calculate the fire time as we can
// with the second, minute and hour intervals. But, rather
// than slowly crawling our way there by iteratively adding the
// increment to the start time until we reach the "after time",
// we can first make a big leap most of the way there...
long jumpCount = secondsAfterStart / (repeatLong * 24L * 60L * 60L);
// if we need to make a big jump, jump most of the way there,
// but not all the way because in some cases we may over-shoot or under-shoot
if(jumpCount > 20) {
if(jumpCount < 50)
jumpCount = (long) (jumpCount * 0.80);
else if(jumpCount < 500)
jumpCount = (long) (jumpCount * 0.90);
else
jumpCount = (long) (jumpCount * 0.95);
sTime.add(java.util.Calendar.DAY_OF_YEAR, (int) (getRepeatInterval() * jumpCount));
}
// now baby-step the rest of the way there...
while(!sTime.getTime().after(afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.DAY_OF_YEAR, getRepeatInterval());
}
while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.DAY_OF_YEAR, getRepeatInterval());
}
time = sTime.getTime();
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) {
sTime.setLenient(true);
// Because intervals greater than an hour have an non-fixed number
// of seconds in them (due to daylight savings, variation number of
// days in each month, leap year, etc. ) we can't jump forward an
// exact number of seconds to calculate the fire time as we can
// with the second, minute and hour intervals. But, rather
// than slowly crawling our way there by iteratively adding the
// increment to the start time until we reach the "after time",
// we can first make a big leap most of the way there...
long jumpCount = secondsAfterStart / (repeatLong * 7L * 24L * 60L * 60L);
// if we need to make a big jump, jump most of the way there,
// but not all the way because in some cases we may over-shoot or under-shoot
if(jumpCount > 20) {
if(jumpCount < 50)
jumpCount = (long) (jumpCount * 0.80);
else if(jumpCount < 500)
jumpCount = (long) (jumpCount * 0.90);
else
jumpCount = (long) (jumpCount * 0.95);
sTime.add(java.util.Calendar.WEEK_OF_YEAR, (int) (getRepeatInterval() * jumpCount));
}
while(!sTime.getTime().after(afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.WEEK_OF_YEAR, getRepeatInterval());
}
while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.WEEK_OF_YEAR, getRepeatInterval());
}
time = sTime.getTime();
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) {
sTime.setLenient(true);
// because of the large variation in size of months, and
// because months are already large blocks of time, we will
// just advance via brute-force iteration.
while(!sTime.getTime().after(afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.MONTH, getRepeatInterval());
}
while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.MONTH, getRepeatInterval());
}
time = sTime.getTime();
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) {
while(!sTime.getTime().after(afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.YEAR, getRepeatInterval());
}
while(daylightSavingHourShiftOccurredAndAdvanceNeeded(sTime, initialHourOfDay, afterTime) &&
(sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) {
sTime.add(java.util.Calendar.YEAR, getRepeatInterval());
}
time = sTime.getTime();
}
} // case of interval of a day or greater
if (!ignoreEndTime && (endMillis <= time.getTime())) {
return null;
}
return time;
}
private boolean daylightSavingHourShiftOccurredAndAdvanceNeeded(Calendar newTime, int initialHourOfDay, Date afterTime) {
if(isPreserveHourOfDayAcrossDaylightSavings() && newTime.get(Calendar.HOUR_OF_DAY) != initialHourOfDay) {
newTime.set(Calendar.HOUR_OF_DAY, initialHourOfDay);
if (newTime.get(Calendar.HOUR_OF_DAY) != initialHourOfDay) {
return isSkipDayIfHourDoesNotExist();
} else {
return !newTime.getTime().after(afterTime);
}
}
return false;
}
/**
*
* Returns the final time at which the DateIntervalTrigger
will
* fire, if there is no end time set, null will be returned.
*
*
*
* Note that the return time may be in the past.
*
*/
@Override
public Date getFinalFireTime() {
if (complete || getEndTime() == null) {
return null;
}
// back up a second from end time
Date fTime = new Date(getEndTime().getTime() - 1000L);
// find the next fire time after that
fTime = getFireTimeAfter(fTime, true);
// the the trigger fires at the end time, that's it!
if(fTime.equals(getEndTime()))
return fTime;
// otherwise we have to back up one interval from the fire time after the end time
Calendar lTime = Calendar.getInstance();
if(timeZone != null)
lTime.setTimeZone(timeZone);
lTime.setTime(fTime);
lTime.setLenient(true);
if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) {
lTime.add(java.util.Calendar.SECOND, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) {
lTime.add(java.util.Calendar.MINUTE, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) {
lTime.add(java.util.Calendar.HOUR_OF_DAY, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) {
lTime.add(java.util.Calendar.DAY_OF_YEAR, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) {
lTime.add(java.util.Calendar.WEEK_OF_YEAR, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) {
lTime.add(java.util.Calendar.MONTH, -1 * getRepeatInterval());
}
else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) {
lTime.add(java.util.Calendar.YEAR, -1 * getRepeatInterval());
}
return lTime.getTime();
}
/**
*
* Determines whether or not the DateIntervalTrigger
will occur
* again.
*
*/
@Override
public boolean mayFireAgain() {
return (getNextFireTime() != null);
}
/**
*
* Validates whether the properties of the JobDetail
are
* valid for submission into a Scheduler
.
*
* @throws IllegalStateException
* if a required property (such as Name, Group, Class) is not
* set.
*/
@Override
public void validate() throws SchedulerException {
super.validate();
if (repeatInterval < 1) {
throw new SchedulerException("Repeat Interval cannot be zero.");
}
}
/**
* Get a {@link ScheduleBuilder} that is configured to produce a
* schedule identical to this trigger's schedule.
*
* @see #getTriggerBuilder()
*/
@Override
public ScheduleBuilder getScheduleBuilder() {
CalendarIntervalScheduleBuilder cb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
.withInterval(getRepeatInterval(), getRepeatIntervalUnit());
switch(getMisfireInstruction()) {
case MISFIRE_INSTRUCTION_DO_NOTHING : cb.withMisfireHandlingInstructionDoNothing();
break;
case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW : cb.withMisfireHandlingInstructionFireAndProceed();
break;
}
return cb;
}
public boolean hasAdditionalProperties() {
return false;
}
}