You have an issue if you live/deploy in an area that has daylight saving times. The way you set it up is to happen every 24 hours, rather than at a particular time each day. When a time shift happens, your event will happen at 5am, or 7am depending.
In the past I have set up a time-of-day schedule for jobs, and I found the best way to do that was to set up a self-repeating wrapper for a task. When the task runs, it reschedules itself for the next iteration.
So, the way I did it was to create a Runnable that self-replicates each time it runs. The concept ends up being somewhat simple (somewhat...). So, consider the following code that will reschedule itself after running:
import java.util.Calendar;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class ReschedulingDailyTask implements Runnable {
private final ScheduledExecutorService service;
private final Runnable task;
private final int hour;
private final int min;
private final int sec;
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean scheduled = new AtomicBoolean(false);
public ReschedulingDailyTask(final ScheduledExecutorService service,
final Runnable task, final int hour) {
this (service, task, hour, 0, 0);
}
public ReschedulingDailyTask(final ScheduledExecutorService service,
final Runnable task, final int hour, final int min, final int sec) {
this.service = service;
this.task = task;
this.hour = hour;
this.min = min;
this.sec = sec;
}
public void enable() {
if (!active.getAndSet(true)) {
// was not enabled:
reSchedule();
}
}
public void disable() {
active.getAndSet(false);
}
private void reSchedule() {
if (!scheduled.getAndSet(true)) {
Calendar calendar = Calendar.getInstance();
long now = calendar.getTimeInMillis();
calendar.set(Calendar.HOUR, hour);
calendar.set(Calendar.MINUTE, min);
calendar.set(Calendar.SECOND, sec);
calendar.set(Calendar.MILLISECOND, 0);
while (calendar.getTimeInMillis() < now) {
// scheduled in the past, go forward one day....
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
// for debug, if needed....
//System.out.println("Reschedule for " + calendar.getTimeInMillis()
// + " (in " + (calendar.getTimeInMillis() - now) + "ms)" );
// set ourselves up to run at a given time.
service.schedule(this, calendar.getTimeInMillis() - now, TimeUnit.MILLISECONDS);
}
}
@Override
public void run() {
// since we are running, we are no longer scheduled...
scheduled.set(false);
// we may have been disabled after we were enabled ...
// you can't cancel the schedule, but you can ignore the task...
if (!active.get()) {
return;
}
// we were active, and we run the task, and force the reschedule.
try {
task.run();
} finally {
reSchedule();
}
}
}
I tested it with the following code:
private static final class DaemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "Schedule Thread");
thread.setDaemon(true);
return thread;
}
}
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1, new DaemonThreadFactory());
Calendar cal = Calendar.getInstance();
System.out.println("Time now: " + cal.getTimeInMillis());
cal.add(Calendar.SECOND, 5);
cal.set(Calendar.MILLISECOND, 0);
System.out.println("Execute at: " + cal.getTimeInMillis());
int hour = cal.get(Calendar.HOUR);
int min = cal.get(Calendar.MINUTE);
int sec = cal.get(Calendar.SECOND);
final Object lock = new Object();
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Time is " + Calendar.getInstance().getTimeInMillis());
synchronized(lock) {
lock.notifyAll();
}
}
};
ReschedulingDailyTask daily = new ReschedulingDailyTask(service, task, hour, min, sec);
daily.enable();
System.out.println("Waiting");
synchronized (lock) {
lock.wait();
}
System.out.println("Done");
}
Note that I create a Daemon thread factory for the Scheduler, so that, if the main thread closes, the application terminates. The above method will schedule the task for about 5 seconds in the future....