diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index 319165d2bec..c4206c63534 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -100,9 +100,9 @@ public @interface Scheduled {
/**
* A time zone for which the cron expression will be resolved. By default, this
- * attribute is the empty String (i.e. the server's local time zone will be used).
+ * attribute is the empty String (i.e. the scheduler's time zone will be used).
* @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)},
- * or an empty String to indicate the server's default time zone
+ * or an empty String to indicate the scheduler's default time zone
* @since 4.0
* @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
* @see java.util.TimeZone
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
index db1372b70cb..28d1aea5f08 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -27,7 +27,6 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -84,7 +83,7 @@ import org.springframework.util.StringValueResolver;
* "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.
*
*
This post-processor is automatically registered by Spring's
- * {@code } XML element, and also by the
+ * {@code } XML element and also by the
* {@link EnableScheduling @EnableScheduling} annotation.
*
* Autodetects any {@link SchedulingConfigurer} instances in the container,
@@ -434,14 +433,14 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
- TimeZone timeZone;
+ CronTrigger trigger;
if (StringUtils.hasText(zone)) {
- timeZone = StringUtils.parseTimeZoneString(zone);
+ trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));
}
else {
- timeZone = TimeZone.getDefault();
+ trigger = new CronTrigger(cron);
}
- tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
+ tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));
}
}
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java
index e2e6687055e..2549faa3efe 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -57,18 +57,12 @@ public final class CronExpression {
private final String expression;
- private CronExpression(
- CronField seconds,
- CronField minutes,
- CronField hours,
- CronField daysOfMonth,
- CronField months,
- CronField daysOfWeek,
- String expression) {
+ private CronExpression(CronField seconds, CronField minutes, CronField hours,
+ CronField daysOfMonth, CronField months, CronField daysOfWeek, String expression) {
- // reverse order, to make big changes first
- // to make sure we end up at 0 nanos, we add an extra field
- this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
+ // Reverse order, to make big changes first.
+ // To make sure we end up at 0 nanos, we add an extra field.
+ this.fields = new CronField[] {daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
this.expression = expression;
}
@@ -121,11 +115,8 @@ public final class CronExpression {
* {@code LW}), it means "the last weekday of the month".
*
*
- * In the "day of week" field, {@code L} stands for "the last day of the
- * week".
- * If prefixed by a number or three-letter name (i.e. {@code dL} or
- * {@code DDDL}), it means "the last day of week {@code d} (or {@code DDD})
- * in the month".
+ * In the "day of week" field, {@code dL} or {@code DDDL} stands for
+ * "the last day of week {@code d} (or {@code DDD}) in the month".
*
*
*
@@ -177,7 +168,7 @@ public final class CronExpression {
* the cron format
*/
public static CronExpression parse(String expression) {
- Assert.hasLength(expression, "Expression string must not be empty");
+ Assert.hasLength(expression, "Expression must not be empty");
expression = resolveMacros(expression);
@@ -271,27 +262,18 @@ public final class CronExpression {
@Override
- public int hashCode() {
- return Arrays.hashCode(this.fields);
+ public boolean equals(@Nullable Object other) {
+ return (this == other || (other instanceof CronExpression &&
+ Arrays.equals(this.fields, ((CronExpression) other).fields)));
}
@Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o instanceof CronExpression) {
- CronExpression other = (CronExpression) o;
- return Arrays.equals(this.fields, other.fields);
- }
- else {
- return false;
- }
+ public int hashCode() {
+ return Arrays.hashCode(this.fields);
}
/**
* Return the expression string used to create this {@code CronExpression}.
- * @return the expression string
*/
@Override
public String toString() {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
index 6581cdd61fe..215ef40bbb1 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -39,6 +39,7 @@ public class CronTrigger implements Trigger {
private final CronExpression expression;
+ @Nullable
private final ZoneId zoneId;
@@ -48,7 +49,8 @@ public class CronTrigger implements Trigger {
* expression conventions
*/
public CronTrigger(String expression) {
- this(expression, ZoneId.systemDefault());
+ this.expression = CronExpression.parse(expression);
+ this.zoneId = null;
}
/**
@@ -58,7 +60,9 @@ public class CronTrigger implements Trigger {
* @param timeZone a time zone in which the trigger times will be generated
*/
public CronTrigger(String expression, TimeZone timeZone) {
- this(expression, timeZone.toZoneId());
+ this.expression = CronExpression.parse(expression);
+ Assert.notNull(timeZone, "TimeZone must not be null");
+ this.zoneId = timeZone.toZoneId();
}
/**
@@ -70,10 +74,8 @@ public class CronTrigger implements Trigger {
* @see CronExpression#parse(String)
*/
public CronTrigger(String expression, ZoneId zoneId) {
- Assert.hasLength(expression, "Expression must not be empty");
- Assert.notNull(zoneId, "ZoneId must not be null");
-
this.expression = CronExpression.parse(expression);
+ Assert.notNull(zoneId, "ZoneId must not be null");
this.zoneId = zoneId;
}
@@ -94,22 +96,23 @@ public class CronTrigger implements Trigger {
*/
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
- Date date = triggerContext.lastCompletionTime();
- if (date != null) {
+ Date timestamp = triggerContext.lastCompletionTime();
+ if (timestamp != null) {
Date scheduled = triggerContext.lastScheduledExecutionTime();
- if (scheduled != null && date.before(scheduled)) {
+ if (scheduled != null && timestamp.before(scheduled)) {
// Previous task apparently executed too early...
// Let's simply use the last calculated execution time then,
// in order to prevent accidental re-fires in the same second.
- date = scheduled;
+ timestamp = scheduled;
}
}
else {
- date = new Date(triggerContext.getClock().millis());
+ timestamp = new Date(triggerContext.getClock().millis());
}
- ZonedDateTime dateTime = ZonedDateTime.ofInstant(date.toInstant(), this.zoneId);
- ZonedDateTime next = this.expression.next(dateTime);
- return (next != null ? Date.from(next.toInstant()) : null);
+ ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone());
+ ZonedDateTime zonedTimestamp = ZonedDateTime.ofInstant(timestamp.toInstant(), zone);
+ ZonedDateTime nextTimestamp = this.expression.next(zonedTimestamp);
+ return (nextTimestamp != null ? Date.from(nextTimestamp.toInstant()) : null);
}
diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java
index 1fe501b1301..14185a30096 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -848,6 +848,7 @@ class CronTriggerTests {
assertThat(nextExecutionTime).isEqualTo(this.calendar.getTime());
}
+
private static void roundup(Calendar calendar) {
calendar.add(Calendar.SECOND, 1);
calendar.set(Calendar.MILLISECOND, 0);
@@ -861,9 +862,7 @@ class CronTriggerTests {
}
private static TriggerContext getTriggerContext(Date lastCompletionTime) {
- SimpleTriggerContext context = new SimpleTriggerContext();
- context.update(null, null, lastCompletionTime);
- return context;
+ return new SimpleTriggerContext(null, null, lastCompletionTime);
}