The Answer by Bera is close to correct, but chooses not quite the right java.time classes & methods.
ZonedDateTime
& Instant
, not LocalDateTime
It incorrectly uses the LocalDateTime
class which lacks any concept of time zone or offset-from-UTC. So a LocalDateTime
does not represent a specific moment on the timeline.
Instead, use ZonedDateTime
to represent a specific moment with the wall-clock time used by the people of a particular region (a time zone).
A time zone is crucial in determining a date and a time-of-day. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime now = ZonedDateTime.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Extract the date-only portion, without a time-of-day and without a time zone. Any java.time class named “Local…” means it is “unzoned”, without any concept of zone or offset.
LocalDate ld = now.toLocalDate(); // Extract date-only without time-of-day and without offset-from-UTC.
Avoid thinking/talking about “midnight” as that becomes a slippery concept. Instead, focus on “the first moment of the day”. So we need the first moment of tomorrow.
Determine “tomorrow”.
LocalDate tomorrow = ld.plusDays( 1 ) ;
Let java.time determine the first moment. Do not assume that will be the time-of-day 00:00:00. Anomalies such as Daylight Saving Time (DST) mean the day may start at a time such as 01:00:00. Specify a time zone, of course, to handle any anomalies affecting the people of this region, this time zone.
ZonedDateTime tomorrowStart = tomorrow.atStartOfDay( z ) ;
Calculate the span of time from now until then, the start of tomorrow. The Duration
class represents a span of time unattached to the timeline.
Duration d = Duration.between( now , tomorrowStart ) ;
By the way, it may help your understanding to see those values in UTC. As a programmer (or sysadmin), you should be thinking in UTC. Forget about your own parochial time zone while on the job.
The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instantNow = now.toInstant() ;
Instant instantTomorrowStart = tomorrowStart.toInstant() ;
Duration d = Duration.between( instantNow , instantTomorrowStart ) ;
That pair of Instant
objects represent the very same moments as the pair of ZonedDateTime
objects. Same points on the timeline, but different wall-clock time. So the resulting Duration
calculation is the same.
ScheduledExecutorService::schedule
, not scheduleAtFixedRate
The other Answer is correct in suggesting ScheduledExecutorService
to schedule a task to be run at a certain time.
But the other Answer calls scheduleAtFixedRate
which repeatedly executes the same task (a Runnable
or Callable
). But the Question specifies the requirement that the task be run at the first moment of the day, everyday. That time of day may change on various dates, because of anomalies such as Daylight Saving Time (DST) or other changes to the offset-from-UTC used by the particular time zone. These changes happen surprisingly often as politicians around the world show a proclivity to making these changes. So if you really mean to check the first moment of the day, then you need to recalculate the waiting duration each and every time your run the task.
So you should ask your executor service to run the task only once. And at the end of that task, schedule another run for the following day.
Set up your executor service, and save a reference to it somewhere in your app. The Executors
class provides many such services. Get a count of either SECONDS
or NANOS
from generic Duration::get
method. Using nanoseconds is a bit silly as an executor is not guaranteed to run with such precision. There is a always a fudge-factor involved, as the load on your CPU’s cores, your host OS’ multi-tasking scheduling, your JVM’s threading, all may impact when your task actually gets run. So I use seconds here.
By the way, a bit of greybeard advice… Midnight is the witching hour on computers. This is when many OSes and apps engage in clean-up and roll-over activities such as making fresh log files. Some programmers and sysadmins choose to run a little while after midnight, or later in the middle of the night such as 2 or 3 AM.
ScheduledExecutorService executorService = Executors.newSingleThreadExecutor() ;
Calculate a delay until the first execution.
long countUntilNextTaskRun = d.getSeconds() ;
Schedule the task by calling ScheduledExecutorService.schedule
and passing the amount of time to wait.
executorService.schedule( myRunnableOrCallable , countUntilNextTaskRun , unit ) ;
You may want to capture the ScheduledFuture
for later use.
ScheduledFuture<?> scheduledFuture = executorService.schedule( myRunnableOrCallable , countUntilNextTaskRun , TimeUnit.SECONDS ) ;
This task will run only once. After that task does its main job have it also schedule the next run by using the same code seen above. Calculate the time until the next start-of-day in that particular time zone.
Lifecycle
The executor service lives in memory of your app. This has two important implications:
- Be absolutely sure to shutdown the executor service. Otherwise, the thread pool and scheduled task may remain in memory even after your app has closed (depending on the implementation of your JVM and host OS). So your app should always detect when it is closing/exiting, and shutdown the executor service.
- The executor service is not persisted after your app closes/exits. So when your app launches, your initialization/startup code must re-establish a fresh executor service.
If your business requirements ask that you catch-up on missed days of task-runs, then you will also need to program your task to record in some persistent manner the date-time (Instant
) when it last ran. Then you calculate how many days you missed using the same kind of classes and methods discussed above.
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?