Solución:
Al igual que con la versión actual de Java SE 8 con su excelente API de fecha y hora con java.time
este tipo de cálculo se puede hacer más fácilmente en lugar de utilizar java.util.Calendar
y java.util.Date
.
- Use la clase de fecha y hora, es decir, LocalDateTime de esta nueva API
- Utilice la clase ZonedDateTime para manejar el cálculo específico de la zona horaria, incluidos los problemas de horario de verano. Encontrarás un tutorial y un ejemplo aquí.
Ahora, como un ejemplo de muestra para programar una tarea con su caso de uso:
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);
los initalDelay
se calcula para pedirle al planificador que retrase la ejecución en TimeUnit.SECONDS
. Los problemas de diferencia de tiempo con unidades de milisegundos e inferiores parecen ser insignificantes para este caso de uso. Pero aún puedes hacer uso de duration.toMillis()
y TimeUnit.MILLISECONDS
para manejar los cálculos de programación en milisegundos.
¿Y también TimerTask es mejor para esto o ScheduledExecutorService?
NO: ScheduledExecutorService
aparentemente mejor que TimerTask
. StackOverflow ya tiene una respuesta para ti.
De @PaddyD,
Todavía tiene el problema por el cual debe reiniciar esto dos veces al año si desea que se ejecute a la hora local correcta. scheduleAtFixedRate no lo reducirá a menos que esté satisfecho con la misma hora UTC durante todo el año.
Como es cierto y @PaddyD ya le ha dado una solución (+1 para él), estoy proporcionando un ejemplo de trabajo con la API de fecha y hora de Java8 con ScheduledExecutorService
. Usar hilo de demonio es peligroso
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);
}
}
}
Nota:
-
MyTask
es una interfaz con funciónexecute
. - Mientras se detiene
ScheduledExecutorService
, Siempre usaawaitTermination
después de invocarshutdown
en él: siempre existe la posibilidad de que su tarea se bloquee / bloquee y el usuario esperaría para siempre.
El ejemplo anterior que di con Calender fue solo un idea que mencioné, evité el cálculo de la hora exacta y los problemas de horario de verano. Se actualizó la solución según la queja de @PaddyD
En Java 8:
scheduler = Executors.newScheduledThreadPool(1);
//Change here for the hour you want ----------------------------------.at()
Long midnight=LocalDateTime.now().until(LocalDate.now().plusDays(1).atStartOfDay(), ChronoUnit.MINUTES);
scheduler.scheduleAtFixedRate(this, midnight, 1440, TimeUnit.MINUTES);
Si no tiene el lujo de poder utilizar Java 8, lo siguiente hará lo que necesite:
public class DailyRunnerDaemon
{
private final Runnable dailyTask;
private final int hour;
private final int minute;
private final int second;
private final String runThreadName;
public DailyRunnerDaemon(Calendar timeOfDay, Runnable dailyTask, String runThreadName)
{
this.dailyTask = dailyTask;
this.hour = timeOfDay.get(Calendar.HOUR_OF_DAY);
this.minute = timeOfDay.get(Calendar.MINUTE);
this.second = timeOfDay.get(Calendar.SECOND);
this.runThreadName = runThreadName;
}
public void start()
{
startTimer();
}
private void startTimer();
{
new Timer(runThreadName, true).schedule(new TimerTask()
{
@Override
public void run()
{
dailyTask.run();
startTimer();
}
}, getNextRunTime());
}
private Date getNextRunTime()
{
Calendar startTime = Calendar.getInstance();
Calendar now = Calendar.getInstance();
startTime.set(Calendar.HOUR_OF_DAY, hour);
startTime.set(Calendar.MINUTE, minute);
startTime.set(Calendar.SECOND, second);
startTime.set(Calendar.MILLISECOND, 0);
if(startTime.before(now) || startTime.equals(now))
{
startTime.add(Calendar.DATE, 1);
}
return startTime.getTime();
}
}
No requiere bibliotecas externas y tendrá en cuenta el horario de verano. Simplemente pase la hora del día en la que desea ejecutar la tarea como Calendar
objeto, y la tarea como un Runnable
. Por ejemplo:
Calendar timeOfDay = Calendar.getInstance();
timeOfDay.set(Calendar.HOUR_OF_DAY, 5);
timeOfDay.set(Calendar.MINUTE, 0);
timeOfDay.set(Calendar.SECOND, 0);
new DailyRunnerDaemon(timeOfDay, new Runnable()
{
@Override
public void run()
{
try
{
// call whatever your daily task is here
doHousekeeping();
}
catch(Exception e)
{
logger.error("An error occurred performing daily housekeeping", e);
}
}
}, "daily-housekeeping");
Nota: la tarea del temporizador se ejecuta en un subproceso de Daemon que no se recomienda para realizar ninguna E / S. Si necesita utilizar un hilo de usuario, deberá agregar otro método que cancele el temporizador.
Si tiene que usar un ScheduledExecutorService
, simplemente cambie el startTimer
método a lo siguiente:
private void startTimer()
{
Executors.newSingleThreadExecutor().schedule(new Runnable()
{
Thread.currentThread().setName(runThreadName);
dailyTask.run();
startTimer();
}, getNextRunTime().getTime() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
}
No estoy seguro del comportamiento, pero es posible que necesite un método de detención que llame shutdownNow
si bajas por el ScheduledExecutorService
route, de lo contrario, su aplicación puede bloquearse cuando intente detenerla.