Default time zone resolution from scheduler-wide Clock
Closes gh-31948
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>This post-processor is automatically registered by Spring's
|
||||
* {@code <task:annotation-driven>} XML element, and also by the
|
||||
* {@code <task:annotation-driven>} XML element and also by the
|
||||
* {@link EnableScheduling @EnableScheduling} annotation.
|
||||
*
|
||||
* <p>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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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".
|
||||
* </li>
|
||||
* <li>
|
||||
* 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".
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
@@ -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);
|
||||
|
||||
@@ -270,28 +261,19 @@ public final class CronExpression {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof CronExpression &&
|
||||
Arrays.equals(this.fields, ((CronExpression) other).fields)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this.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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the expression string used to create this {@code CronExpression}.
|
||||
* @return the expression string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user