[java] How to run certain task every day at a particular time using ScheduledExecutorService?

As with the present java SE 8 release with it's excellent date time API with java.time these kind of calculation can be done more easily instead of using java.util.Calendar and java.util.Date.

Now as a sample example for scheduling a task with your use case:

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nextRun = now.withHour(5).withMinute(0).withSecond(0);
if(now.compareTo(nextRun) > 0)
    nextRun = nextRun.plusDays(1);

Duration duration = Duration.between(now, nextRun);
long initalDelay = duration.getSeconds();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
scheduler.scheduleAtFixedRate(new MyRunnableTask(),
    initalDelay,
    TimeUnit.DAYS.toSeconds(1),
    TimeUnit.SECONDS);

The initalDelay is computed to ask the scheduler to delay the execution in TimeUnit.SECONDS. Time difference issues with unit milliseconds and below seems to be negligible for this use case. But you can still make use of duration.toMillis() and TimeUnit.MILLISECONDS for handling the scheduling computaions in milliseconds.

And also TimerTask is better for this or ScheduledExecutorService?

NO: ScheduledExecutorService seemingly better than TimerTask. StackOverflow has already an answer for you.

From @PaddyD,

You still have the issue whereby you need to restart this twice a year if you want it to run at the right local time. scheduleAtFixedRate won't cut it unless you are happy with the same UTC time all year.

As it is true and @PaddyD already has given a workaround(+1 to him), I am providing a working example with Java8 date time API with ScheduledExecutorService. Using daemon thread is dangerous

class MyTaskExecutor
{
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    MyTask myTask;
    volatile boolean isStopIssued;

    public MyTaskExecutor(MyTask myTask$) 
    {
        myTask = myTask$;

    }

    public void startExecutionAt(int targetHour, int targetMin, int targetSec)
    {
        Runnable taskWrapper = new Runnable(){

            @Override
            public void run() 
            {
                myTask.execute();
                startExecutionAt(targetHour, targetMin, targetSec);
            }

        };
        long delay = computeNextDelay(targetHour, targetMin, targetSec);
        executorService.schedule(taskWrapper, delay, TimeUnit.SECONDS);
    }

    private long computeNextDelay(int targetHour, int targetMin, int targetSec) 
    {
        LocalDateTime localNow = LocalDateTime.now();
        ZoneId currentZone = ZoneId.systemDefault();
        ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
        ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec);
        if(zonedNow.compareTo(zonedNextTarget) > 0)
            zonedNextTarget = zonedNextTarget.plusDays(1);

        Duration duration = Duration.between(zonedNow, zonedNextTarget);
        return duration.getSeconds();
    }

    public void stop()
    {
        executorService.shutdown();
        try {
            executorService.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException ex) {
            Logger.getLogger(MyTaskExecutor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Note:

  • MyTask is an interface with function execute.
  • While stopping ScheduledExecutorService, Always use awaitTermination after invoking shutdown on it: There's always a likelihood your task is stuck / deadlocking and the user would wait forever.

The previous example I gave with Calender was just an idea which I did mention, I avoided exact time calculation and Daylight saving issues. Updated the solution on per the complain of @PaddyD