Skip to content

Commit fb0361b

Browse files
committed
Introduce CronExpression
This commit introduces CronExpression, a new for representing cron expressions, and a direct replacement for CronSequenceGenerator.
1 parent 05683fe commit fb0361b

File tree

8 files changed

+1195
-15
lines changed

8 files changed

+1195
-15
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.scheduling.support;
18+
19+
import java.time.temporal.ChronoUnit;
20+
import java.time.temporal.Temporal;
21+
import java.util.Arrays;
22+
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.util.Assert;
25+
import org.springframework.util.StringUtils;
26+
27+
/**
28+
* Representation of a
29+
* <a href="https://www.manpagez.com/man/5/crontab/">crontab expression</a>
30+
* that can calculate the next time it matches.
31+
*
32+
* <p>{@code CronExpression} instances are created through
33+
* {@link #parse(String)}; the next match is determined with
34+
* {@link #next(Temporal)}.
35+
*
36+
* @author Arjen Poutsma
37+
* @since 5.3
38+
* @see CronTrigger
39+
*/
40+
public final class CronExpression {
41+
42+
static final int MAX_ATTEMPTS = 366;
43+
44+
45+
private final CronField[] fields;
46+
47+
private final String expression;
48+
49+
50+
private CronExpression(
51+
CronField seconds,
52+
CronField minutes,
53+
CronField hours,
54+
CronField daysOfMonth,
55+
CronField months,
56+
CronField daysOfWeek,
57+
String expression) {
58+
59+
// to make sure we end up at 0 nanos, we add an extra field
60+
this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
61+
this.expression = expression;
62+
}
63+
64+
65+
/**
66+
* Parse the given expression string into a {@code CronExpression}.
67+
* The string has six single space-separated fields, representing
68+
* second, minute, hour, day, month, weekday. Month and weekday names can be
69+
* given as the first three letters of the English names.
70+
*
71+
* <p>Example expressions:
72+
* <ul>
73+
* <li>{@code "0 0 * * * *"} = the top of every hour of every day.</li>
74+
* <li><code>"*&#47;10 * * * * *"</code> = every ten seconds.</li>
75+
* <li>{@code "0 0 8-10 * * *"} = 8, 9 and 10 o'clock of every day.</li>
76+
* <li>{@code "0 0 6,19 * * *"} = 6:00 AM and 7:00 PM every day.</li>
77+
* <li>{@code "0 0/30 8-10 * * *"} = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.</li>
78+
* <li>{@code "0 0 9-17 * * MON-FRI"} = on the hour nine-to-five weekdays</li>
79+
* <li>{@code "0 0 0 25 12 ?"} = every Christmas Day at midnight</li>
80+
* </ul>
81+
*
82+
* @param expression the expression string to parse
83+
* @return the parsed {@code CronExpression} object
84+
* @throws IllegalArgumentException in the expression does not conform to
85+
* the cron format
86+
*/
87+
public static CronExpression parse(String expression) {
88+
Assert.hasLength(expression, "Expression string must not be empty");
89+
90+
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
91+
if (fields.length != 6) {
92+
throw new IllegalArgumentException(String.format(
93+
"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
94+
}
95+
try {
96+
CronField seconds = CronField.parseSeconds(fields[0]);
97+
CronField minutes = CronField.parseMinutes(fields[1]);
98+
CronField hours = CronField.parseHours(fields[2]);
99+
CronField daysOfMonth = CronField.parseDaysOfMonth(fields[3]);
100+
CronField months = CronField.parseMonth(fields[4]);
101+
CronField daysOfWeek = CronField.parseDaysOfWeek(fields[5]);
102+
103+
return new CronExpression(seconds, minutes, hours, daysOfMonth, months, daysOfWeek, expression);
104+
}
105+
catch (IllegalArgumentException ex) {
106+
String msg = ex.getMessage() + " in cron expression \"" + expression + "\"";
107+
throw new IllegalArgumentException(msg, ex);
108+
}
109+
}
110+
111+
112+
/**
113+
* Calculate the next {@link Temporal} that matches this expression.
114+
* @param temporal the seed value
115+
* @param <T> the type of temporal
116+
* @return the next temporal that matches this expression, or {@code null}
117+
* if no such temporal can be found
118+
*/
119+
@Nullable
120+
public <T extends Temporal> T next(T temporal) {
121+
return nextOrSame(ChronoUnit.NANOS.addTo(temporal, 1));
122+
}
123+
124+
125+
@Nullable
126+
private <T extends Temporal> T nextOrSame(T temporal) {
127+
for (int i = 0; i < MAX_ATTEMPTS; i++) {
128+
T result = nextOrSameInternal(temporal);
129+
if (result == null || result.equals(temporal)) {
130+
return result;
131+
}
132+
temporal = result;
133+
}
134+
return null;
135+
}
136+
137+
@Nullable
138+
private <T extends Temporal> T nextOrSameInternal(T temporal) {
139+
for (CronField field : this.fields) {
140+
temporal = field.nextOrSame(temporal);
141+
if (temporal == null) {
142+
return null;
143+
}
144+
}
145+
return temporal;
146+
}
147+
148+
149+
@Override
150+
public int hashCode() {
151+
return Arrays.hashCode(this.fields);
152+
}
153+
154+
@Override
155+
public boolean equals(Object o) {
156+
if (this == o) {
157+
return true;
158+
}
159+
if (o instanceof CronExpression) {
160+
CronExpression other = (CronExpression) o;
161+
return Arrays.equals(this.fields, other.fields);
162+
}
163+
else {
164+
return false;
165+
}
166+
}
167+
168+
/**
169+
* Return the expression string used to create this {@code CronExpression}.
170+
* @return the expression string
171+
*/
172+
@Override
173+
public String toString() {
174+
return this.expression;
175+
}
176+
177+
}

0 commit comments

Comments
 (0)