1 /*
2 * Copyright 2004-2005 OpenSymphony
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 *
16 */
17
18 /*
19 * Previously Copyright (c) 2001-2004 James House
20 */
21 package org.quartz;
22
23 import java.text.ParseException;
24 import java.util.Calendar;
25 import java.util.Date;
26 import java.util.TimeZone;
27
28
29 /**
30 * <p>
31 * A concrete <code>{@link Trigger}</code> that is used to fire a <code>{@link org.quartz.JobDetail}</code>
32 * at given moments in time, defined with Unix 'cron-like' definitions.
33 * </p>
34 *
35 * <p>
36 * For those unfamiliar with "cron", this means being able to create a firing
37 * schedule such as: "At 8:00am every Monday through Friday" or "At 1:30am
38 * every last Friday of the month".
39 * </p>
40 *
41 * <p>
42 * The format of a "Cron-Expression" string is documented on the
43 * {@link org.quartz.CronExpression} class.
44 * </p>
45 *
46 * <p>
47 * Here are some full examples: <br><table cellspacing="8">
48 * <tr>
49 * <th align="left">Expression</th>
50 * <th align="left"> </th>
51 * <th align="left">Meaning</th>
52 * </tr>
53 * <tr>
54 * <td align="left"><code>"0 0 12 * * ?"</code></td>
55 * <td align="left"> </th>
56 * <td align="left"><code>Fire at 12pm (noon) every day</code></td>
57 * </tr>
58 * <tr>
59 * <td align="left"><code>"0 15 10 ? * *"</code></td>
60 * <td align="left"> </th>
61 * <td align="left"><code>Fire at 10:15am every day</code></td>
62 * </tr>
63 * <tr>
64 * <td align="left"><code>"0 15 10 * * ?"</code></td>
65 * <td align="left"> </th>
66 * <td align="left"><code>Fire at 10:15am every day</code></td>
67 * </tr>
68 * <tr>
69 * <td align="left"><code>"0 15 10 * * ? *"</code></td>
70 * <td align="left"> </th>
71 * <td align="left"><code>Fire at 10:15am every day</code></td>
72 * </tr>
73 * <tr>
74 * <td align="left"><code>"0 15 10 * * ? 2005"</code></td>
75 * <td align="left"> </th>
76 * <td align="left"><code>Fire at 10:15am every day during the year 2005</code>
77 * </td>
78 * </tr>
79 * <tr>
80 * <td align="left"><code>"0 * 14 * * ?"</code></td>
81 * <td align="left"> </th>
82 * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:59pm, every day</code>
83 * </td>
84 * </tr>
85 * <tr>
86 * <td align="left"><code>"0 0/5 14 * * ?"</code></td>
87 * <td align="left"> </th>
88 * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day</code>
89 * </td>
90 * </tr>
91 * <tr>
92 * <td align="left"><code>"0 0/5 14,18 * * ?"</code></td>
93 * <td align="left"> </th>
94 * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day</code>
95 * </td>
96 * </tr>
97 * <tr>
98 * <td align="left"><code>"0 0-5 14 * * ?"</code></td>
99 * <td align="left"> </th>
100 * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:05pm, every day</code>
101 * </td>
102 * </tr>
103 * <tr>
104 * <td align="left"><code>"0 10,44 14 ? 3 WED"</code></td>
105 * <td align="left"> </th>
106 * <td align="left"><code>Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.</code>
107 * </td>
108 * </tr>
109 * <tr>
110 * <td align="left"><code>"0 15 10 ? * MON-FRI"</code></td>
111 * <td align="left"> </th>
112 * <td align="left"><code>Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday</code>
113 * </td>
114 * </tr>
115 * <tr>
116 * <td align="left"><code>"0 15 10 15 * ?"</code></td>
117 * <td align="left"> </th>
118 * <td align="left"><code>Fire at 10:15am on the 15th day of every month</code>
119 * </td>
120 * </tr>
121 * <tr>
122 * <td align="left"><code>"0 15 10 L * ?"</code></td>
123 * <td align="left"> </th>
124 * <td align="left"><code>Fire at 10:15am on the last day of every month</code>
125 * </td>
126 * </tr>
127 * <tr>
128 * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
129 * <td align="left"> </th>
130 * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
131 * </td>
132 * </tr>
133 * <tr>
134 * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
135 * <td align="left"> </th>
136 * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
137 * </td>
138 * </tr>
139 * <tr>
140 * <td align="left"><code>"0 15 10 ? * 6L 2002-2005"</code></td>
141 * <td align="left"> </th>
142 * <td align="left"><code>Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005</code>
143 * </td>
144 * </tr>
145 * <tr>
146 * <td align="left"><code>"0 15 10 ? * 6#3"</code></td>
147 * <td align="left"> </th>
148 * <td align="left"><code>Fire at 10:15am on the third Friday of every month</code>
149 * </td>
150 * </tr>
151 * </table>
152 * </p>
153 *
154 * <p>
155 * Pay attention to the effects of '?' and '*' in the day-of-week and
156 * day-of-month fields!
157 * </p>
158 *
159 * <p>
160 * <b>NOTES:</b>
161 * <ul>
162 * <li>Support for specifying both a day-of-week and a day-of-month value is
163 * not complete (you'll need to use the '?' character in on of these fields).
164 * </li>
165 * <li>Be careful when setting fire times between mid-night and 1:00 AM -
166 * "daylight savings" can cause a skip or a repeat depending on whether the
167 * time moves back or jumps forward.</li>
168 * </ul>
169 * </p>
170 *
171 * @see Trigger
172 * @see SimpleTrigger
173 * @see TriggerUtils
174 *
175 * @author Sharada Jambula, James House
176 * @author Contributions from Mads Henderson
177 */
178 public class CronTrigger extends Trigger {
179
180 /*
181 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182 *
183 * Constants.
184 *
185 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
186 */
187
188 /**
189 * <p>
190 * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
191 * situation, the <code>{@link CronTrigger}</code> wants to be fired now
192 * by <code>Scheduler</code>.
193 * </p>
194 */
195 public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
196
197 /**
198 * <p>
199 * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
200 * situation, the <code>{@link CronTrigger}</code> wants to have it's
201 * next-fire-time updated to the next time in the schedule after the
202 * current time (taking into account any associated <code>{@link Calendar}</code>,
203 * but it does not want to be fired now.
204 * </p>
205 */
206 public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
207
208 /*
209 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
210 *
211 * Data members.
212 *
213 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214 */
215
216 private CronExpression cronEx = null;
217 private Date startTime = null;
218 private Date endTime = null;
219 private Date nextFireTime = null;
220 private Date previousFireTime = null;
221 private transient TimeZone timeZone = null;
222
223 /*
224 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
225 *
226 * Constructors.
227 *
228 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
229 */
230
231 /**
232 * <p>
233 * Create a <code>CronTrigger</code> with no settings.
234 * </p>
235 *
236 * <p>
237 * The start-time will also be set to the current time, and the time zone
238 * will be set the the system's default time zone.
239 * </p>
240 */
241 public CronTrigger() {
242 super();
243 setStartTime(new Date());
244 setTimeZone(TimeZone.getDefault());
245 }
246
247 /**
248 * <p>
249 * Create a <code>CronTrigger</code> with the given name and group.
250 * </p>
251 *
252 * <p>
253 * The start-time will also be set to the current time, and the time zone
254 * will be set the the system's default time zone.
255 * </p>
256 */
257 public CronTrigger(String name, String group) {
258 super(name, group);
259 setStartTime(new Date());
260 setTimeZone(TimeZone.getDefault());
261 }
262
263 /**
264 * <p>
265 * Create a <code>CronTrigger</code> with the given name, group and
266 * expression.
267 * </p>
268 *
269 * <p>
270 * The start-time will also be set to the current time, and the time zone
271 * will be set the the system's default time zone.
272 * </p>
273 */
274 public CronTrigger(String name, String group, String cronExpression)
275 throws ParseException {
276
277 super(name, group);
278
279 setCronExpression(cronExpression);
280
281 setStartTime(new Date());
282 setTimeZone(TimeZone.getDefault());
283 }
284
285 /**
286 * <p>
287 * Create a <code>CronTrigger</code> with the given name and group, and
288 * associated with the identified <code>{@link org.quartz.JobDetail}</code>.
289 * </p>
290 *
291 * <p>
292 * The start-time will also be set to the current time, and the time zone
293 * will be set the the system's default time zone.
294 * </p>
295 */
296 public CronTrigger(String name, String group, String jobName,
297 String jobGroup) {
298 super(name, group, jobName, jobGroup);
299 setStartTime(new Date());
300 setTimeZone(TimeZone.getDefault());
301 }
302
303 /**
304 * <p>
305 * Create a <code>CronTrigger</code> with the given name and group,
306 * associated with the identified <code>{@link org.quartz.JobDetail}</code>,
307 * and with the given "cron" expression.
308 * </p>
309 *
310 * <p>
311 * The start-time will also be set to the current time, and the time zone
312 * will be set the the system's default time zone.
313 * </p>
314 */
315 public CronTrigger(String name, String group, String jobName,
316 String jobGroup, String cronExpression) throws ParseException {
317 this(name, group, jobName, jobGroup, null, null, cronExpression,
318 TimeZone.getDefault());
319 }
320
321 /**
322 * <p>
323 * Create a <code>CronTrigger</code> with the given name and group,
324 * associated with the identified <code>{@link org.quartz.JobDetail}</code>,
325 * and with the given "cron" expression resolved with respect to the <code>TimeZone</code>.
326 * </p>
327 */
328 public CronTrigger(String name, String group, String jobName,
329 String jobGroup, String cronExpression, TimeZone timeZone)
330 throws ParseException {
331 this(name, group, jobName, jobGroup, null, null, cronExpression,
332 timeZone);
333 }
334
335 /**
336 * <p>
337 * Create a <code>CronTrigger</code> that will occur at the given time,
338 * until the given end time.
339 * </p>
340 *
341 * <p>
342 * If null, the start-time will also be set to the current time, the time
343 * zone will be set the the system's default.
344 * </p>
345 *
346 * @param startTime
347 * A <code>Date</code> set to the time for the <code>Trigger</code>
348 * to fire.
349 * @param endTime
350 * A <code>Date</code> set to the time for the <code>Trigger</code>
351 * to quit repeat firing.
352 */
353 public CronTrigger(String name, String group, String jobName,
354 String jobGroup, Date startTime, Date endTime, String cronExpression)
355 throws ParseException {
356 super(name, group, jobName, jobGroup);
357
358 setCronExpression(cronExpression);
359
360 if (startTime == null) {
361 startTime = new Date();
362 }
363 setStartTime(startTime);
364 if (endTime != null) {
365 setEndTime(endTime);
366 }
367 setTimeZone(TimeZone.getDefault());
368
369 }
370
371 /**
372 * <p>
373 * Create a <code>CronTrigger</code> with fire time dictated by the
374 * <code>cronExpression</code> resolved with respect to the specified
375 * <code>timeZone</code> occuring from the <code>startTime</code> until
376 * the given <code>endTime</code>.
377 * </p>
378 *
379 * <p>
380 * If null, the start-time will also be set to the current time. If null,
381 * the time zone will be set to the system's default.
382 * </p>
383 *
384 * @param name
385 * of the <code>Trigger</code>
386 * @param group
387 * of the <code>Trigger</code>
388 * @param jobName
389 * name of the <code>{@link org.quartz.JobDetail}</code>
390 * executed on firetime
391 * @param jobGroup
392 * group of the <code>{@link org.quartz.JobDetail}</code>
393 * executed on firetime
394 * @param startTime
395 * A <code>Date</code> set to the earliest time for the <code>Trigger</code>
396 * to start firing.
397 * @param endTime
398 * A <code>Date</code> set to the time for the <code>Trigger</code>
399 * to quit repeat firing.
400 * @param cronExpression
401 * A cron expression dictating the firing sequence of the <code>Trigger</code>
402 * @param timeZone
403 * Specifies for which time zone the <code>cronExpression</code>
404 * should be interprted, i.e. the expression 0 0 10 * * ?, is
405 * resolved to 10:00 am in this time zone.
406 * @throws ParseException
407 * if the <code>cronExpression</code> is invalid.
408 */
409 public CronTrigger(String name, String group, String jobName,
410 String jobGroup, Date startTime, Date endTime,
411 String cronExpression, TimeZone timeZone) throws ParseException {
412 super(name, group, jobName, jobGroup);
413
414 setCronExpression(cronExpression);
415
416 if (startTime == null) {
417 startTime = new Date();
418 }
419 setStartTime(startTime);
420 if (endTime != null) {
421 setEndTime(endTime);
422 }
423 if (timeZone == null) {
424 setTimeZone(TimeZone.getDefault());
425 } else {
426 setTimeZone(timeZone);
427 }
428 }
429
430 /*
431 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
432 *
433 * Interface.
434 *
435 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
436 */
437
438 public Object clone() {
439 CronTrigger copy = (CronTrigger) super.clone();
440 copy.setCronExpression((CronExpression)cronEx.clone());
441 return copy;
442 }
443
444 public void setCronExpression(String cronExpression) throws ParseException {
445 this.cronEx = new CronExpression(cronExpression);
446 this.cronEx.setTimeZone(getTimeZone());
447 }
448
449 public String getCronExpression() {
450 return cronEx == null ? null : cronEx.getCronExpression();
451 }
452
453 public void setCronExpression(CronExpression cronExpression) {
454 this.cronEx = cronExpression;
455 this.timeZone = cronExpression.getTimeZone();
456 }
457
458 /**
459 * <p>
460 * Get the time at which the <code>CronTrigger</code> should occur.
461 * </p>
462 */
463 public Date getStartTime() {
464 return this.startTime;
465 }
466
467 public void setStartTime(Date startTime) {
468 if (startTime == null) {
469 throw new IllegalArgumentException("Start time cannot be null");
470 }
471
472 Date eTime = getEndTime();
473 if (eTime != null && startTime != null && eTime.before(startTime)) {
474 throw new IllegalArgumentException(
475 "End time cannot be before start time");
476 }
477
478 // round off millisecond...
479 // Note timeZone is not needed here as parameter for
480 // Calendar.getInstance(),
481 // since time zone is implicit when using a Date in the setTime method.
482 Calendar cl = Calendar.getInstance();
483 cl.setTime(startTime);
484 cl.set(Calendar.MILLISECOND, 0);
485
486 this.startTime = cl.getTime();
487 }
488
489 /**
490 * <p>
491 * Get the time at which the <code>CronTrigger</code> should quit
492 * repeating - even if repeastCount isn't yet satisfied.
493 * </p>
494 *
495 * @see #getFinalFireTime()
496 */
497 public Date getEndTime() {
498 return this.endTime;
499 }
500
501 public void setEndTime(Date endTime) {
502 Date sTime = getStartTime();
503 if (sTime != null && endTime != null && sTime.after(endTime)) {
504 throw new IllegalArgumentException(
505 "End time cannot be before start time");
506 }
507
508 this.endTime = endTime;
509 }
510
511 /**
512 * <p>
513 * Returns the next time at which the <code>CronTrigger</code> will fire.
514 * If the trigger will not fire again, <code>null</code> will be
515 * returned. The value returned is not guaranteed to be valid until after
516 * the <code>Trigger</code> has been added to the scheduler.
517 * </p>
518 */
519 public Date getNextFireTime() {
520 return this.nextFireTime;
521 }
522
523 /**
524 * <p>
525 * Returns the previous time at which the <code>CronTrigger</code> will
526 * fire. If the trigger has not yet fired, <code>null</code> will be
527 * returned.
528 */
529 public Date getPreviousFireTime() {
530 return this.previousFireTime;
531 }
532
533 /**
534 * <p>
535 * Sets the next time at which the <code>CronTrigger</code> will fire.
536 * <b>This method should not be invoked by client code.</b>
537 * </p>
538 */
539 public void setNextFireTime(Date nextFireTime) {
540 this.nextFireTime = nextFireTime;
541 }
542
543 /**
544 * <p>
545 * Set the previous time at which the <code>CronTrigger</code> fired.
546 * </p>
547 *
548 * <p>
549 * <b>This method should not be invoked by client code.</b>
550 * </p>
551 */
552 public void setPreviousFireTime(Date previousFireTime) {
553 this.previousFireTime = previousFireTime;
554 }
555
556 /**
557 * <p>
558 * Returns the time zone for which the <code>cronExpression</code> of
559 * this <code>CronTrigger</code> will be resolved.
560 * </p>
561 */
562 public TimeZone getTimeZone() {
563
564 if(cronEx != null) {
565 return cronEx.getTimeZone();
566 }
567
568 if (timeZone == null) {
569 timeZone = TimeZone.getDefault();
570 }
571 return timeZone;
572 }
573
574 /**
575 * <p>
576 * Sets the time zone for which the <code>cronExpression</code> of this
577 * <code>CronTrigger</code> will be resolved.
578 * </p>
579 */
580 public void setTimeZone(TimeZone timeZone) {
581 if(cronEx != null) {
582 cronEx.setTimeZone(timeZone);
583 }
584 this.timeZone = timeZone;
585 }
586
587 /**
588 * <p>
589 * Returns the next time at which the <code>CronTrigger</code> will fire,
590 * after the given time. If the trigger will not fire after the given time,
591 * <code>null</code> will be returned.
592 * </p>
593 *
594 * <p>
595 * Note that the date returned is NOT validated against the related
596 * org.quartz.Calendar (if any)
597 * </p>
598 */
599 public Date getFireTimeAfter(Date afterTime) {
600 if (afterTime == null) {
601 afterTime = new Date();
602 }
603
604 if (getStartTime().after(afterTime)) {
605 afterTime = new Date(getStartTime().getTime() - 1000l);
606 }
607
608 if (getEndTime() != null && (afterTime.compareTo(getEndTime()) >= 0)) {
609 return null;
610 }
611
612 Date pot = getTimeAfter(afterTime);
613 if (getEndTime() != null && pot != null && pot.after(getEndTime())) {
614 return null;
615 }
616
617 return pot;
618 }
619
620 /**
621 * <p>
622 * NOT YET IMPLEMENTED: Returns the final time at which the
623 * <code>CronTrigger</code> will fire.
624 * </p>
625 *
626 * <p>
627 * Note that the return time *may* be in the past. and the date returned is
628 * not validated against org.quartz.calendar
629 * </p>
630 */
631 public Date getFinalFireTime() {
632 Date resultTime;
633 if (getEndTime() != null) {
634 resultTime = getTimeBefore(new Date(getEndTime().getTime() + 1000l));
635 } else {
636 resultTime = (cronEx == null) ? null : cronEx.getFinalFireTime();
637 }
638
639 if ((resultTime != null) && (getStartTime() != null) && (resultTime.before(getStartTime()))) {
640 return null;
641 }
642
643 return resultTime;
644 }
645
646 /**
647 * <p>
648 * Determines whether or not the <code>CronTrigger</code> will occur
649 * again.
650 * </p>
651 */
652 public boolean mayFireAgain() {
653 return (getNextFireTime() != null);
654 }
655
656 protected boolean validateMisfireInstruction(int misfireInstruction) {
657 if (misfireInstruction < MISFIRE_INSTRUCTION_SMART_POLICY) {
658 return false;
659 }
660
661 if (misfireInstruction > MISFIRE_INSTRUCTION_DO_NOTHING) {
662 return false;
663 }
664
665 return true;
666 }
667
668 /**
669 * <p>
670 * Updates the <code>CronTrigger</code>'s state based on the
671 * MISFIRE_INSTRUCTION_XXX that was selected when the <code>CronTrigger</code>
672 * was created.
673 * </p>
674 *
675 * <p>
676 * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY,
677 * then the following scheme will be used: <br>
678 * <ul>
679 * <li>The instruction will be interpreted as <code>MISFIRE_INSTRUCTION_FIRE_ONCE_NOW</code>
680 * </ul>
681 * </p>
682 */
683 public void updateAfterMisfire(org.quartz.Calendar cal) {
684 int instr = getMisfireInstruction();
685
686 if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) {
687 instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW;
688 }
689
690 if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {
691 Date newFireTime = getFireTimeAfter(new Date());
692 while (newFireTime != null && cal != null
693 && !cal.isTimeIncluded(newFireTime.getTime())) {
694 newFireTime = getFireTimeAfter(newFireTime);
695 }
696 setNextFireTime(newFireTime);
697 } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {
698 setNextFireTime(new Date());
699 }
700 }
701
702 /**
703 * <p>
704 * Determines whether the date and (optionally) time of the given Calendar
705 * instance falls on a scheduled fire-time of this trigger.
706 * </p>
707 *
708 * <p>
709 * Equivalent to calling <code>willFireOn(cal, false)</code>.
710 * </p>
711 *
712 * @param test the date to compare
713 *
714 * @see #willFireOn(Calendar, boolean)
715 */
716 public boolean willFireOn(Calendar test) {
717 return willFireOn(test, false);
718 }
719
720 /**
721 * <p>
722 * Determines whether the date and (optionally) time of the given Calendar
723 * instance falls on a scheduled fire-time of this trigger.
724 * </p>
725 *
726 * <p>
727 * Note that the value returned is NOT validated against the related
728 * org.quartz.Calendar (if any)
729 * </p>
730 *
731 * @param test the date to compare
732 * @param dayOnly if set to true, the method will only determine if the
733 * trigger will fire during the day represented by the given Calendar
734 * (hours, minutes and seconds will be ignored).
735 * @see #willFireOn(Calendar)
736 */
737 public boolean willFireOn(Calendar test, boolean dayOnly) {
738
739 test = (Calendar) test.clone();
740
741 test.set(Calendar.MILLISECOND, 0); // don't compare millis.
742
743 if(dayOnly) {
744 test.set(Calendar.HOUR, 0);
745 test.set(Calendar.MINUTE, 0);
746 test.set(Calendar.SECOND, 0);
747 }
748
749 Date testTime = test.getTime();
750
751 Date fta = getFireTimeAfter(new Date(test.getTime().getTime() - 1000));
752
753 Calendar p = Calendar.getInstance(test.getTimeZone());
754 p.setTime(fta);
755
756 int year = p.get(Calendar.YEAR);
757 int month = p.get(Calendar.MONTH);
758 int day = p.get(Calendar.DATE);
759
760 if(dayOnly) {
761 return (year == test.get(Calendar.YEAR)
762 && month == test.get(Calendar.MONTH)
763 && day == test.get(Calendar.DATE));
764 }
765
766 while(fta.before(testTime)) {
767 fta = getFireTimeAfter(fta);
768 }
769
770 if(fta.equals(testTime)) {
771 return true;
772 }
773
774 return false;
775 }
776
777 /**
778 * <p>
779 * Called after the <code>{@link Scheduler}</code> has executed the
780 * <code>{@link org.quartz.JobDetail}</code> associated with the <code>Trigger</code>
781 * in order to get the final instruction code from the trigger.
782 * </p>
783 *
784 * @param context
785 * is the <code>JobExecutionContext</code> that was used by the
786 * <code>Job</code>'s<code>execute(xx)</code> method.
787 * @param result
788 * is the <code>JobExecutionException</code> thrown by the
789 * <code>Job</code>, if any (may be null).
790 * @return one of the Trigger.INSTRUCTION_XXX constants.
791 *
792 * @see #INSTRUCTION_NOOP
793 * @see #INSTRUCTION_RE_EXECUTE_JOB
794 * @see #INSTRUCTION_DELETE_TRIGGER
795 * @see #INSTRUCTION_SET_TRIGGER_COMPLETE
796 * @see #triggered(Calendar)
797 */
798 public int executionComplete(JobExecutionContext context,
799 JobExecutionException result) {
800 if (result != null && result.refireImmediately()) {
801 return INSTRUCTION_RE_EXECUTE_JOB;
802 }
803
804 if (result != null && result.unscheduleFiringTrigger()) {
805 return INSTRUCTION_SET_TRIGGER_COMPLETE;
806 }
807
808 if (result != null && result.unscheduleAllTriggers()) {
809 return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE;
810 }
811
812 if (!mayFireAgain()) {
813 return INSTRUCTION_DELETE_TRIGGER;
814 }
815
816 return INSTRUCTION_NOOP;
817 }
818
819 /**
820 * <p>
821 * Called when the <code>{@link Scheduler}</code> has decided to 'fire'
822 * the trigger (execute the associated <code>Job</code>), in order to
823 * give the <code>Trigger</code> a chance to update itself for its next
824 * triggering (if any).
825 * </p>
826 *
827 * @see #executionComplete(JobExecutionContext, JobExecutionException)
828 */
829 public void triggered(org.quartz.Calendar calendar) {
830 previousFireTime = nextFireTime;
831 nextFireTime = getFireTimeAfter(nextFireTime);
832
833 while (nextFireTime != null && calendar != null
834 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
835 nextFireTime = getFireTimeAfter(nextFireTime);
836 }
837 }
838
839 /**
840 *
841 * @see org.quartz.Trigger#updateWithNewCalendar(org.quartz.Calendar, long)
842 */
843 public void updateWithNewCalendar(org.quartz.Calendar calendar, long misfireThreshold)
844 {
845 nextFireTime = getFireTimeAfter(previousFireTime);
846
847 Date now = new Date();
848 do {
849 while (nextFireTime != null && calendar != null
850 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
851 nextFireTime = getFireTimeAfter(nextFireTime);
852 }
853
854 if(nextFireTime != null && nextFireTime.before(now)) {
855 long diff = now.getTime() - nextFireTime.getTime();
856 if(diff >= misfireThreshold) {
857 nextFireTime = getFireTimeAfter(nextFireTime);
858 continue;
859 }
860 }
861 }while(false);
862 }
863
864 /**
865 * <p>
866 * Called by the scheduler at the time a <code>Trigger</code> is first
867 * added to the scheduler, in order to have the <code>Trigger</code>
868 * compute its first fire time, based on any associated calendar.
869 * </p>
870 *
871 * <p>
872 * After this method has been called, <code>getNextFireTime()</code>
873 * should return a valid answer.
874 * </p>
875 *
876 * @return the first time at which the <code>Trigger</code> will be fired
877 * by the scheduler, which is also the same value <code>getNextFireTime()</code>
878 * will return (until after the first firing of the <code>Trigger</code>).
879 * </p>
880 */
881 public Date computeFirstFireTime(org.quartz.Calendar calendar) {
882 nextFireTime = getFireTimeAfter(new Date(getStartTime().getTime() - 1000l));
883
884 while (nextFireTime != null && calendar != null
885 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
886 nextFireTime = getFireTimeAfter(nextFireTime);
887 }
888
889 return nextFireTime;
890 }
891
892 public String getExpressionSummary() {
893 return cronEx == null ? null : cronEx.getExpressionSummary();
894 }
895
896 ////////////////////////////////////////////////////////////////////////////
897 //
898 // Computation Functions
899 //
900 ////////////////////////////////////////////////////////////////////////////
901
902 protected Date getTimeAfter(Date afterTime) {
903 return (cronEx == null) ? null : cronEx.getTimeAfter(afterTime);
904 }
905
906 /**
907 * NOT YET IMPLEMENTED: Returns the time before the given time
908 * that this <code>CronTrigger</code> will fire.
909 */
910 protected Date getTimeBefore(Date endTime) {
911 return (cronEx == null) ? null : cronEx.getTimeBefore(endTime);
912 }
913
914 public static void main(String[] args) // TODO: remove method after good
915 // unit testing
916 throws Exception {
917
918 String expr = "15 10 0/4 * * ?";
919 if(args != null && args.length > 0 && args[0] != null) {
920 expr = args[0];
921 }
922
923 CronTrigger ct = new CronTrigger("t", "g", "j", "g", new Date(), null, expr);
924 ct.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
925 System.err.println(ct.getExpressionSummary());
926 System.err.println("tz=" + ct.getTimeZone().getID());
927 System.err.println();
928
929 java.util.List times = TriggerUtils.computeFireTimes(ct, null, 25);
930
931 for (int i = 0; i < times.size(); i++) {
932 System.err.println("firetime = " + times.get(i));
933 }
934
935 Calendar tt = Calendar.getInstance();
936 tt.set(Calendar.DATE, 17);
937 tt.set(Calendar.MONTH, 5 - 1);
938 tt.set(Calendar.HOUR, 11);
939 tt.set(Calendar.MINUTE, 0);
940 tt.set(Calendar.SECOND, 7);
941
942 System.err.println("\nWill fire on: " + tt.getTime() + " -- " + ct.willFireOn(tt, false));
943
944
945 // CRON Expression: 0 0 9 * * ?
946 //
947 // TimeZone.getDefault().getDisplayName() = Central African Time
948 // TimeZone.getDefault().getID() = Africa/Harare
949 //
950 //// System.err.println();
951 //// System.err.println();
952 //// System.err.println();
953 //// System.err.println("Daylight test:");
954 ////
955 //// CronTrigger trigger = new CronTrigger();
956 ////
957 //// TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
958 //// // TimeZone timeZone = TimeZone.getDefault();
959 ////
960 //// trigger.setTimeZone(timeZone);
961 //// trigger.setCronExpression("0 0 1 ? 4 *");
962 ////
963 //// Date start = new Date(1056319200000L);
964 //// Date end = new Date(1087682399000L);
965 ////
966 //// trigger.setStartTime(start);
967 //// trigger.setEndTime(end);
968 ////
969 //// Date next = new Date(1056232800000L);
970 //// while (next != null) {
971 //// next = trigger.getFireTimeAfter(next);
972 //// if (next != null) {
973 //// Calendar cal = Calendar.getInstance();
974 //// cal.setTimeZone(timeZone);
975 //// cal.setTime(next);
976 //// System.err.println(cal.get(Calendar.MONTH) + "/"
977 //// + cal.get(Calendar.DATE) + "/" + cal.get(Calendar.YEAR)
978 //// + " - " + cal.get(Calendar.HOUR_OF_DAY) + ":"
979 //// + cal.get(Calendar.MINUTE));
980 //// }
981 //// }
982 ////
983 //// System.err.println();
984 //// System.err.println();
985 //// System.err.println();
986 //// System.err.println("Midnight test:");
987 ////
988 //// trigger = new CronTrigger();
989 ////
990 //// timeZone = TimeZone.getTimeZone("Asia/Jerusalem");
991 //// // timeZone = TimeZone.getTimeZone("America/Los_Angeles");
992 //// // TimeZone timeZone = TimeZone.getDefault();
993 ////
994 //// trigger.setTimeZone(timeZone);
995 //// trigger.setCronExpression("0 /15 * ? 4 *");
996 ////
997 //// start = new Date(1056319200000L);
998 //// end = new Date(1087682399000L);
999 ////
1000 //// trigger.setStartTime(start);
1001 //// trigger.setEndTime(end);
1002 ////
1003 //// next = new Date(1056232800000L);
1004 //// while (next != null) {
1005 //// next = trigger.getFireTimeAfter(next);
1006 //// if (next != null) {
1007 //// Calendar cal = Calendar.getInstance();
1008 //// cal.setTimeZone(timeZone);
1009 //// cal.setTime(next);
1010 //// System.err.println(cal.get(Calendar.MONTH) + "/"
1011 //// + cal.get(Calendar.DATE) + "/" + cal.get(Calendar.YEAR)
1012 //// + " - " + cal.get(Calendar.HOUR_OF_DAY) + ":"
1013 //// + cal.get(Calendar.MINUTE));
1014 //// }
1015 //// }
1016
1017 }
1018 }
1019