Skip to content

Commit 3fa3680

Browse files
authored
Watcher: Properly find next valid date in cron expressions (#32734)
When a list/an array of cron expressions is provided, and one of those addresses is already expired, the expired one will be considered as an option instead of the valid next one. This commit also reduces the visibility of the CronnableSchedule and refactors a comparator to look like java 8.
1 parent dce72c7 commit 3fa3680

File tree

2 files changed

+30
-16
lines changed

2 files changed

+30
-16
lines changed

x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/trigger/schedule/CronnableSchedule.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,15 @@
1313

1414
public abstract class CronnableSchedule implements Schedule {
1515

16-
private static final Comparator<Cron> CRON_COMPARATOR = new Comparator<Cron>() {
17-
@Override
18-
public int compare(Cron c1, Cron c2) {
19-
return c1.expression().compareTo(c2.expression());
20-
}
21-
};
16+
private static final Comparator<Cron> CRON_COMPARATOR = Comparator.comparing(Cron::expression);
2217

2318
protected final Cron[] crons;
2419

25-
public CronnableSchedule(String... expressions) {
20+
CronnableSchedule(String... expressions) {
2621
this(crons(expressions));
2722
}
2823

29-
public CronnableSchedule(Cron... crons) {
24+
private CronnableSchedule(Cron... crons) {
3025
assert crons.length > 0;
3126
this.crons = crons;
3227
Arrays.sort(crons, CRON_COMPARATOR);
@@ -37,7 +32,15 @@ public long nextScheduledTimeAfter(long startTime, long time) {
3732
assert time >= startTime;
3833
long nextTime = Long.MAX_VALUE;
3934
for (Cron cron : crons) {
40-
nextTime = Math.min(nextTime, cron.getNextValidTimeAfter(time));
35+
long nextValidTimeAfter = cron.getNextValidTimeAfter(time);
36+
37+
boolean previousCronExpired = nextTime == -1;
38+
boolean currentCronValid = nextValidTimeAfter > -1;
39+
if (previousCronExpired && currentCronValid) {
40+
nextTime = nextValidTimeAfter;
41+
} else {
42+
nextTime = Math.min(nextTime, nextValidTimeAfter);
43+
}
4144
}
4245
return nextTime;
4346
}

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/trigger/schedule/CronScheduleTests.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
import org.elasticsearch.common.xcontent.XContentParser;
1212
import org.elasticsearch.common.xcontent.json.JsonXContent;
1313

14+
import java.time.ZoneOffset;
15+
import java.time.ZonedDateTime;
16+
1417
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
1518
import static org.hamcrest.Matchers.arrayWithSize;
1619
import static org.hamcrest.Matchers.hasItemInArray;
1720
import static org.hamcrest.Matchers.instanceOf;
1821
import static org.hamcrest.Matchers.is;
22+
import static org.hamcrest.Matchers.not;
1923

2024
public class CronScheduleTests extends ScheduleTestCase {
2125
public void testInvalid() throws Exception {
@@ -54,18 +58,25 @@ public void testParseMultiple() throws Exception {
5458
assertThat(crons, hasItemInArray("0 0/3 * * * ?"));
5559
}
5660

61+
public void testMultipleCronsNextScheduledAfter() {
62+
CronSchedule schedule = new CronSchedule("0 5 9 1 1 ? 2019", "0 5 9 1 1 ? 2020", "0 5 9 1 1 ? 2017");
63+
ZonedDateTime start2019 = ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
64+
ZonedDateTime start2020 = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
65+
long firstSchedule = schedule.nextScheduledTimeAfter(0, start2019.toInstant().toEpochMilli());
66+
long secondSchedule = schedule.nextScheduledTimeAfter(0, start2020.toInstant().toEpochMilli());
67+
68+
assertThat(firstSchedule, is(not(-1L)));
69+
assertThat(secondSchedule, is(not(-1L)));
70+
assertThat(firstSchedule, is(not(secondSchedule)));
71+
}
72+
5773
public void testParseInvalidBadExpression() throws Exception {
5874
XContentBuilder builder = jsonBuilder().value("0 0/5 * * ?");
5975
BytesReference bytes = BytesReference.bytes(builder);
6076
XContentParser parser = createParser(JsonXContent.jsonXContent, bytes);
6177
parser.nextToken();
62-
try {
63-
new CronSchedule.Parser().parse(parser);
64-
fail("expected cron parsing to fail when using invalid cron expression");
65-
} catch (ElasticsearchParseException pe) {
66-
// expected
67-
assertThat(pe.getCause(), instanceOf(IllegalArgumentException.class));
68-
}
78+
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> new CronSchedule.Parser().parse(parser));
79+
assertThat(e.getCause(), instanceOf(IllegalArgumentException.class));
6980
}
7081

7182
public void testParseInvalidEmpty() throws Exception {

0 commit comments

Comments
 (0)