The following code block does not work for me (alert is not being triggered):
public static void main(String[] args) throws InterruptedException, ParseException {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(Thread::new);
TimeZone timeZone = TimeZone.getTimeZone(ZoneId.systemDefault());
Calendar calendar = Calendar.getInstance(timeZone);
Scanner scanner = new Scanner(System.in);
System.out.println("The time now is: " + calendar.getTime());
System.out.println("Enter alert date time: ");
String dateStr = scanner.nextLine();
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
Date date = sdf.parse(dateStr);
calendar.setTime(date);
long alertTimeInMillis = calendar.getTimeInMillis();
long now = Calendar.getInstance(timeZone).getTimeInMillis();
System.out.println("Time to alert: " + (alertTimeInMillis - now) + " millis ");
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(() -> System.out.println("alert!")
, alertTimeInMillis, TimeUnit.MILLISECONDS);
while (!scheduledFuture.isDone()) {
System.out.println("The time now: " + Calendar.getInstance(timeZone).getTime());
System.out.println("Expected alert time: " + date);
Thread.sleep(1000);
}
scheduledExecutorService.shutdown();
scheduledExecutorService.awaitTermination(30, TimeUnit.SECONDS);
}
While this code block does work:
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(Thread::new);
LocalDateTime localDateTime = LocalDateTime.of(2023, 1, 10, 12, 1);
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(() ->
System.out.println("alert!"),
LocalDateTime.now().until(localDateTime, ChronoUnit.SECONDS), TimeUnit.SECONDS);
while (!scheduledFuture.isDone()) {
Thread.sleep(1000);
}
scheduledExecutorService.shutdown();
scheduledExecutorService.awaitTermination(30, TimeUnit.SECONDS);
}
I don't understand the difference, or what exactly is wrong with the first block that I'm missing.
Debugging legacy code
No need for that
Thread::new. Use empty parens, no arguments needed.Weird. You are mixing the legacy
TimeZoneclass with its replacementZoneIdfrom java.time. Use only java.time; never use the terribly flawed legacy date-time classes.New conversion methods were added to the old classes for writing code to interoperate with old code not yet updated to java.time. TimeZone.getTimeZone( ZoneId ) is one such conversion method.
Also, be aware that the JVM’s current default time zone can be changed at any moment by any code in any thread. Consider if you really want your code to spin the wheel on time-zone-roulette at runtime.
Your formatting code uses
hhin lowercase. This means 12-hour clock. But you neglected to collect an indicator of which half of the day. If the user types a time for02:00:00, we cannot know if they meant 2 AM or 2 PM. I will change this toHHfor 24-hour clock in example code below.Be aware that
SimpleDateFormatis using a default time zone to parse that date-with-time string. You happen to want the default time zone in your earlier code, so this may work out well. Or this may not work out well if some code has changed your JVM’s current default time zone in the interim.This line is the source of your problem. You told the scheduled executor service to wait a number of milliseconds before executing. Your number of executors is the number of milliseconds since the epoch reference of first moment of 1970 in UTC — decades, that is. You told the executor service to wait decades, about 53 years (2023-1970 = 53). Which the executor service will do, faithfully, if you leave your computer running that long.
Your code:
… calls the
Calendar#getTimeInMillismethod. Javadoc says:What you meant to do is what you did do in your
System.out.println: Calculate time to elapse between the current moment and that target moment.Tip: Generally best to log existing values, rather than generating a value to log.
Modern solution: java.time
But enough of struggling with the terribly flawed legacy date-time classes. Never use
Calendar,Date, orSimpleDateFormat. Always use java.time classes.Firstly, if using an executor service, copy the boilerplate code for a graceful shutdown. Find that code on
ExecutorServiceJavadoc. Here is a slightly modified version.Here is complete example code.
Let's break up your long code into a few separate methods, each with a specific goal.
To represent a span of time not attached to the timeline, use
Durationclass.Notice how we minimized the use of
LocalDateTime. That class cannot represent a moment, a point on the timeline as it lacks the context of a time zone or offset-from-UTC. I cannot imagine a situation where callingLocalDateTime.nowis optimal.Where run:
My example code when run was aiming to run the task at 2023-01-10T14:30-08:00[America/Los_Angeles]. That is the exact same moment, same point on the timeline as 2023-01-10T22:30:00Z where the “Z” means an offset from UTC of zero hours-minutes-seconds. The 14:30 in America/Los_Angeles is 8 hours behind UTC, so adding 8 hours brings us to 22:30 in UTC — same moment, different wall-clock time.
In other words… Alice in Portland Oregon notices her clock on the wall strike 2:30 PM (14:30) as she dials the phone to call Bob in Reykjavík. As Bob answers the call, he notices his own clock on the wall reads 10:30 PM (22:30). Same moment, different wall-clock time.
By the way, be aware that lines sent to
System.out.printlnacross threads may not appear in the console in chronological order. Always include a timestamp such asInstant.now(). If you care about sequence, study those timestamps.