Skip to content

Commit 3dde324

Browse files
authored
Merge pull request #17 from Coreoz/cron-expression
Cron expression
2 parents 803bc10 + 6e099fa commit 3dde324

File tree

5 files changed

+154
-8
lines changed

5 files changed

+154
-8
lines changed

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,24 @@ the job will be executed once 10 seconds after it has been scheduled.
8383

8484
### Cron
8585
Schedules can be created using [cron expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression).
86-
This feature is made possible by the use of [cron-utils](https://github.com/jmrozanec/cron-utils).
87-
So to use cron expression, cron-utils should be added in the project:
86+
This feature is made possible by the use of [cron library](https://github.com/frode-carlsen/cron). This library is very lightweight: it has no dependency and is made of a single Java class of 650 lines of code.
87+
88+
So to use cron expression, this library has to be added:
8889
```xml
8990
<dependency>
90-
<groupId>com.cronutils</groupId>
91-
<artifactId>cron-utils</artifactId>
92-
<version>9.1.6</version>
91+
<groupId>ch.eitchnet</groupId>
92+
<artifactId>cron</artifactId>
93+
<version>1.6.2</version>
9394
</dependency>
9495
```
96+
9597
Then to create a job which is executed every hour at the 30th minute,
96-
you can create the schedule: `CronSchedule.parseQuartzCron("0 30 * * * ? *")`.
98+
you can create the schedule: `CronExpressionSchedule.parse("30 * * * *")`.
99+
100+
Cron expression should be checked using a tool like [Cronhub](https://crontab.cronhub.io/).
97101

98-
Cron expression should be created and checked using a tool like [Cron Maker](http://www.cronmaker.com/).
102+
Cron-utils was the default Cron implementation before Wisp 2.2.2. This has [changed in version 2.3.0](/../../issues/14).
103+
Documentation about cron-utils implementation can be found at [Wisp 2.2.2](/../../tree/2.2.2#cron).
99104

100105
### Custom schedules
101106
Custom schedules can be created,

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.coreoz</groupId>
66
<artifactId>wisp</artifactId>
7-
<version>2.2.3-SNAPSHOT</version>
7+
<version>2.3.1-SNAPSHOT</version>
88
<packaging>jar</packaging>
99

1010
<name>Wisp Scheduler</name>
@@ -176,6 +176,12 @@
176176
<version>9.1.6</version>
177177
<optional>true</optional>
178178
</dependency>
179+
<dependency>
180+
<groupId>ch.eitchnet</groupId>
181+
<artifactId>cron</artifactId>
182+
<version>1.6.2</version>
183+
<optional>true</optional>
184+
</dependency>
179185

180186
<dependency>
181187
<groupId>junit</groupId>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.coreoz.wisp.schedule.cron;
2+
3+
import java.time.Instant;
4+
import java.time.ZoneId;
5+
import java.time.ZonedDateTime;
6+
7+
import com.coreoz.wisp.schedule.Schedule;
8+
9+
import fc.cron.CronExpression;
10+
11+
/**
12+
* A {@link Schedule} based on a <a href="https://en.wikipedia.org/wiki/Cron#CRON_expression">
13+
* Cron expression</a>.<br>
14+
* <br>
15+
* This class depends on <a href="https://github.com/frode-carlsen/cron">Cron library</a>,
16+
* so this dependency has to be in the classpath in order to be able to use {@link CronExpressionSchedule}.
17+
* Since the Cron library is marked as optional in Wisp, it has to be
18+
* <a href="https://github.com/Coreoz/Wisp#cron">explicitly referenced in the project dependency configuration</a>
19+
* (pom.xml, build.gradle, build.sbt etc.).<br>
20+
* <br>
21+
* See also {@link CronExpression} for format details and implementation.
22+
*/
23+
public class CronExpressionSchedule implements Schedule {
24+
25+
private final CronExpression cronExpression;
26+
private final ZoneId zoneId;
27+
28+
public CronExpressionSchedule(CronExpression cronExpression, ZoneId zoneId) {
29+
this.cronExpression = cronExpression;
30+
this.zoneId = zoneId;
31+
}
32+
33+
public CronExpressionSchedule(CronExpression cronExpression) {
34+
this(cronExpression, ZoneId.systemDefault());
35+
}
36+
37+
@Override
38+
public long nextExecutionInMillis(long currentTimeInMillis, int executionsCount, Long lastExecutionTimeInMillis) {
39+
Instant currentInstant = Instant.ofEpochMilli(currentTimeInMillis);
40+
try {
41+
return cronExpression.nextTimeAfter(ZonedDateTime.ofInstant(
42+
currentInstant,
43+
zoneId
44+
)).toEpochSecond() * 1000L;
45+
} catch (IllegalArgumentException e) {
46+
return Schedule.WILL_NOT_BE_EXECUTED_AGAIN;
47+
}
48+
}
49+
50+
@Override
51+
public String toString() {
52+
return cronExpression.toString();
53+
}
54+
55+
/**
56+
* Create a {@link Schedule} from a cron expression based on the Unix format,
57+
* e.g. 1 * * * * for each minute.
58+
*/
59+
public static CronExpressionSchedule parse(String cronExpression) {
60+
return new CronExpressionSchedule(CronExpression.createWithoutSeconds(cronExpression));
61+
}
62+
63+
/**
64+
* Create a {@link Schedule} from a cron expression based on the Unix format, but accepting a second field as the first one,
65+
* e.g. 29 * * * * * for each minute at the second 29, for instance 12:05:29.
66+
*/
67+
public static CronExpressionSchedule parseWithSeconds(String cronExpression) {
68+
return new CronExpressionSchedule(CronExpression.create(cronExpression));
69+
}
70+
71+
}

src/main/java/com/coreoz/wisp/schedule/cron/CronSchedule.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
* so this dependency have to be in the classpath in order to be able to use {@link CronSchedule}.
2222
* Since cron-utils is marked as optional, it has to be explicitly referenced in the
2323
* project dependency configuration (pom.xml, build.gradle, build.sbt etc.).
24+
*
25+
* @deprecated Use {@link CronExpressionScheduleTest} instead.
26+
* This class has been deprecated to move away from cron-utils. See
27+
* <a href="https://github.com/Coreoz/Wisp/issues/14">issue #14</a> for details.
2428
*/
29+
@Deprecated
2530
public class CronSchedule implements Schedule {
2631

2732
private static final CronParser UNIX_CRON_PARSER = new CronParser(
@@ -68,15 +73,22 @@ public String toString() {
6873
/**
6974
* Create a {@link Schedule} from a cron expression based on the Unix format,
7075
* e.g. 1 * * * * for each minute.
76+
*
77+
* @deprecated Use {@link CronExpressionScheduleTest#parse(String)} instead
7178
*/
79+
@Deprecated
7280
public static CronSchedule parseUnixCron(String cronExpression) {
7381
return new CronSchedule(UNIX_CRON_PARSER.parse(cronExpression));
7482
}
7583

7684
/**
7785
* Create a {@link Schedule} from a cron expression based on the Quartz format,
7886
* e.g. 0 * * * * ? * for each minute.
87+
*
88+
* @deprecated Use {@link CronExpressionScheduleTest#parse(String)}
89+
* or {@link CronExpressionScheduleTest#parseWithSeconds(String)} instead
7990
*/
91+
@Deprecated
8092
public static CronSchedule parseQuartzCron(String cronExpression) {
8193
return new CronSchedule(QUARTZ_CRON_PARSER.parse(cronExpression));
8294
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.coreoz.wisp.schedule.cron;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.time.Duration;
6+
import java.time.LocalDate;
7+
import java.time.ZoneId;
8+
import java.time.ZonedDateTime;
9+
10+
import org.junit.Test;
11+
12+
public class CronExpressionScheduleTest {
13+
14+
@Test
15+
public void should_calcule_the_next_execution_time_based_on_a_unix_cron_expression() {
16+
CronExpressionSchedule everyMinuteScheduler = CronExpressionSchedule.parse("* * * * *");
17+
18+
// To ease calculations, next execution time are calculated from the timestamp "0".
19+
// So here, the absolute timestamp for an execution in 1 minute will be 60
20+
assertThat(everyMinuteScheduler.nextExecutionInMillis(0, 0, null))
21+
.isEqualTo(Duration.ofMinutes(1).toMillis());
22+
}
23+
24+
@Test
25+
public void should_calcule_the_next_execution_time_based_on_a_unix_cron_expression_with_seconds() {
26+
CronExpressionSchedule everyMinuteScheduler = CronExpressionSchedule.parseWithSeconds("29 * * * * *");
27+
28+
assertThat(everyMinuteScheduler.nextExecutionInMillis(0, 0, null))
29+
// the first iteration will be the absolute timestamp "29"
30+
.isEqualTo(Duration.ofSeconds(29).toMillis());
31+
assertThat(everyMinuteScheduler.nextExecutionInMillis(29000 , 1, null))
32+
// the second iteration will be the absolute timestamp "89"
33+
.isEqualTo(Duration.ofSeconds(60 + 29).toMillis());
34+
}
35+
36+
@Test
37+
public void should_not_executed_daily_jobs_twice_a_day() {
38+
CronExpressionSchedule everyMinuteScheduler = CronExpressionSchedule.parse("0 12 * * *");
39+
40+
ZonedDateTime augustMidday = LocalDate
41+
.of(2016, 8, 31)
42+
.atTime(12, 0)
43+
.atZone(ZoneId.systemDefault());
44+
long midday = augustMidday.toEpochSecond() * 1000;
45+
46+
assertThat(everyMinuteScheduler.nextExecutionInMillis(midday-1, 0, null))
47+
.isEqualTo(midday);
48+
assertThat(everyMinuteScheduler.nextExecutionInMillis(midday, 0, null))
49+
.isEqualTo(midday + Duration.ofDays(1).toMillis());
50+
}
51+
52+
}

0 commit comments

Comments
 (0)