Skip to content

Commit b13b9ca

Browse files
committed
Merge branch 'master' of https://github.com/jmrozanec/cron-utils
2 parents 077a866 + 64673b2 commit b13b9ca

8 files changed

Lines changed: 257 additions & 5 deletions

File tree

src/main/java/com/cronutils/builder/CronBuilder.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.cronutils.model.field.CronField;
2424
import com.cronutils.model.field.CronFieldName;
2525
import com.cronutils.model.field.constraint.FieldConstraints;
26+
import com.cronutils.model.field.definition.FieldDefinition;
2627
import com.cronutils.model.field.expression.FieldExpression;
2728
import com.cronutils.model.field.expression.visitor.ValidationFieldExpressionVisitor;
2829
import com.cronutils.utils.VisibleForTesting;
@@ -90,7 +91,10 @@ public Cron instance() {
9091
CronBuilder addField(final CronFieldName name, final FieldExpression expression) {
9192
checkState(definition != null, "CronBuilder not initialized.");
9293

93-
final FieldConstraints constraints = definition.getFieldDefinition(name).getConstraints();
94+
final FieldDefinition fieldDefinition = definition.getFieldDefinition(name);
95+
checkState(fieldDefinition != null, "Cron field definition does not exist: %s", name);
96+
97+
final FieldConstraints constraints = fieldDefinition.getConstraints();
9498
expression.accept(new ValidationFieldExpressionVisitor(constraints));
9599
fields.put(name, new CronField(name, expression, constraints));
96100

src/main/java/com/cronutils/model/definition/CronConstraintsFactory.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ public static CronConstraint ensureEitherDayOfWeekOrDayOfMonth() {
5353
@Override
5454
public boolean validate(Cron cron) {
5555
CronField dayOfYearField = cron.retrieve(CronFieldName.DAY_OF_YEAR);
56+
CronField dayOfMonthField = cron.retrieve(CronFieldName.DAY_OF_MONTH);
57+
CronField dayOfWeekField = cron.retrieve(CronFieldName.DAY_OF_WEEK);
5658
if (dayOfYearField == null || dayOfYearField.getExpression() instanceof QuestionMark) {
57-
if (!(cron.retrieve(CronFieldName.DAY_OF_MONTH).getExpression() instanceof QuestionMark)) {
58-
return cron.retrieve(CronFieldName.DAY_OF_WEEK).getExpression() instanceof QuestionMark;
59+
if (dayOfMonthField != null && !(dayOfMonthField.getExpression() instanceof QuestionMark)) {
60+
return dayOfWeekField != null && dayOfWeekField.getExpression() instanceof QuestionMark;
5961
} else {
60-
return !(cron.retrieve(CronFieldName.DAY_OF_WEEK).getExpression() instanceof QuestionMark);
62+
return dayOfWeekField != null && !(dayOfWeekField.getExpression() instanceof QuestionMark);
6163
}
6264
}
6365

src/main/java/com/cronutils/parser/CronParser.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ public Cron parse(final String expression) {
121121
String.format("Cron expression contains %s parts but we expect one of %s", expressionLength, expressions.keySet()));
122122
}
123123
try {
124-
final int size = fields.size();
124+
125+
final int size = expressionParts.length;
125126
final List<CronField> results = new ArrayList<>(size + 1);
126127
for (int j = 0; j < size; j++) {
127128
results.add(fields.get(j).parse(expressionParts[j]));
@@ -133,3 +134,4 @@ public Cron parse(final String expression) {
133134
}
134135
}
135136
}
137+

src/main/java/com/cronutils/parser/FieldParser.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ protected On parseOnWithHash(final String exp) {
182182
}
183183
final SpecialCharFieldValue specialChar = new SpecialCharFieldValue(HASH);
184184
final String[] array = exp.split(HASH_TAG);
185+
if (array.length == 0) {
186+
throw new IllegalArgumentException("Invalid Position of # Character!");
187+
}
185188
final IntegerFieldValue nth = mapToIntegerFieldValue(array[1]);
186189
if (array[0].isEmpty()) {
187190
throw new IllegalArgumentException("Time should be specified!");

src/test/java/Issue421Test.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import com.cronutils.builder.CronBuilder;
2+
import com.cronutils.descriptor.CronDescriptor;
3+
import com.cronutils.model.Cron;
4+
import com.cronutils.model.definition.CronConstraintsFactory;
5+
import com.cronutils.model.definition.CronDefinition;
6+
import com.cronutils.model.definition.CronDefinitionBuilder;
7+
import com.cronutils.model.time.ExecutionTime;
8+
import org.junit.Assert;
9+
import org.junit.Test;
10+
11+
import java.time.*;
12+
import java.util.Locale;
13+
import java.util.Optional;
14+
15+
import static com.cronutils.model.field.expression.FieldExpression.questionMark;
16+
import static com.cronutils.model.field.expression.FieldExpressionFactory.every;
17+
import static com.cronutils.model.field.expression.FieldExpressionFactory.on;
18+
import static junit.framework.TestCase.fail;
19+
20+
public class Issue421Test {
21+
22+
private static final CronDefinition definition = CronDefinitionBuilder.defineCron()
23+
.withMinutes().and()
24+
.withHours().and()
25+
.withDayOfWeek().supportsQuestionMark().and()
26+
.withDayOfMonth().supportsL().supportsQuestionMark().and()
27+
.withDayOfYear().supportsQuestionMark().and()
28+
.withMonth().and()
29+
.matchDayOfWeekAndDayOfMonth()
30+
.withCronValidation(CronConstraintsFactory.ensureEitherDayOfWeekOrDayOfMonth())
31+
.withCronValidation(CronConstraintsFactory.ensureEitherDayOfYearOrMonth())
32+
.instance();
33+
34+
@Test
35+
public void testWrongIntervalsForEvery6Months() {
36+
LocalDateTime firstOfJanuary = LocalDateTime.of(2020, 4, 25, 0, 0);
37+
Clock clock = Clock.fixed(firstOfJanuary.toInstant(ZoneOffset.UTC), ZoneId.systemDefault());
38+
ZonedDateTime now = ZonedDateTime.now(clock);
39+
System.out.println("now: " + now);
40+
41+
Cron cron = getEveryMonth(now, 6).instance();
42+
ZonedDateTime nextRun;
43+
44+
nextRun = nextRun(cron, now); // first run
45+
Assert.assertEquals(2020, nextRun.getYear());
46+
Assert.assertEquals(10, nextRun.getMonthValue());
47+
48+
nextRun = nextRun(cron, nextRun); // second
49+
Assert.assertEquals(2021, nextRun.getYear());
50+
Assert.assertEquals(4, nextRun.getMonthValue());
51+
}
52+
53+
@Test
54+
public void testWrongEveryXMonthsDescription() {
55+
ZonedDateTime now = ZonedDateTime.now();
56+
57+
String description = CronDescriptor.instance(Locale.US).describe(getEveryMonth(now, 3).instance());
58+
System.out.println(description);
59+
Assert.assertTrue(description.contains("every 3 months"));
60+
61+
description = CronDescriptor.instance(Locale.US).describe(getEveryMonth(now, 6).instance());
62+
System.out.println(description);
63+
Assert.assertTrue(description.contains("every 6 months"));
64+
}
65+
66+
public static CronBuilder getEveryMonth(ZonedDateTime now, int every) {
67+
return CronBuilder.cron(definition)
68+
.withMinute(on(now.getMinute()))
69+
.withHour(on(now.getHour()))
70+
.withDoW(questionMark())
71+
.withDoM(on(now.getDayOfMonth()))
72+
.withDoY(questionMark())
73+
.withMonth(every(every));
74+
}
75+
76+
private static ZonedDateTime nextRun(Cron cron, ZonedDateTime when) {
77+
final Optional<ZonedDateTime> next = ExecutionTime.forCron(cron).nextExecution(when);
78+
if (!next.isPresent()) {
79+
fail();
80+
}
81+
System.out.println("Calculated next run at " + next.get());
82+
return next.get();
83+
}
84+
85+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.cronutils;
2+
3+
import com.cronutils.model.CronType;
4+
import com.cronutils.model.definition.CronDefinition;
5+
import com.cronutils.model.definition.CronDefinitionBuilder;
6+
import com.cronutils.model.time.ExecutionTime;
7+
import com.cronutils.parser.CronParser;
8+
import org.junit.Test;
9+
10+
import java.time.LocalDate;
11+
import java.time.LocalTime;
12+
import java.time.ZoneId;
13+
import java.time.ZonedDateTime;
14+
import java.util.Optional;
15+
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.fail;
18+
19+
public class Issue418Test {
20+
21+
@Test
22+
public void testQuartzEvery7DaysStartingSunday() {
23+
final CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
24+
final CronParser parser = new CronParser(cronDefinition);
25+
final ExecutionTime execTime = ExecutionTime.forCron(parser.parse("0 0 2 ? * 1/7 *"));
26+
27+
final ZonedDateTime startDate = ZonedDateTime.of(
28+
LocalDate.of(2020, 4, 1),
29+
LocalTime.of(3, 0),
30+
ZoneId.systemDefault()
31+
);
32+
final ZonedDateTime[] expectedDates = {
33+
ZonedDateTime.of(
34+
LocalDate.of(2020, 4, 5),
35+
LocalTime.of(2, 0),
36+
ZoneId.systemDefault()
37+
),
38+
ZonedDateTime.of(
39+
LocalDate.of(2020, 4, 12),
40+
LocalTime.of(2, 0),
41+
ZoneId.systemDefault()
42+
)
43+
};
44+
45+
Optional<ZonedDateTime> nextExecution = execTime.nextExecution(startDate);
46+
assert(nextExecution.isPresent());
47+
assertEquals( expectedDates[0], nextExecution.get());
48+
49+
nextExecution = execTime.nextExecution(nextExecution.get());
50+
assert(nextExecution.isPresent());
51+
assertEquals( expectedDates[1], nextExecution.get());
52+
}
53+
54+
@Test
55+
public void TestInvalidWeekDayStart() {
56+
try {
57+
final CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
58+
final CronParser parser = new CronParser(cronDefinition);
59+
parser.parse("0 0 2 ? * 0/7 *");
60+
fail("Expected exception for invalid expression");
61+
} catch (IllegalArgumentException expected) {
62+
assertEquals("Failed to parse '0 0 2 ? * 0/7 *'. Value 0 not in range [1, 7]", expected.getMessage());
63+
}
64+
}
65+
66+
@Test
67+
public void TestInvalidWeekDayEnd() {
68+
try {
69+
final CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
70+
final CronParser parser = new CronParser(cronDefinition);
71+
parser.parse("0 0 2 ? * 1/8 *");
72+
fail("Expected exception for invalid expression");
73+
} catch (IllegalArgumentException expected) {
74+
assertEquals("Failed to parse '0 0 2 ? * 1/8 *'. Period 8 not in range [1, 7]", expected.getMessage());
75+
}
76+
}
77+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.cronutils;
2+
3+
import com.cronutils.descriptor.CronDescriptor;
4+
import com.cronutils.model.Cron;
5+
import com.cronutils.model.CronType;
6+
import com.cronutils.model.definition.CronDefinitionBuilder;
7+
import com.cronutils.model.time.ExecutionTime;
8+
import com.cronutils.parser.CronParser;
9+
import org.junit.Test;
10+
11+
import java.time.LocalDate;
12+
import java.time.LocalTime;
13+
import java.time.ZoneId;
14+
import java.time.ZonedDateTime;
15+
import java.util.Arrays;
16+
import java.util.Locale;
17+
18+
import static org.junit.Assert.*;
19+
20+
public class Issue423Test {
21+
private static final LocalDate saturday = LocalDate.of(2020, 4, 25);
22+
private static ZonedDateTime shortZDT(int h, int m) {
23+
return ZonedDateTime.of(
24+
saturday,
25+
LocalTime.of(h, m),
26+
ZoneId.of("Australia/Perth")
27+
);
28+
}
29+
30+
private static class TestPair {
31+
public final ZonedDateTime test;
32+
public final ZonedDateTime expected;
33+
public TestPair(ZonedDateTime t, ZonedDateTime exp) {
34+
test = t;
35+
expected = exp;
36+
}
37+
}
38+
39+
@Test
40+
public void issue423() {
41+
final CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
42+
final Cron cron = parser.parse("0 0 0-07,17-0 ? * SAT");
43+
final CronDescriptor cd = CronDescriptor.instance(Locale.UK);
44+
assertTrue(cd.describe(cron).length() > 0);
45+
// at time of test creation, the descriptor is
46+
// "every hour between 0 and 7 and every hour between 17 and 0 at Saturday day"
47+
48+
final ExecutionTime et = ExecutionTime.forCron(cron);
49+
// At this point, an an exception WAS logged. But, not anymore!
50+
51+
Arrays.asList(
52+
new TestPair(shortZDT( 0, 0), shortZDT( 1, 0)),
53+
new TestPair(shortZDT( 0, 30), shortZDT( 1, 0)),
54+
new TestPair(shortZDT( 6, 0), shortZDT( 7, 0)),
55+
new TestPair(shortZDT( 7, 0), shortZDT(17, 0)),
56+
new TestPair(shortZDT(16, 0), shortZDT(17, 0)), // Should be 17:00, but skips to the next Saturday
57+
new TestPair(shortZDT(17, 0), shortZDT(18, 0)), // Should be 18:00, but skips to the next Saturday
58+
new TestPair(shortZDT(18, 0), shortZDT(19, 0)) // Should be 19:00, but skips to the next Saturday
59+
).forEach(tp -> {
60+
// System.err.println("Expected: " + tp.expected + "; Actual: " + et.nextExecution(tp.test).get().toString());
61+
assertEquals(
62+
"All these should be on the same Saturday",
63+
tp.expected,
64+
et.nextExecution(tp.test).get()
65+
);
66+
});
67+
}
68+
}

src/test/java/com/cronutils/parser/CronParserTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ public void testTrailingCommaListUnix(){
101101
validateExpression(CronType.UNIX, "1, * * * *");
102102
}
103103

104+
@Test(expected = IllegalArgumentException.class)
105+
public void testHashListUnix(){
106+
validateExpression(CronType.UNIX, "0 0 0 ? * #");
107+
}
108+
104109
@Test // issue #369
105110
public void testParseIncompleteRangeNoValues() {
106111
parseIncompleteExpression("-", "Missing values for range: -");
@@ -217,4 +222,10 @@ public void testParseMulticron(){
217222
Cron cron = parser.parse(multicron);
218223
assertEquals(multicron, cron.asString());
219224
}
225+
226+
@Test(expected = IllegalArgumentException.class)
227+
public void testParseQuartzCronWithHash() {
228+
parser = new CronParser(TestCronDefinitionsFactory.withDayOfYearDefinitionWhereYearAndDoYOptionals());
229+
parser.parse("0 0 0 ? * #");
230+
}
220231
}

0 commit comments

Comments
 (0)