@@ -61,6 +61,7 @@ public class CronSchedule implements Schedule {
61
61
private String expression ;
62
62
private ExecutionTime executionTime ;
63
63
private Clock clock ;
64
+ private Long scheduleDelay ;
64
65
65
66
public CronSchedule (String expression , ZoneId timezone ) {
66
67
this .expression = expression ;
@@ -69,9 +70,15 @@ public CronSchedule(String expression, ZoneId timezone) {
69
70
clock = Clock .system (timezone );
70
71
}
71
72
73
+ public CronSchedule (String expression , ZoneId timezone , long scheduleDelay ) {
74
+ this (expression , timezone );
75
+ this .scheduleDelay = scheduleDelay ;
76
+ }
77
+
72
78
public CronSchedule (StreamInput input ) throws IOException {
73
79
timezone = input .readZoneId ();
74
80
expression = input .readString ();
81
+ scheduleDelay = input .readOptionalLong ();
75
82
executionTime = ExecutionTime .forCron (cronParser .parse (expression ));
76
83
clock = Clock .system (timezone );
77
84
}
@@ -86,59 +93,68 @@ void setExecutionTime(ExecutionTime executionTime) {
86
93
this .executionTime = executionTime ;
87
94
}
88
95
89
- @ VisibleForTesting
90
- ZoneId getTimeZone () {
96
+ public ZoneId getTimeZone () {
91
97
return this .timezone ;
92
98
}
93
99
94
- @ VisibleForTesting
95
- String getCronExpression () {
100
+ public String getCronExpression () {
96
101
return this .expression ;
97
102
}
98
103
104
+ public Long getDelay () { return this .scheduleDelay ; }
105
+
99
106
@ Override
100
107
public Instant getNextExecutionTime (Instant time ) {
101
108
Instant baseTime = time == null ? this .clock .instant () : time ;
102
-
103
- ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (baseTime , this .timezone );
109
+ long delay = scheduleDelay == null ? 0 : scheduleDelay ;
110
+ // The executionTime object doesn't know about the delay, so first subtract the delay from the baseTime in case
111
+ // this moves to the previous interval, then add the delay to the returned execution time to get the correct time.
112
+ // For example, say it is 10:07 AM with an hourly schedule and a delay of 15 minutes. The next execution time
113
+ // should be 10:15 AM, but executionTime.nextExecution( 10:07 AM ) would return the next execution as 11 AM.
114
+ // By subtracting the delay first, the ExecutionTime object is given the input time as 9:52 AM, it returns
115
+ // 10:00 AM, and after adding the delay, we get the correct next execution time of 10:15 AM.
116
+ ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (baseTime .minusMillis (delay ), this .timezone );
104
117
ZonedDateTime nextExecutionTime = this .executionTime .nextExecution (zonedDateTime ).orElse (null );
105
118
106
- return nextExecutionTime == null ? null : nextExecutionTime .toInstant ();
119
+ return nextExecutionTime == null ? null : nextExecutionTime .toInstant (). plusMillis ( delay ) ;
107
120
}
108
121
109
122
@ Override
110
123
public Duration nextTimeToExecute () {
111
- Instant now = this .clock .instant ();
124
+ long delay = scheduleDelay == null ? 0 : scheduleDelay ;
125
+ Instant now = this .clock .instant ().minusMillis (delay );
112
126
ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (now , this .timezone );
113
127
Optional <Duration > timeToNextExecution = this .executionTime .timeToNextExecution (zonedDateTime );
114
128
return timeToNextExecution .orElse (null );
115
129
}
116
130
117
131
@ Override
118
132
public Tuple <Instant , Instant > getPeriodStartingAt (Instant startTime ) {
133
+ long delay = scheduleDelay == null ? 0 : scheduleDelay ;
119
134
Instant realStartTime ;
120
135
if (startTime != null ) {
121
136
realStartTime = startTime ;
122
137
} else {
123
138
Instant now = this .clock .instant ();
124
- Optional <ZonedDateTime > lastExecutionTime = this .executionTime .lastExecution (ZonedDateTime .ofInstant (now , this .timezone ));
139
+ Optional <ZonedDateTime > lastExecutionTime = this .executionTime .lastExecution (ZonedDateTime .ofInstant (now . minusMillis ( delay ) , this .timezone ));
125
140
if (!lastExecutionTime .isPresent ()) {
126
141
return new Tuple <>(now , now );
127
142
}
128
- realStartTime = lastExecutionTime .get ().toInstant ();
143
+ realStartTime = lastExecutionTime .get ().toInstant (). plusMillis ( delay ) ;
129
144
}
130
- ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (realStartTime , this .timezone );
145
+ ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (realStartTime . minusMillis ( delay ) , this .timezone );
131
146
ZonedDateTime newEndTime = executionTime .nextExecution (zonedDateTime ).orElse (null );
132
- return new Tuple <>(realStartTime , newEndTime == null ? null : newEndTime .toInstant ());
147
+ return new Tuple <>(realStartTime , newEndTime == null ? null : newEndTime .toInstant (). plusMillis ( delay ) );
133
148
}
134
149
135
150
@ Override
136
151
public Boolean runningOnTime (Instant lastExecutionTime ) {
152
+ long delay = scheduleDelay == null ? 0 : scheduleDelay ;
137
153
if (lastExecutionTime == null ) {
138
154
return true ;
139
155
}
140
156
141
- Instant now = this .clock .instant ();
157
+ Instant now = this .clock .instant (). minusMillis ( delay ) ;
142
158
ZonedDateTime zonedDateTime = ZonedDateTime .ofInstant (now , timezone );
143
159
Optional <ZonedDateTime > expectedExecutionTime = this .executionTime .lastExecution (zonedDateTime );
144
160
@@ -147,15 +163,30 @@ public Boolean runningOnTime(Instant lastExecutionTime) {
147
163
}
148
164
ZonedDateTime actualExecutionTime = ZonedDateTime .ofInstant (lastExecutionTime , timezone );
149
165
150
- return ChronoUnit .SECONDS .between (expectedExecutionTime .get (), actualExecutionTime ) == 0L ;
166
+ return ChronoUnit .SECONDS .between (expectedExecutionTime .get (). plus ( delay , ChronoUnit . MILLIS ) , actualExecutionTime ) == 0L ;
151
167
}
152
168
153
169
@ Override
154
170
public XContentBuilder toXContent (XContentBuilder builder , Params params ) throws IOException {
171
+ return this .scheduleDelay == null ? toXContentNoDelay (builder ) : toXContentWithDelay (builder );
172
+ }
173
+
174
+ private XContentBuilder toXContentNoDelay (XContentBuilder builder ) throws IOException {
175
+ builder .startObject ()
176
+ .startObject (CRON_FIELD )
177
+ .field (EXPRESSION_FIELD , this .expression )
178
+ .field (TIMEZONE_FIELD , this .timezone .getId ())
179
+ .endObject ()
180
+ .endObject ();
181
+ return builder ;
182
+ }
183
+
184
+ private XContentBuilder toXContentWithDelay (XContentBuilder builder ) throws IOException {
155
185
builder .startObject ()
156
186
.startObject (CRON_FIELD )
157
187
.field (EXPRESSION_FIELD , this .expression )
158
188
.field (TIMEZONE_FIELD , this .timezone .getId ())
189
+ .field (DELAY_FIELD , this .scheduleDelay )
159
190
.endObject ()
160
191
.endObject ();
161
192
return builder ;
@@ -172,17 +203,19 @@ public boolean equals(Object o) {
172
203
if (o == null || getClass () != o .getClass ()) return false ;
173
204
CronSchedule cronSchedule = (CronSchedule ) o ;
174
205
return timezone .equals (cronSchedule .timezone ) &&
175
- expression .equals (cronSchedule .expression );
206
+ expression .equals (cronSchedule .expression ) &&
207
+ Objects .equals (scheduleDelay , cronSchedule .scheduleDelay );
176
208
}
177
209
178
210
@ Override
179
211
public int hashCode () {
180
- return Objects .hash (timezone , expression );
212
+ return scheduleDelay == null ? Objects .hash (timezone , expression ) : Objects . hash ( timezone , expression , scheduleDelay );
181
213
}
182
214
183
215
@ Override
184
216
public void writeTo (StreamOutput out ) throws IOException {
185
217
out .writeZoneId (timezone );
186
218
out .writeString (expression );
219
+ out .writeOptionalLong (scheduleDelay );
187
220
}
188
221
}
0 commit comments