diff --git a/spring-context/src/main/java/org/springframework/scheduling/TaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/TaskScheduler.java index cc729389f4..326923d10d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/TaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/TaskScheduler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.scheduling; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Date; @@ -49,6 +50,15 @@ import org.springframework.lang.Nullable; */ public interface TaskScheduler { + /** + * Return the clock to use for scheduling purposes. + * @since 5.3 + * @see Clock#systemDefaultZone() + */ + default Clock getClock() { + return Clock.systemDefaultZone(); + } + /** * Schedule the given {@link Runnable}, invoking it whenever the trigger * indicates a next execution time. diff --git a/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java b/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java index bff852e424..f50421eaa1 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java +++ b/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.scheduling; +import java.time.Clock; import java.util.Date; import org.springframework.lang.Nullable; @@ -29,6 +30,16 @@ import org.springframework.lang.Nullable; */ public interface TriggerContext { + /** + * Return the clock to use for trigger calculation. + * @since 5.3 + * @see TaskScheduler#getClock() + * @see Clock#systemDefaultZone() + */ + default Clock getClock() { + return Clock.systemDefaultZone(); + } + /** * Return the last scheduled execution time of the task, * or {@code null} if not scheduled before. diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java index 4e3827bdb1..887c3f5965 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.scheduling.concurrent; +import java.time.Clock; import java.util.Date; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -89,6 +90,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T @Nullable private ErrorHandler errorHandler; + private Clock clock = Clock.systemDefaultZone(); + /** * Create a new ConcurrentTaskScheduler, @@ -168,6 +171,21 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T this.errorHandler = errorHandler; } + /** + * Set the clock to use for scheduling purposes. + *
The default clock is the system clock for the default time zone. + * @since 5.3 + * @see Clock#systemDefaultZone() + */ + public void setClock(Clock clock) { + this.clock = clock; + } + + @Override + public Clock getClock() { + return this.clock; + } + @Override @Nullable @@ -179,7 +197,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T else { ErrorHandler errorHandler = (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true)); - return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule(); + return new ReschedulingRunnable(task, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule(); } } catch (RejectedExecutionException ex) { @@ -189,7 +207,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T @Override public ScheduledFuture> schedule(Runnable task, Date startTime) { - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay, TimeUnit.MILLISECONDS); } @@ -200,7 +218,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T @Override public ScheduledFuture> scheduleAtFixedRate(Runnable task, Date startTime, long period) { - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS); } @@ -221,7 +239,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T @Override public ScheduledFuture> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ReschedulingRunnable.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ReschedulingRunnable.java index 7a1ee37bc8..ca79c80fc6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ReschedulingRunnable.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ReschedulingRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.scheduling.concurrent; +import java.time.Clock; import java.util.Date; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; @@ -47,7 +48,7 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc private final Trigger trigger; - private final SimpleTriggerContext triggerContext = new SimpleTriggerContext(); + private final SimpleTriggerContext triggerContext; private final ScheduledExecutorService executor; @@ -60,11 +61,12 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc private final Object triggerContextMonitor = new Object(); - public ReschedulingRunnable( - Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) { + public ReschedulingRunnable(Runnable delegate, Trigger trigger, Clock clock, + ScheduledExecutorService executor, ErrorHandler errorHandler) { super(delegate, errorHandler); this.trigger = trigger; + this.triggerContext = new SimpleTriggerContext(clock); this.executor = executor; } @@ -76,7 +78,7 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc if (this.scheduledExecutionTime == null) { return null; } - long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis(); + long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis(); this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS); return this; } @@ -89,9 +91,9 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc @Override public void run() { - Date actualExecutionTime = new Date(); + Date actualExecutionTime = new Date(this.triggerContext.getClock().millis()); super.run(); - Date completionTime = new Date(); + Date completionTime = new Date(this.triggerContext.getClock().millis()); synchronized (this.triggerContextMonitor) { Assert.state(this.scheduledExecutionTime != null, "No scheduled execution"); this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java index fb954082f1..de7bdb286a 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java @@ -16,6 +16,7 @@ package org.springframework.scheduling.concurrent; +import java.time.Clock; import java.util.Date; import java.util.Map; import java.util.concurrent.Callable; @@ -66,6 +67,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @Nullable private volatile ErrorHandler errorHandler; + private Clock clock = Clock.systemDefaultZone(); + @Nullable private ScheduledExecutorService scheduledExecutor; @@ -110,6 +113,21 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport this.errorHandler = errorHandler; } + /** + * Set the clock to use for scheduling purposes. + *
The default clock is the system clock for the default time zone. + * @since 5.3 + * @see Clock#systemDefaultZone() + */ + public void setClock(Clock clock) { + this.clock = clock; + } + + @Override + public Clock getClock() { + return this.clock; + } + @Override protected ExecutorService initializeExecutor( @@ -310,7 +328,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport if (errorHandler == null) { errorHandler = TaskUtils.getDefaultErrorHandler(true); } - return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule(); + return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule(); } catch (RejectedExecutionException ex) { throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex); @@ -320,7 +338,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @Override public ScheduledFuture> schedule(Runnable task, Date startTime) { ScheduledExecutorService executor = getScheduledExecutor(); - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return executor.schedule(errorHandlingTask(task, false), initialDelay, TimeUnit.MILLISECONDS); } @@ -332,7 +350,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @Override public ScheduledFuture> scheduleAtFixedRate(Runnable task, Date startTime, long period) { ScheduledExecutorService executor = getScheduledExecutor(); - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS); } @@ -355,7 +373,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @Override public ScheduledFuture> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { ScheduledExecutorService executor = getScheduledExecutor(); - long initialDelay = startTime.getTime() - System.currentTimeMillis(); + long initialDelay = startTime.getTime() - this.clock.millis(); try { return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java index c237892887..3b9cc27aa1 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -470,7 +470,7 @@ public class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing } if (this.taskScheduler != null) { if (task.getInitialDelay() > 0) { - Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay()); + Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay()); scheduledTask.future = this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval()); } @@ -519,7 +519,7 @@ public class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing } if (this.taskScheduler != null) { if (task.getInitialDelay() > 0) { - Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay()); + Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay()); scheduledTask.future = this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval()); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java index 8331b7163f..8e19a265f0 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java @@ -134,7 +134,7 @@ public class PeriodicTrigger implements Trigger { Date lastExecution = triggerContext.lastScheduledExecutionTime(); Date lastCompletion = triggerContext.lastCompletionTime(); if (lastExecution == null || lastCompletion == null) { - return new Date(System.currentTimeMillis() + this.initialDelay); + return new Date(triggerContext.getClock().millis() + this.initialDelay); } if (this.fixedRate) { return new Date(lastExecution.getTime() + this.period); diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/SimpleTriggerContext.java b/spring-context/src/main/java/org/springframework/scheduling/support/SimpleTriggerContext.java index 0493ac4b12..2c647c34c8 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/SimpleTriggerContext.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/SimpleTriggerContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.scheduling.support; +import java.time.Clock; import java.util.Date; import org.springframework.lang.Nullable; @@ -29,6 +30,8 @@ import org.springframework.scheduling.TriggerContext; */ public class SimpleTriggerContext implements TriggerContext { + private final Clock clock; + @Nullable private volatile Date lastScheduledExecutionTime; @@ -40,23 +43,38 @@ public class SimpleTriggerContext implements TriggerContext { /** - * Create a SimpleTriggerContext with all time values set to {@code null}. + * Create a SimpleTriggerContext with all time values set to {@code null}, + * exposing the system clock for the default time zone. */ public SimpleTriggerContext() { + this.clock = Clock.systemDefaultZone(); } /** - * Create a SimpleTriggerContext with the given time values. + * Create a SimpleTriggerContext with the given time values, + * exposing the system clock for the default time zone. * @param lastScheduledExecutionTime last scheduled execution time * @param lastActualExecutionTime last actual execution time * @param lastCompletionTime last completion time */ public SimpleTriggerContext(Date lastScheduledExecutionTime, Date lastActualExecutionTime, Date lastCompletionTime) { + this(); this.lastScheduledExecutionTime = lastScheduledExecutionTime; this.lastActualExecutionTime = lastActualExecutionTime; this.lastCompletionTime = lastCompletionTime; } + /** + * Create a SimpleTriggerContext with all time values set to {@code null}, + * exposing the given clock. + * @param clock the clock to use for trigger calculation + * @since 5.3 + * @see #update(Date, Date, Date) + */ + public SimpleTriggerContext(Clock clock) { + this.clock = clock; + } + /** * Update this holder's state with the latest time values. @@ -71,6 +89,11 @@ public class SimpleTriggerContext implements TriggerContext { } + @Override + public Clock getClock() { + return this.clock; + } + @Override @Nullable public Date lastScheduledExecutionTime() {