tl;dr
OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ; // Capture the current moment.
….scheduleAtFixedRate(
new Runnable() { … } , // Define task to be executed as a `Runnable`.
Duration.between( // Determine amount of time for initial delay until first execution of our Runnable.
now , // Current moment.
now.toLocalDate().plusDays( 1 ).atStartOfDay( ZoneOffset.UTC ) // Determine the first moment of tomorrow in our target time zone (UTC). Used as the exclusive end of our Half-Open span of time.
) ,
TimeUnit.DAYS.toMillis( 1 ) , // Amount of time between subsequent executions of our Runnable. Use self-documenting code rather than a “magic number” such as `86400000`.
TimeUnit.MILLISECONDS // Specify the granularity of time used in previous pair of arguments.
) // Returns a `ScheduledFuture` which you may want to cache.
Details
Specify zone explicitly
You are assuming the JVM’s current time zone is your desired UTC. You omit the optional time zone argument when calling the date-time methods. That omission means the JVM’s current default time zone is applied implicitly and silently at runtime. That default may change at any moment. Any code in any thread of any app within that JVM can change the default during runtime(!).
Instead of relying implicitly on the JVM’s current default time zone, always specify your desired/expected zone explicitly. In your case, we want ZoneOffset.UTC
. Instead of assuming/hoping the deployment JVM’s current default is set to UTC, and stays at UTC, specify explicitly using the constant.
You seem to be using the excellent Joda-Time library. That project is now in maintenance mode, with the team advising migration to the java.time classes. Same basic concepts, as Joda-Time inspired java.time.
First get the current moment as seen in UTC.
OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC );
Extract a date-only value from that. Add one to get tomorrow’s date.
LocalDate today = now.toLocalDate();
LocalDate tomorrow = today.plusDays( 1 );
The term “midnight” can be ambiguous and confusing. Instead, focus on the concept of “first moment of the day”.
We are aiming for the amount of time to delay until your first execution of your executor service. So we need the span of time between now and the first moment of tomorrow.
And when determining a span of time, use the Half-Open method when the beginning is inclusive while the ending is exclusive. So our span of time starts with now (the current moment) and runs up to, but does not include) the first moment of tomorrow.
Let java.time determine the first moment of the day tomorrow. In UTC the day always starts at 00:00. But not so in some time zones on some dates, where the day might start at a time like 01:00. So, as a habit, always let java.time determine first moment of the day.
OffsetDateTime tomorrowStart = OffsetDateTime.of( tomorrow , LocalTime.MIN , ZoneOffset.UTC );
Calculate the elapsed time between now and that first moment of tomorrow. The Duration
class represents such spans of time unattached to the timeline.
Duration d = Duration.between( now , tomorrowStart );
long millisUntilTomorrowStart = d.toMillis();
Instead of a mysterious number literal such as 86400000
, use a self-documenting call.
TimeUnit.DAYS.toMillis( 1 )
So your ScheduledExecutorService
call would look like this:
….scheduleAtFixedRate(
new Runnable() { … } , // Task to be executed repeatedly, defined as a Runnable.
millisUntilTomorrowStart , // Initial delay, before first execution. Use this to get close to first moment of tomorrow in UTC per our code above.
TimeUnit.DAYS.toMillis( 1 ) , // Amount of time in each interval, between subsequent executions of our Runnable.
TimeUnit.MILLISECONDS // Unit of time intended by the numbers in previous two arguments.
)
For incrementing in whole days, you needn't use such a fine granularity as milliseconds. The executors do not run with perfect timing for various reasons. So I probably would have calculated in minutes. But not important.
Very important: You need to enclose the code of your Runnable’s run
method in a trap for any exception. If an Exception of any type were to reach the executor, the executor silently halts. No further scheduling of tasks, and no warning. Search Stack Overflow for more info including an Answer by me.
You do not explain what is the object on which you call scheduleAtFixedRate
. So that is a major part of the code that we cannot help with until you post more info. I'm concerned you have it named “Thread”. That object must be an implementation of ScheduledExecutorService
, not a thread.
Tip: Avoid running things at exactly midnight. Many things tend to happen on computers at midnight. For example, leap second adjustments, many Unix cleanup utilities, and routine activities such as backups that may have been scheduled by naïve administrators. Waiting something like five or fifteen minutes may avoid hassles and mysterious problems.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.