Execute task each day at precise hour

Hello there,

I’d like my plugin to execute a task each day at a fixed hour, let’s say midnight. To do so when my plugin starts, I submit to the TaskBuilder my task with as an interval 1 day. But for my task to be executed at midnight, I’d like to set as a delay the time until next midnight.

I tried to do so but it the delay is never correct.

taskBuilder
.execute(new MyRunnable())
// 8640000 ms = 1 day
.delay(8640000L - Calendar.getInstance().getTime().getTime() % 8640000L, TimeUnit.MILLISECONDS)
.interval(1, TimeUnit.DAYS)
.submit(this);

My reasoning must be wrong… Any ideas ?

LocalDateTime (Java Platform SE 8 ) it could help you to calculate the time until midnight.

This isn’t an async task, is it?
My guess is, that sync tasks are converting all time values into game-ticks, and rely on them. That would mean if the server isn’t running at exactly 20tps all the time, your interval gets longer or shorter…

Async tasks don’t rely on game ticks, they work with the system-time.
So I’d suggest you to try to create an async task, and (if u need Sponge-API access) sync again by creating another sync-task within that async task… If you understand what i mean :smiley:

It’s late, and my English is bad, so apologize that sloppy explanation ^^

2 Likes

In Java 8 never use java.util.Calendar or java.util.Date! Both where added to Java a long time ago and have serious flaws. Java 8 finally added a useful API that should be used instead.

Calculating the duration between two fixed times is surprisingly complex as you need to take leap seconds and daylight saving times into account. Luckily that is all possible with the new API; an example is given here.

To actually run the task, you can use the scheduler directly {see @simon816’s answer).

2 Likes

Thank you for your answers.

So to calculate the delay, thanks to @TheE’s answer I came up with this (for those who would one day have the same problem as me)

LocalDateTime localNow = LocalDateTime.now();
ZonedDateTime zonedNow = ZonedDateTime.of(localNow, ZoneId.systemDefault());
ZonedDateTime zonedNext = zonedNow.withHour(0).withMinute(0).withSecond(0);
if(zonedNow.compareTo(zonedNext) > 0)
    zonedNext = zonedNext.plusDays(1);
long delay = Duration.between(zonedNow, zonedNext).getSeconds();

So to follow @Blue’s advice, does that seem correct ?

MyPlugin.getTaskBuilder().execute(() -> {
    MyPlugin
    .getTaskBuilder()
    .execute(new MyTrueRunnable())
    .submit(MyPlugin.getInstance());
})
.delay(delay, TimeUnit.SECONDS)
.interval(1, TimeUnit.DAYS)
.async()
.submit(this);

Thanks again :smile:

I don’t think what @Blue mentioned is required, if the delay or interval is specified using the interval/delay(amount, unit) methods then it will use real time instead of ticks to determine when the task has to be run.

The problem is the calculation of the delay time - the interval is easy, but the server could be rebooted at any time of day, and calculating the time left until midnight for the delay is the crux of the problem here.

With LocalDateTime you can calculate the time between two moments. That means, each times the server boot, you calculate the time until the next midnight.

EDIT: I have done something like that with my plugin.

I think the appropriate api is the until API

So to get midnight we use LocalDate.now().plusDays(1).atStartOfDay() which we can compare with LocalDateTime.now() to get:

LocalDateTime now = LocalDateTime.now();
LocalDateTime midnight = LocalDate.now().plusDays(1).atStartOfDay();
long millisUntilMidnight = now.until(midnight, ChronoUnit.MILLIS);

This is actually not correct. I designed the sync scheduler to go off real time if you give it a value in real time (i.e. using delay or interval). To go based of ticks you have to use delayTicks or intervalTicks. Because it is a sync scheduler it will not be exactly on time, but near enough. it likely won’t be more than 50ms out.

1 Like

Yeah, i just guessed. Didn’t check the code. But i like that i was not right! The scheduler is really good designed =)

That is even better!

Could you perhaps also clearly state this in the javadocs of the corresponding methods?