1 /*
2  * Copyright 2002-2011 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  *      http://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.util.ArrayList;
20 import java.util.Calendar;
21 import java.util.Date;
22 import java.util.GregorianCalendar;
23 import java.util.List;
24 import java.util.TimeZone;
25 
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.junit.runners.Parameterized;
30 import org.junit.runners.Parameterized.Parameters;
31 
32 import org.springframework.scheduling.TriggerContext;
33 
34 import static org.junit.Assert.*;
35 
36 /**
37  * @author Dave Syer
38  * @author Mark Fisher
39  * @author Juergen Hoeller
40  */
41 @RunWith(Parameterized.class)
42 public class CronTriggerTests {
43 
44 	private Calendar calendar = new GregorianCalendar();
45 
46 	private final Date date;
47 
48 	private final TimeZone timeZone;
49 
50 
CronTriggerTests(Date date, TimeZone timeZone)51 	public CronTriggerTests(Date date, TimeZone timeZone) {
52 		this.timeZone = timeZone;
53 		this.date = date;
54 	}
55 
56 	@Parameters
getParameters()57 	public static List<Object[]> getParameters() {
58 		List<Object[]> list = new ArrayList<Object[]>();
59 		list.add(new Object[] { new Date(), TimeZone.getDefault() });
60 		list.add(new Object[] { new Date(), TimeZone.getTimeZone("CET") });
61 		return list;
62 	}
63 
roundup(Calendar calendar)64 	private void roundup(Calendar calendar) {
65 		calendar.add(Calendar.SECOND, 1);
66 		calendar.set(Calendar.MILLISECOND, 0);
67 	}
68 
69 	@Before
setUp()70 	public void setUp() {
71 		calendar.setTimeZone(timeZone);
72 		calendar.setTime(date);
73 		roundup(calendar);
74 	}
75 
76 	@Test
testMatchAll()77 	public void testMatchAll() throws Exception {
78 		CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
79 		TriggerContext context = getTriggerContext(date);
80 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
81 	}
82 
83 	@Test
testMatchLastSecond()84 	public void testMatchLastSecond() throws Exception {
85 		CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
86 		GregorianCalendar calendar = new GregorianCalendar();
87 		calendar.set(Calendar.SECOND, 58);
88 		assertMatchesNextSecond(trigger, calendar);
89 	}
90 
91 	@Test
testMatchSpecificSecond()92 	public void testMatchSpecificSecond() throws Exception {
93 		CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
94 		GregorianCalendar calendar = new GregorianCalendar();
95 		calendar.set(Calendar.SECOND, 9);
96 		assertMatchesNextSecond(trigger, calendar);
97 	}
98 
99 	@Test
testIncrementSecondByOne()100 	public void testIncrementSecondByOne() throws Exception {
101 		CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
102 		calendar.set(Calendar.SECOND, 10);
103 		Date date = calendar.getTime();
104 		calendar.add(Calendar.SECOND, 1);
105 		TriggerContext context = getTriggerContext(date);
106 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
107 	}
108 
109 	@Test
testIncrementSecondWithPreviousExecutionTooEarly()110 	public void testIncrementSecondWithPreviousExecutionTooEarly() throws Exception {
111 		CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
112 		calendar.set(Calendar.SECOND, 11);
113 		SimpleTriggerContext context = new SimpleTriggerContext();
114 		context.update(calendar.getTime(), new Date(calendar.getTimeInMillis() - 100),
115 				new Date(calendar.getTimeInMillis() - 90));
116 		calendar.add(Calendar.MINUTE, 1);
117 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
118 	}
119 
120 	@Test
testIncrementSecondAndRollover()121 	public void testIncrementSecondAndRollover() throws Exception {
122 		CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
123 		calendar.set(Calendar.SECOND, 11);
124 		Date date = calendar.getTime();
125 		calendar.add(Calendar.SECOND, 59);
126 		TriggerContext context = getTriggerContext(date);
127 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
128 	}
129 
130 	@Test
testSecondRange()131 	public void testSecondRange() throws Exception {
132 		CronTrigger trigger = new CronTrigger("10-15 * * * * *", timeZone);
133 		calendar.set(Calendar.SECOND, 9);
134 		assertMatchesNextSecond(trigger, calendar);
135 		calendar.set(Calendar.SECOND, 14);
136 		assertMatchesNextSecond(trigger, calendar);
137 	}
138 
139 	@Test
testIncrementMinute()140 	public void testIncrementMinute() throws Exception {
141 		CronTrigger trigger = new CronTrigger("0 * * * * *", timeZone);
142 		calendar.set(Calendar.MINUTE, 10);
143 		Date date = calendar.getTime();
144 		calendar.add(Calendar.MINUTE, 1);
145 		calendar.set(Calendar.SECOND, 0);
146 		TriggerContext context1 = getTriggerContext(date);
147 		date = trigger.nextExecutionTime(context1);
148 		assertEquals(calendar.getTime(), date);
149 		calendar.add(Calendar.MINUTE, 1);
150 		TriggerContext context2 = getTriggerContext(date);
151 		date = trigger.nextExecutionTime(context2);
152 		assertEquals(calendar.getTime(), date);
153 	}
154 
155 	@Test
testIncrementMinuteByOne()156 	public void testIncrementMinuteByOne() throws Exception {
157 		CronTrigger trigger = new CronTrigger("0 11 * * * *", timeZone);
158 		calendar.set(Calendar.MINUTE, 10);
159 		TriggerContext context = getTriggerContext(calendar.getTime());
160 		calendar.add(Calendar.MINUTE, 1);
161 		calendar.set(Calendar.SECOND, 0);
162 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
163 	}
164 
165 	@Test
testIncrementMinuteAndRollover()166 	public void testIncrementMinuteAndRollover() throws Exception {
167 		CronTrigger trigger = new CronTrigger("0 10 * * * *", timeZone);
168 		calendar.set(Calendar.MINUTE, 11);
169 		calendar.set(Calendar.SECOND, 0);
170 		Date date = calendar.getTime();
171 		calendar.add(Calendar.MINUTE, 59);
172 		TriggerContext context = getTriggerContext(date);
173 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
174 	}
175 
176 	@Test
testIncrementHour()177 	public void testIncrementHour() throws Exception {
178 		CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
179 		calendar.set(Calendar.MONTH, 9);
180 		calendar.set(Calendar.DAY_OF_MONTH, 30);
181 		calendar.set(Calendar.HOUR_OF_DAY, 11);
182 		calendar.set(Calendar.MINUTE, 1);
183 		calendar.set(Calendar.SECOND, 0);
184 		Date date = calendar.getTime();
185 		calendar.set(Calendar.MINUTE, 0);
186 		calendar.set(Calendar.HOUR_OF_DAY, 12);
187 		TriggerContext context1 = getTriggerContext(date);
188 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
189 		calendar.set(Calendar.HOUR_OF_DAY, 13);
190 		TriggerContext context2 = getTriggerContext(date);
191 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
192 	}
193 
194 	@Test
testIncrementHourAndRollover()195 	public void testIncrementHourAndRollover() throws Exception {
196 		CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
197 		calendar.set(Calendar.MONTH, 9);
198 		calendar.set(Calendar.DAY_OF_MONTH, 10);
199 		calendar.set(Calendar.HOUR_OF_DAY, 23);
200 		calendar.set(Calendar.MINUTE, 1);
201 		calendar.set(Calendar.SECOND, 0);
202 		Date date = calendar.getTime();
203 		calendar.set(Calendar.MINUTE, 0);
204 		calendar.set(Calendar.HOUR_OF_DAY, 0);
205 		calendar.set(Calendar.DAY_OF_MONTH, 11);
206 		TriggerContext context1 = getTriggerContext(date);
207 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
208 		calendar.set(Calendar.HOUR_OF_DAY, 1);
209 		TriggerContext context2 = getTriggerContext(date);
210 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
211 	}
212 
213 	@Test
testIncrementDayOfMonth()214 	public void testIncrementDayOfMonth() throws Exception {
215 		CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
216 		calendar.set(Calendar.DAY_OF_MONTH, 1);
217 		Date date = calendar.getTime();
218 		calendar.add(Calendar.DAY_OF_MONTH, 1);
219 		calendar.set(Calendar.HOUR_OF_DAY, 0);
220 		calendar.set(Calendar.MINUTE, 0);
221 		calendar.set(Calendar.SECOND, 0);
222 		TriggerContext context1 = getTriggerContext(date);
223 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
224 		assertEquals(2, calendar.get(Calendar.DAY_OF_MONTH));
225 		calendar.add(Calendar.DAY_OF_MONTH, 1);
226 		TriggerContext context2 = getTriggerContext(date);
227 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
228 		assertEquals(3, calendar.get(Calendar.DAY_OF_MONTH));
229 	}
230 
231 	@Test
testIncrementDayOfMonthByOne()232 	public void testIncrementDayOfMonthByOne() throws Exception {
233 		CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
234 		calendar.set(Calendar.DAY_OF_MONTH, 9);
235 		Date date = calendar.getTime();
236 		calendar.add(Calendar.DAY_OF_MONTH, 1);
237 		calendar.set(Calendar.HOUR_OF_DAY, 0);
238 		calendar.set(Calendar.MINUTE, 0);
239 		calendar.set(Calendar.SECOND, 0);
240 		TriggerContext context = getTriggerContext(date);
241 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
242 	}
243 
244 	@Test
testIncrementDayOfMonthAndRollover()245 	public void testIncrementDayOfMonthAndRollover() throws Exception {
246 		CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
247 		calendar.set(Calendar.DAY_OF_MONTH, 11);
248 		Date date = calendar.getTime();
249 		calendar.add(Calendar.MONTH, 1);
250 		calendar.set(Calendar.DAY_OF_MONTH, 10);
251 		calendar.set(Calendar.HOUR_OF_DAY, 0);
252 		calendar.set(Calendar.MINUTE, 0);
253 		calendar.set(Calendar.SECOND, 0);
254 		TriggerContext context = getTriggerContext(date);
255 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
256 	}
257 
258 	@Test
testDailyTriggerInShortMonth()259 	public void testDailyTriggerInShortMonth() throws Exception {
260 		CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
261 		calendar.set(Calendar.MONTH, 8); // September: 30 days
262 		calendar.set(Calendar.DAY_OF_MONTH, 30);
263 		Date date = calendar.getTime();
264 		calendar.set(Calendar.MONTH, 9); // October
265 		calendar.set(Calendar.HOUR_OF_DAY, 0);
266 		calendar.set(Calendar.MINUTE, 0);
267 		calendar.set(Calendar.SECOND, 0);
268 		calendar.set(Calendar.DAY_OF_MONTH, 1);
269 		TriggerContext context1 = getTriggerContext(date);
270 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
271 		calendar.set(Calendar.DAY_OF_MONTH, 2);
272 		TriggerContext context2 = getTriggerContext(date);
273 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
274 	}
275 
276 	@Test
testDailyTriggerInLongMonth()277 	public void testDailyTriggerInLongMonth() throws Exception {
278 		CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
279 		calendar.set(Calendar.MONTH, 7); // August: 31 days and not a daylight saving boundary
280 		calendar.set(Calendar.DAY_OF_MONTH, 30);
281 		Date date = calendar.getTime();
282 		calendar.set(Calendar.HOUR_OF_DAY, 0);
283 		calendar.set(Calendar.MINUTE, 0);
284 		calendar.set(Calendar.SECOND, 0);
285 		calendar.set(Calendar.DAY_OF_MONTH, 31);
286 		TriggerContext context1 = getTriggerContext(date);
287 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
288 		calendar.set(Calendar.MONTH, 8); // September
289 		calendar.set(Calendar.DAY_OF_MONTH, 1);
290 		TriggerContext context2 = getTriggerContext(date);
291 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
292 	}
293 
294 	@Test
testDailyTriggerOnDaylightSavingBoundary()295 	public void testDailyTriggerOnDaylightSavingBoundary() throws Exception {
296 		CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
297 		calendar.set(Calendar.MONTH, 9); // October: 31 days and a daylight saving boundary in CET
298 		calendar.set(Calendar.DAY_OF_MONTH, 30);
299 		Date date = calendar.getTime();
300 		calendar.set(Calendar.HOUR_OF_DAY, 0);
301 		calendar.set(Calendar.MINUTE, 0);
302 		calendar.set(Calendar.SECOND, 0);
303 		calendar.set(Calendar.DAY_OF_MONTH, 31);
304 		TriggerContext context1 = getTriggerContext(date);
305 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
306 		calendar.set(Calendar.MONTH, 10); // November
307 		calendar.set(Calendar.DAY_OF_MONTH, 1);
308 		TriggerContext context2 = getTriggerContext(date);
309 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
310 	}
311 
312 	@Test
testIncrementMonth()313 	public void testIncrementMonth() throws Exception {
314 		CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
315 		calendar.set(Calendar.MONTH, 9);
316 		calendar.set(Calendar.DAY_OF_MONTH, 30);
317 		Date date = calendar.getTime();
318 		calendar.set(Calendar.DAY_OF_MONTH, 1);
319 		calendar.set(Calendar.HOUR_OF_DAY, 0);
320 		calendar.set(Calendar.MINUTE, 0);
321 		calendar.set(Calendar.SECOND, 0);
322 		calendar.set(Calendar.MONTH, 10);
323 		TriggerContext context1 = getTriggerContext(date);
324 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
325 		calendar.set(Calendar.MONTH, 11);
326 		TriggerContext context2 = getTriggerContext(date);
327 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
328 	}
329 
330 	@Test
testIncrementMonthAndRollover()331 	public void testIncrementMonthAndRollover() throws Exception {
332 		CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
333 		calendar.set(Calendar.MONTH, 11);
334 		calendar.set(Calendar.DAY_OF_MONTH, 31);
335 		calendar.set(Calendar.YEAR, 2010);
336 		Date date = calendar.getTime();
337 		calendar.set(Calendar.DAY_OF_MONTH, 1);
338 		calendar.set(Calendar.HOUR_OF_DAY, 0);
339 		calendar.set(Calendar.MINUTE, 0);
340 		calendar.set(Calendar.SECOND, 0);
341 		calendar.set(Calendar.MONTH, 0);
342 		calendar.set(Calendar.YEAR, 2011);
343 		TriggerContext context1 = getTriggerContext(date);
344 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
345 		calendar.set(Calendar.MONTH, 1);
346 		TriggerContext context2 = getTriggerContext(date);
347 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context2));
348 	}
349 
350 	@Test
testMonthlyTriggerInLongMonth()351 	public void testMonthlyTriggerInLongMonth() throws Exception {
352 		CronTrigger trigger = new CronTrigger("0 0 0 31 * *", timeZone);
353 		calendar.set(Calendar.MONTH, 9);
354 		calendar.set(Calendar.DAY_OF_MONTH, 30);
355 		Date date = calendar.getTime();
356 		calendar.set(Calendar.DAY_OF_MONTH, 31);
357 		calendar.set(Calendar.HOUR_OF_DAY, 0);
358 		calendar.set(Calendar.MINUTE, 0);
359 		calendar.set(Calendar.SECOND, 0);
360 		TriggerContext context = getTriggerContext(date);
361 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
362 	}
363 
364 	@Test
testMonthlyTriggerInShortMonth()365 	public void testMonthlyTriggerInShortMonth() throws Exception {
366 		CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
367 		calendar.set(Calendar.MONTH, 9);
368 		calendar.set(Calendar.DAY_OF_MONTH, 30);
369 		Date date = calendar.getTime();
370 		calendar.set(Calendar.MONTH, 10);
371 		calendar.set(Calendar.DAY_OF_MONTH, 1);
372 		calendar.set(Calendar.HOUR_OF_DAY, 0);
373 		calendar.set(Calendar.MINUTE, 0);
374 		calendar.set(Calendar.SECOND, 0);
375 		TriggerContext context = getTriggerContext(date);
376 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
377 	}
378 
379 	@Test
testIncrementDayOfWeekByOne()380 	public void testIncrementDayOfWeekByOne() throws Exception {
381 		CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
382 		calendar.set(Calendar.DAY_OF_WEEK, 2);
383 		Date date = calendar.getTime();
384 		calendar.add(Calendar.DAY_OF_WEEK, 1);
385 		calendar.set(Calendar.HOUR_OF_DAY, 0);
386 		calendar.set(Calendar.MINUTE, 0);
387 		calendar.set(Calendar.SECOND, 0);
388 		TriggerContext context = getTriggerContext(date);
389 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
390 		assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
391 	}
392 
393 	@Test
testIncrementDayOfWeekAndRollover()394 	public void testIncrementDayOfWeekAndRollover() throws Exception {
395 		CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
396 		calendar.set(Calendar.DAY_OF_WEEK, 4);
397 		Date date = calendar.getTime();
398 		calendar.add(Calendar.DAY_OF_MONTH, 6);
399 		calendar.set(Calendar.HOUR_OF_DAY, 0);
400 		calendar.set(Calendar.MINUTE, 0);
401 		calendar.set(Calendar.SECOND, 0);
402 		TriggerContext context = getTriggerContext(date);
403 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
404 		assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
405 	}
406 
407 	@Test
testSpecificMinuteSecond()408 	public void testSpecificMinuteSecond() throws Exception {
409 		CronTrigger trigger = new CronTrigger("55 5 * * * *", timeZone);
410 		calendar.set(Calendar.MINUTE, 4);
411 		calendar.set(Calendar.SECOND, 54);
412 		Date date = calendar.getTime();
413 		TriggerContext context1 = getTriggerContext(date);
414 		calendar.add(Calendar.MINUTE, 1);
415 		calendar.set(Calendar.SECOND, 55);
416 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
417 		calendar.add(Calendar.HOUR, 1);
418 		TriggerContext context2 = getTriggerContext(date);
419 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
420 	}
421 
422 	@Test
testSpecificHourSecond()423 	public void testSpecificHourSecond() throws Exception {
424 		CronTrigger trigger = new CronTrigger("55 * 2 * * *", timeZone);
425 		calendar.set(Calendar.HOUR_OF_DAY, 1);
426 		calendar.set(Calendar.SECOND, 54);
427 		Date date = calendar.getTime();
428 		TriggerContext context1 = getTriggerContext(date);
429 		calendar.add(Calendar.HOUR_OF_DAY, 1);
430 		calendar.set(Calendar.MINUTE, 0);
431 		calendar.set(Calendar.SECOND, 55);
432 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
433 		calendar.add(Calendar.MINUTE, 1);
434 		TriggerContext context2 = getTriggerContext(date);
435 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
436 	}
437 
438 	@Test
testSpecificMinuteHour()439 	public void testSpecificMinuteHour() throws Exception {
440 		CronTrigger trigger = new CronTrigger("* 5 10 * * *", timeZone);
441 		calendar.set(Calendar.MINUTE, 4);
442 		calendar.set(Calendar.HOUR_OF_DAY, 9);
443 		Date date = calendar.getTime();
444 		calendar.add(Calendar.MINUTE, 1);
445 		calendar.add(Calendar.HOUR_OF_DAY, 1);
446 		calendar.set(Calendar.SECOND, 0);
447 		TriggerContext context1 = getTriggerContext(date);
448 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
449 		// next trigger is in one second because second is wildcard
450 		calendar.add(Calendar.SECOND, 1);
451 		TriggerContext context2 = getTriggerContext(date);
452 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
453 	}
454 
455 	@Test
testSpecificDayOfMonthSecond()456 	public void testSpecificDayOfMonthSecond() throws Exception {
457 		CronTrigger trigger = new CronTrigger("55 * * 3 * *", timeZone);
458 		calendar.set(Calendar.DAY_OF_MONTH, 2);
459 		calendar.set(Calendar.SECOND, 54);
460 		Date date = calendar.getTime();
461 		TriggerContext context1 = getTriggerContext(date);
462 		calendar.add(Calendar.DAY_OF_MONTH, 1);
463 		calendar.set(Calendar.HOUR_OF_DAY, 0);
464 		calendar.set(Calendar.MINUTE, 0);
465 		calendar.set(Calendar.SECOND, 55);
466 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
467 		calendar.add(Calendar.MINUTE, 1);
468 		TriggerContext context2 = getTriggerContext(date);
469 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
470 	}
471 
472 	@Test
testSpecificDate()473 	public void testSpecificDate() throws Exception {
474 		CronTrigger trigger = new CronTrigger("* * * 3 11 *", timeZone);
475 		calendar.set(Calendar.DAY_OF_MONTH, 2);
476 		calendar.set(Calendar.MONTH, 9);
477 		Date date = calendar.getTime();
478 		TriggerContext context1 = getTriggerContext(date);
479 		calendar.add(Calendar.DAY_OF_MONTH, 1);
480 		calendar.set(Calendar.HOUR_OF_DAY, 0);
481 		calendar.set(Calendar.MONTH, 10); // 10=November
482 		calendar.set(Calendar.MINUTE, 0);
483 		calendar.set(Calendar.SECOND, 0);
484 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
485 		calendar.add(Calendar.SECOND, 1);
486 		TriggerContext context2 = getTriggerContext(date);
487 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
488 	}
489 
490 	@Test(expected=IllegalStateException.class)
testNonExistentSpecificDate()491 	public void testNonExistentSpecificDate() throws Exception {
492 		// TODO: maybe try and detect this as a special case in parser?
493 		CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone);
494 		calendar.set(Calendar.DAY_OF_MONTH, 10);
495 		calendar.set(Calendar.MONTH, 2);
496 		Date date = calendar.getTime();
497 		TriggerContext context1 = getTriggerContext(date);
498 		trigger.nextExecutionTime(context1);
499 		// new CronTrigger("0 0 0 30 1 ?", timeZone);
500 	}
501 
502 	@Test
testLeapYearSpecificDate()503 	public void testLeapYearSpecificDate() throws Exception {
504 		CronTrigger trigger = new CronTrigger("0 0 0 29 2 *", timeZone);
505 		calendar.set(Calendar.YEAR, 2007);
506 		calendar.set(Calendar.DAY_OF_MONTH, 10);
507 		calendar.set(Calendar.MONTH, 1); // 2=February
508 		Date date = calendar.getTime();
509 		TriggerContext context1 = getTriggerContext(date);
510 		calendar.set(Calendar.YEAR, 2008);
511 		calendar.set(Calendar.DAY_OF_MONTH, 29);
512 		calendar.set(Calendar.HOUR_OF_DAY, 0);
513 		calendar.set(Calendar.MINUTE, 0);
514 		calendar.set(Calendar.SECOND, 0);
515 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
516 		calendar.add(Calendar.YEAR, 4);
517 		TriggerContext context2 = getTriggerContext(date);
518 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
519 	}
520 
521 	@Test
testWeekDaySequence()522 	public void testWeekDaySequence() throws Exception {
523 		CronTrigger trigger = new CronTrigger("0 0 7 ? * MON-FRI", timeZone);
524 		// This is a Saturday
525 		calendar.set(2009, 8, 26);
526 		Date date = calendar.getTime();
527 		// 7 am is the trigger time
528 		calendar.set(Calendar.HOUR_OF_DAY, 7);
529 		calendar.set(Calendar.MINUTE, 0);
530 		calendar.set(Calendar.SECOND, 0);
531 		// Add two days because we start on Saturday
532 		calendar.add(Calendar.DAY_OF_MONTH, 2);
533 		TriggerContext context1 = getTriggerContext(date);
534 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
535 		// Next day is a week day so add one
536 		calendar.add(Calendar.DAY_OF_MONTH, 1);
537 		TriggerContext context2 = getTriggerContext(date);
538 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
539 		calendar.add(Calendar.DAY_OF_MONTH, 1);
540 		TriggerContext context3 = getTriggerContext(date);
541 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context3));
542 	}
543 
544 	@Test
testDayOfWeekIndifferent()545 	public void testDayOfWeekIndifferent() throws Exception {
546 		CronTrigger trigger1 = new CronTrigger("* * * 2 * *", timeZone);
547 		CronTrigger trigger2 = new CronTrigger("* * * 2 * ?", timeZone);
548 		assertEquals(trigger1, trigger2);
549 	}
550 
551 	@Test
testSecondIncrementer()552 	public void testSecondIncrementer() throws Exception {
553 		CronTrigger trigger1 = new CronTrigger("57,59 * * * * *", timeZone);
554 		CronTrigger trigger2 = new CronTrigger("57/2 * * * * *", timeZone);
555 		assertEquals(trigger1, trigger2);
556 	}
557 
558 	@Test
testSecondIncrementerWithRange()559 	public void testSecondIncrementerWithRange() throws Exception {
560 		CronTrigger trigger1 = new CronTrigger("1,3,5 * * * * *", timeZone);
561 		CronTrigger trigger2 = new CronTrigger("1-6/2 * * * * *", timeZone);
562 		assertEquals(trigger1, trigger2);
563 	}
564 
565 	@Test
testHourIncrementer()566 	public void testHourIncrementer() throws Exception {
567 		CronTrigger trigger1 = new CronTrigger("* * 4,8,12,16,20 * * *", timeZone);
568 		CronTrigger trigger2 = new CronTrigger("* * 4/4 * * *", timeZone);
569 		assertEquals(trigger1, trigger2);
570 	}
571 
572 	@Test
testDayNames()573 	public void testDayNames() throws Exception {
574 		CronTrigger trigger1 = new CronTrigger("* * * * * 0-6", timeZone);
575 		CronTrigger trigger2 = new CronTrigger("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON", timeZone);
576 		assertEquals(trigger1, trigger2);
577 	}
578 
579 	@Test
testSundayIsZero()580 	public void testSundayIsZero() throws Exception {
581 		CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
582 		CronTrigger trigger2 = new CronTrigger("* * * * * SUN", timeZone);
583 		assertEquals(trigger1, trigger2);
584 	}
585 
586 	@Test
testSundaySynonym()587 	public void testSundaySynonym() throws Exception {
588 		CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
589 		CronTrigger trigger2 = new CronTrigger("* * * * * 7", timeZone);
590 		assertEquals(trigger1, trigger2);
591 	}
592 
593 	@Test
testMonthNames()594 	public void testMonthNames() throws Exception {
595 		CronTrigger trigger1 = new CronTrigger("* * * * 1-12 *", timeZone);
596 		CronTrigger trigger2 = new CronTrigger("* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *", timeZone);
597 		assertEquals(trigger1, trigger2);
598 	}
599 
600 	@Test
testMonthNamesMixedCase()601 	public void testMonthNamesMixedCase() throws Exception {
602 		CronTrigger trigger1 = new CronTrigger("* * * * 2 *", timeZone);
603 		CronTrigger trigger2 = new CronTrigger("* * * * Feb *", timeZone);
604 		assertEquals(trigger1, trigger2);
605 	}
606 
607 	@Test(expected = IllegalArgumentException.class)
testSecondInvalid()608 	public void testSecondInvalid() throws Exception {
609 		new CronTrigger("77 * * * * *", timeZone);
610 	}
611 
612 	@Test(expected = IllegalArgumentException.class)
testSecondRangeInvalid()613 	public void testSecondRangeInvalid() throws Exception {
614 		new CronTrigger("44-77 * * * * *", timeZone);
615 	}
616 
617 	@Test(expected = IllegalArgumentException.class)
testMinuteInvalid()618 	public void testMinuteInvalid() throws Exception {
619 		new CronTrigger("* 77 * * * *", timeZone);
620 	}
621 
622 	@Test(expected = IllegalArgumentException.class)
testMinuteRangeInvalid()623 	public void testMinuteRangeInvalid() throws Exception {
624 		new CronTrigger("* 44-77 * * * *", timeZone);
625 	}
626 
627 	@Test(expected = IllegalArgumentException.class)
testHourInvalid()628 	public void testHourInvalid() throws Exception {
629 		new CronTrigger("* * 27 * * *", timeZone);
630 	}
631 
632 	@Test(expected = IllegalArgumentException.class)
testHourRangeInvalid()633 	public void testHourRangeInvalid() throws Exception {
634 		new CronTrigger("* * 23-28 * * *", timeZone);
635 	}
636 
637 	@Test(expected = IllegalArgumentException.class)
testDayInvalid()638 	public void testDayInvalid() throws Exception {
639 		new CronTrigger("* * * 45 * *", timeZone);
640 	}
641 
642 	@Test(expected = IllegalArgumentException.class)
testDayRangeInvalid()643 	public void testDayRangeInvalid() throws Exception {
644 		new CronTrigger("* * * 28-45 * *", timeZone);
645 	}
646 
647 	@Test(expected = IllegalArgumentException.class)
testMonthInvalid()648 	public void testMonthInvalid() throws Exception {
649 		new CronTrigger("0 0 0 25 13 ?", timeZone);
650 	}
651 
652 	@Test(expected = IllegalArgumentException.class)
testMonthInvalidTooSmall()653 	public void testMonthInvalidTooSmall() throws Exception {
654 		new CronTrigger("0 0 0 25 0 ?", timeZone);
655 	}
656 
657 	@Test(expected = IllegalArgumentException.class)
testDayOfMonthInvalid()658 	public void testDayOfMonthInvalid() throws Exception {
659 		new CronTrigger("0 0 0 32 12 ?", timeZone);
660 	}
661 
662 	@Test(expected = IllegalArgumentException.class)
testMonthRangeInvalid()663 	public void testMonthRangeInvalid() throws Exception {
664 		new CronTrigger("* * * * 11-13 *", timeZone);
665 	}
666 
667 	@Test
testWhitespace()668 	public void testWhitespace() throws Exception {
669 		CronTrigger trigger1 = new CronTrigger("*  *  * *  1 *", timeZone);
670 		CronTrigger trigger2 = new CronTrigger("* * * * 1 *", timeZone);
671 		assertEquals(trigger1, trigger2);
672 	}
673 
674 	@Test
testMonthSequence()675 	public void testMonthSequence() throws Exception {
676 		CronTrigger trigger = new CronTrigger("0 30 23 30 1/3 ?", timeZone);
677 		calendar.set(2010, 11, 30);
678 		Date date = calendar.getTime();
679 		// set expected next trigger time
680 		calendar.set(Calendar.HOUR_OF_DAY, 23);
681 		calendar.set(Calendar.MINUTE, 30);
682 		calendar.set(Calendar.SECOND, 0);
683 		calendar.add(Calendar.MONTH, 1);
684 		TriggerContext context1 = getTriggerContext(date);
685 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context1));
686 		// Next trigger is 3 months latter
687 		calendar.add(Calendar.MONTH, 3);
688 		TriggerContext context2 = getTriggerContext(date);
689 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
690 		// Next trigger is 3 months latter
691 		calendar.add(Calendar.MONTH, 3);
692 		TriggerContext context3 = getTriggerContext(date);
693 		assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context3));
694 	}
695 
696 
assertMatchesNextSecond(CronTrigger trigger, Calendar calendar)697 	private void assertMatchesNextSecond(CronTrigger trigger, Calendar calendar) {
698 		Date date = calendar.getTime();
699 		roundup(calendar);
700 		TriggerContext context = getTriggerContext(date);
701 		assertEquals(calendar.getTime(), trigger.nextExecutionTime(context));
702 	}
703 
getTriggerContext(Date lastCompletionTime)704 	private static TriggerContext getTriggerContext(Date lastCompletionTime) {
705 		SimpleTriggerContext context = new SimpleTriggerContext();
706 		context.update(null, null, lastCompletionTime);
707 		return context;
708 	}
709 
710 }
711