1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.util;
18
19
20
21
22
23
24 import java.text.ParseException;
25 import java.util.Calendar;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.SortedSet;
32 import java.util.StringTokenizer;
33 import java.util.TimeZone;
34 import java.util.TreeSet;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 public final class CronExpression {
198
199 protected static final int SECOND = 0;
200 protected static final int MINUTE = 1;
201 protected static final int HOUR = 2;
202 protected static final int DAY_OF_MONTH = 3;
203 protected static final int MONTH = 4;
204 protected static final int DAY_OF_WEEK = 5;
205 protected static final int YEAR = 6;
206 protected static final int ALL_SPEC_INT = 99;
207 protected static final int NO_SPEC_INT = 98;
208 protected static final Integer ALL_SPEC = ALL_SPEC_INT;
209 protected static final Integer NO_SPEC = NO_SPEC_INT;
210
211 protected static final Map<String, Integer> monthMap = new HashMap<>(20);
212 protected static final Map<String, Integer> dayMap = new HashMap<>(60);
213
214 static {
215 monthMap.put("JAN", 0);
216 monthMap.put("FEB", 1);
217 monthMap.put("MAR", 2);
218 monthMap.put("APR", 3);
219 monthMap.put("MAY", 4);
220 monthMap.put("JUN", 5);
221 monthMap.put("JUL", 6);
222 monthMap.put("AUG", 7);
223 monthMap.put("SEP", 8);
224 monthMap.put("OCT", 9);
225 monthMap.put("NOV", 10);
226 monthMap.put("DEC", 11);
227
228 dayMap.put("SUN", 1);
229 dayMap.put("MON", 2);
230 dayMap.put("TUE", 3);
231 dayMap.put("WED", 4);
232 dayMap.put("THU", 5);
233 dayMap.put("FRI", 6);
234 dayMap.put("SAT", 7);
235 }
236
237 private final String cronExpression;
238 private TimeZone timeZone = null;
239 protected transient TreeSet<Integer> seconds;
240 protected transient TreeSet<Integer> minutes;
241 protected transient TreeSet<Integer> hours;
242 protected transient TreeSet<Integer> daysOfMonth;
243 protected transient TreeSet<Integer> months;
244 protected transient TreeSet<Integer> daysOfWeek;
245 protected transient TreeSet<Integer> years;
246
247 protected transient boolean lastdayOfWeek = false;
248 protected transient int nthdayOfWeek = 0;
249 protected transient boolean lastdayOfMonth = false;
250 protected transient boolean nearestWeekday = false;
251 protected transient int lastdayOffset = 0;
252 protected transient boolean expressionParsed = false;
253
254 public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
255 public static final Calendar MIN_CAL = Calendar.getInstance();
256 static {
257 MIN_CAL.set(1970, 0, 1);
258 }
259 public static final Date MIN_DATE = MIN_CAL.getTime();
260
261
262
263
264
265
266
267
268
269
270 public CronExpression(final String cronExpression) throws ParseException {
271 if (cronExpression == null) {
272 throw new IllegalArgumentException("cronExpression cannot be null");
273 }
274
275 this.cronExpression = cronExpression.toUpperCase(Locale.US);
276
277 buildExpression(this.cronExpression);
278 }
279
280
281
282
283
284
285
286
287
288
289 public boolean isSatisfiedBy(final Date date) {
290 final Calendar testDateCal = Calendar.getInstance(getTimeZone());
291 testDateCal.setTime(date);
292 testDateCal.set(Calendar.MILLISECOND, 0);
293 final Date originalDate = testDateCal.getTime();
294
295 testDateCal.add(Calendar.SECOND, -1);
296
297 final Date timeAfter = getTimeAfter(testDateCal.getTime());
298
299 return ((timeAfter != null) && (timeAfter.equals(originalDate)));
300 }
301
302
303
304
305
306
307
308
309
310 public Date getNextValidTimeAfter(final Date date) {
311 return getTimeAfter(date);
312 }
313
314
315
316
317
318
319
320
321
322 public Date getNextInvalidTimeAfter(final Date date) {
323 long difference = 1000;
324
325
326 final Calendar adjustCal = Calendar.getInstance(getTimeZone());
327 adjustCal.setTime(date);
328 adjustCal.set(Calendar.MILLISECOND, 0);
329 Date lastDate = adjustCal.getTime();
330
331 Date newDate;
332
333
334
335
336
337
338 while (difference == 1000) {
339 newDate = getTimeAfter(lastDate);
340 if (newDate == null) {
341 break;
342 }
343
344 difference = newDate.getTime() - lastDate.getTime();
345
346 if (difference == 1000) {
347 lastDate = newDate;
348 }
349 }
350
351 return new Date(lastDate.getTime() + 1000);
352 }
353
354
355
356
357
358 public TimeZone getTimeZone() {
359 if (timeZone == null) {
360 timeZone = TimeZone.getDefault();
361 }
362
363 return timeZone;
364 }
365
366
367
368
369
370 public void setTimeZone(final TimeZone timeZone) {
371 this.timeZone = timeZone;
372 }
373
374
375
376
377
378
379 @Override
380 public String toString() {
381 return cronExpression;
382 }
383
384
385
386
387
388
389
390
391
392 public static boolean isValidExpression(final String cronExpression) {
393
394 try {
395 new CronExpression(cronExpression);
396 } catch (final ParseException pe) {
397 return false;
398 }
399
400 return true;
401 }
402
403 public static void validateExpression(final String cronExpression) throws ParseException {
404
405 new CronExpression(cronExpression);
406 }
407
408
409
410
411
412
413
414
415 protected void buildExpression(final String expression) throws ParseException {
416 expressionParsed = true;
417
418 try {
419
420 if (seconds == null) {
421 seconds = new TreeSet<>();
422 }
423 if (minutes == null) {
424 minutes = new TreeSet<>();
425 }
426 if (hours == null) {
427 hours = new TreeSet<>();
428 }
429 if (daysOfMonth == null) {
430 daysOfMonth = new TreeSet<>();
431 }
432 if (months == null) {
433 months = new TreeSet<>();
434 }
435 if (daysOfWeek == null) {
436 daysOfWeek = new TreeSet<>();
437 }
438 if (years == null) {
439 years = new TreeSet<>();
440 }
441
442 int exprOn = SECOND;
443
444 final StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
445 false);
446
447 while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
448 final String expr = exprsTok.nextToken().trim();
449
450
451 if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
452 throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
453 }
454
455 if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
456 throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
457 }
458 if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) {
459 throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
460 }
461
462 final StringTokenizer vTok = new StringTokenizer(expr, ",");
463 while (vTok.hasMoreTokens()) {
464 final String v = vTok.nextToken();
465 storeExpressionVals(0, v, exprOn);
466 }
467
468 exprOn++;
469 }
470
471 if (exprOn <= DAY_OF_WEEK) {
472 throw new ParseException("Unexpected end of expression.",
473 expression.length());
474 }
475
476 if (exprOn <= YEAR) {
477 storeExpressionVals(0, "*", YEAR);
478 }
479
480 final TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
481 final TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
482
483
484 final boolean dayOfMSpec = !dom.contains(NO_SPEC);
485 final boolean dayOfWSpec = !dow.contains(NO_SPEC);
486
487 if (!dayOfMSpec || dayOfWSpec) {
488 if (!dayOfWSpec || dayOfMSpec) {
489 throw new ParseException(
490 "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
491 }
492 }
493 } catch (final ParseException pe) {
494 throw pe;
495 } catch (final Exception e) {
496 throw new ParseException("Illegal cron expression format ("
497 + e.toString() + ")", 0);
498 }
499 }
500
501 protected int storeExpressionVals(final int pos, final String s, final int type)
502 throws ParseException {
503
504 int incr = 0;
505 int i = skipWhiteSpace(pos, s);
506 if (i >= s.length()) {
507 return i;
508 }
509 char c = s.charAt(i);
510 if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
511 String sub = s.substring(i, i + 3);
512 int sval = -1;
513 int eval = -1;
514 if (type == MONTH) {
515 sval = getMonthNumber(sub) + 1;
516 if (sval <= 0) {
517 throw new ParseException("Invalid Month value: '" + sub + "'", i);
518 }
519 if (s.length() > i + 3) {
520 c = s.charAt(i + 3);
521 if (c == '-') {
522 i += 4;
523 sub = s.substring(i, i + 3);
524 eval = getMonthNumber(sub) + 1;
525 if (eval <= 0) {
526 throw new ParseException("Invalid Month value: '" + sub + "'", i);
527 }
528 }
529 }
530 } else if (type == DAY_OF_WEEK) {
531 sval = getDayOfWeekNumber(sub);
532 if (sval < 0) {
533 throw new ParseException("Invalid Day-of-Week value: '"
534 + sub + "'", i);
535 }
536 if (s.length() > i + 3) {
537 c = s.charAt(i + 3);
538 if (c == '-') {
539 i += 4;
540 sub = s.substring(i, i + 3);
541 eval = getDayOfWeekNumber(sub);
542 if (eval < 0) {
543 throw new ParseException(
544 "Invalid Day-of-Week value: '" + sub
545 + "'", i);
546 }
547 } else if (c == '#') {
548 try {
549 i += 4;
550 nthdayOfWeek = Integer.parseInt(s.substring(i));
551 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
552 throw new Exception();
553 }
554 } catch (final Exception e) {
555 throw new ParseException(
556 "A numeric value between 1 and 5 must follow the '#' option",
557 i);
558 }
559 } else if (c == 'L') {
560 lastdayOfWeek = true;
561 i++;
562 }
563 }
564
565 } else {
566 throw new ParseException(
567 "Illegal characters for this position: '" + sub + "'",
568 i);
569 }
570 if (eval != -1) {
571 incr = 1;
572 }
573 addToSet(sval, eval, incr, type);
574 return (i + 3);
575 }
576
577 if (c == '?') {
578 i++;
579 if ((i + 1) < s.length()
580 && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
581 throw new ParseException("Illegal character after '?': "
582 + s.charAt(i), i);
583 }
584 if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
585 throw new ParseException(
586 "'?' can only be specfied for Day-of-Month or Day-of-Week.",
587 i);
588 }
589 if (type == DAY_OF_WEEK && !lastdayOfMonth) {
590 final int val = daysOfMonth.last();
591 if (val == NO_SPEC_INT) {
592 throw new ParseException(
593 "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
594 i);
595 }
596 }
597
598 addToSet(NO_SPEC_INT, -1, 0, type);
599 return i;
600 }
601
602 if (c == '*' || c == '/') {
603 if (c == '*' && (i + 1) >= s.length()) {
604 addToSet(ALL_SPEC_INT, -1, incr, type);
605 return i + 1;
606 } else if (c == '/'
607 && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
608 .charAt(i + 1) == '\t')) {
609 throw new ParseException("'/' must be followed by an integer.", i);
610 } else if (c == '*') {
611 i++;
612 }
613 c = s.charAt(i);
614 if (c == '/') {
615 i++;
616 if (i >= s.length()) {
617 throw new ParseException("Unexpected end of string.", i);
618 }
619
620 incr = getNumericValue(s, i);
621
622 i++;
623 if (incr > 10) {
624 i++;
625 }
626 if (incr > 59 && (type == SECOND || type == MINUTE)) {
627 throw new ParseException("Increment > 60 : " + incr, i);
628 } else if (incr > 23 && (type == HOUR)) {
629 throw new ParseException("Increment > 24 : " + incr, i);
630 } else if (incr > 31 && (type == DAY_OF_MONTH)) {
631 throw new ParseException("Increment > 31 : " + incr, i);
632 } else if (incr > 7 && (type == DAY_OF_WEEK)) {
633 throw new ParseException("Increment > 7 : " + incr, i);
634 } else if (incr > 12 && (type == MONTH)) {
635 throw new ParseException("Increment > 12 : " + incr, i);
636 }
637 } else {
638 incr = 1;
639 }
640
641 addToSet(ALL_SPEC_INT, -1, incr, type);
642 return i;
643 } else if (c == 'L') {
644 i++;
645 if (type == DAY_OF_MONTH) {
646 lastdayOfMonth = true;
647 }
648 if (type == DAY_OF_WEEK) {
649 addToSet(7, 7, 0, type);
650 }
651 if (type == DAY_OF_MONTH && s.length() > i) {
652 c = s.charAt(i);
653 if (c == '-') {
654 final ValueSet vs = getValue(0, s, i + 1);
655 lastdayOffset = vs.value;
656 if (lastdayOffset > 30) {
657 throw new ParseException("Offset from last day must be <= 30", i + 1);
658 }
659 i = vs.pos;
660 }
661 if (s.length() > i) {
662 c = s.charAt(i);
663 if (c == 'W') {
664 nearestWeekday = true;
665 i++;
666 }
667 }
668 }
669 return i;
670 } else if (c >= '0' && c <= '9') {
671 int val = Integer.parseInt(String.valueOf(c));
672 i++;
673 if (i >= s.length()) {
674 addToSet(val, -1, -1, type);
675 } else {
676 c = s.charAt(i);
677 if (c >= '0' && c <= '9') {
678 final ValueSet vs = getValue(val, s, i);
679 val = vs.value;
680 i = vs.pos;
681 }
682 i = checkNext(i, s, val, type);
683 return i;
684 }
685 } else {
686 throw new ParseException("Unexpected character: " + c, i);
687 }
688
689 return i;
690 }
691
692 protected int checkNext(final int pos, final String s, final int val, final int type)
693 throws ParseException {
694
695 int end = -1;
696 int i = pos;
697
698 if (i >= s.length()) {
699 addToSet(val, end, -1, type);
700 return i;
701 }
702
703 char c = s.charAt(pos);
704
705 if (c == 'L') {
706 if (type == DAY_OF_WEEK) {
707 if (val < 1 || val > 7) {
708 throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
709 }
710 lastdayOfWeek = true;
711 } else {
712 throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
713 }
714 final TreeSet<Integer> set = getSet(type);
715 set.add(val);
716 i++;
717 return i;
718 }
719
720 if (c == 'W') {
721 if (type == DAY_OF_MONTH) {
722 nearestWeekday = true;
723 } else {
724 throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
725 }
726 if (val > 31) {
727 throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
728 }
729 final TreeSet<Integer> set = getSet(type);
730 set.add(val);
731 i++;
732 return i;
733 }
734
735 if (c == '#') {
736 if (type != DAY_OF_WEEK) {
737 throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
738 }
739 i++;
740 try {
741 nthdayOfWeek = Integer.parseInt(s.substring(i));
742 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
743 throw new Exception();
744 }
745 } catch (final Exception e) {
746 throw new ParseException(
747 "A numeric value between 1 and 5 must follow the '#' option",
748 i);
749 }
750
751 final TreeSet<Integer> set = getSet(type);
752 set.add(val);
753 i++;
754 return i;
755 }
756
757 if (c == '-') {
758 i++;
759 c = s.charAt(i);
760 final int v = Integer.parseInt(String.valueOf(c));
761 end = v;
762 i++;
763 if (i >= s.length()) {
764 addToSet(val, end, 1, type);
765 return i;
766 }
767 c = s.charAt(i);
768 if (c >= '0' && c <= '9') {
769 final ValueSet vs = getValue(v, s, i);
770 end = vs.value;
771 i = vs.pos;
772 }
773 if (i < s.length() && ((c = s.charAt(i)) == '/')) {
774 i++;
775 c = s.charAt(i);
776 final int v2 = Integer.parseInt(String.valueOf(c));
777 i++;
778 if (i >= s.length()) {
779 addToSet(val, end, v2, type);
780 return i;
781 }
782 c = s.charAt(i);
783 if (c >= '0' && c <= '9') {
784 final ValueSet vs = getValue(v2, s, i);
785 final int v3 = vs.value;
786 addToSet(val, end, v3, type);
787 i = vs.pos;
788 return i;
789 } else {
790 addToSet(val, end, v2, type);
791 return i;
792 }
793 } else {
794 addToSet(val, end, 1, type);
795 return i;
796 }
797 }
798
799 if (c == '/') {
800 i++;
801 c = s.charAt(i);
802 final int v2 = Integer.parseInt(String.valueOf(c));
803 i++;
804 if (i >= s.length()) {
805 addToSet(val, end, v2, type);
806 return i;
807 }
808 c = s.charAt(i);
809 if (c >= '0' && c <= '9') {
810 final ValueSet vs = getValue(v2, s, i);
811 final int v3 = vs.value;
812 addToSet(val, end, v3, type);
813 i = vs.pos;
814 return i;
815 } else {
816 throw new ParseException("Unexpected character '" + c + "' after '/'", i);
817 }
818 }
819
820 addToSet(val, end, 0, type);
821 i++;
822 return i;
823 }
824
825 public String getCronExpression() {
826 return cronExpression;
827 }
828
829 public String getExpressionSummary() {
830 final StringBuilder buf = new StringBuilder();
831
832 buf.append("seconds: ");
833 buf.append(getExpressionSetSummary(seconds));
834 buf.append("\n");
835 buf.append("minutes: ");
836 buf.append(getExpressionSetSummary(minutes));
837 buf.append("\n");
838 buf.append("hours: ");
839 buf.append(getExpressionSetSummary(hours));
840 buf.append("\n");
841 buf.append("daysOfMonth: ");
842 buf.append(getExpressionSetSummary(daysOfMonth));
843 buf.append("\n");
844 buf.append("months: ");
845 buf.append(getExpressionSetSummary(months));
846 buf.append("\n");
847 buf.append("daysOfWeek: ");
848 buf.append(getExpressionSetSummary(daysOfWeek));
849 buf.append("\n");
850 buf.append("lastdayOfWeek: ");
851 buf.append(lastdayOfWeek);
852 buf.append("\n");
853 buf.append("nearestWeekday: ");
854 buf.append(nearestWeekday);
855 buf.append("\n");
856 buf.append("NthDayOfWeek: ");
857 buf.append(nthdayOfWeek);
858 buf.append("\n");
859 buf.append("lastdayOfMonth: ");
860 buf.append(lastdayOfMonth);
861 buf.append("\n");
862 buf.append("years: ");
863 buf.append(getExpressionSetSummary(years));
864 buf.append("\n");
865
866 return buf.toString();
867 }
868
869 protected String getExpressionSetSummary(final java.util.Set<Integer> set) {
870
871 if (set.contains(NO_SPEC)) {
872 return "?";
873 }
874 if (set.contains(ALL_SPEC)) {
875 return "*";
876 }
877
878 final StringBuilder buf = new StringBuilder();
879
880 final Iterator<Integer> itr = set.iterator();
881 boolean first = true;
882 while (itr.hasNext()) {
883 final Integer iVal = itr.next();
884 final String val = iVal.toString();
885 if (!first) {
886 buf.append(",");
887 }
888 buf.append(val);
889 first = false;
890 }
891
892 return buf.toString();
893 }
894
895 protected String getExpressionSetSummary(final java.util.ArrayList<Integer> list) {
896
897 if (list.contains(NO_SPEC)) {
898 return "?";
899 }
900 if (list.contains(ALL_SPEC)) {
901 return "*";
902 }
903
904 final StringBuilder buf = new StringBuilder();
905
906 final Iterator<Integer> itr = list.iterator();
907 boolean first = true;
908 while (itr.hasNext()) {
909 final Integer iVal = itr.next();
910 final String val = iVal.toString();
911 if (!first) {
912 buf.append(",");
913 }
914 buf.append(val);
915 first = false;
916 }
917
918 return buf.toString();
919 }
920
921 protected int skipWhiteSpace(int i, final String s) {
922 for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
923
924 }
925
926 return i;
927 }
928
929 protected int findNextWhiteSpace(int i, final String s) {
930 for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
931
932 }
933
934 return i;
935 }
936
937 protected void addToSet(final int val, final int end, int incr, final int type)
938 throws ParseException {
939
940 final TreeSet<Integer> set = getSet(type);
941
942 if (type == SECOND || type == MINUTE) {
943 if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
944 throw new ParseException(
945 "Minute and Second values must be between 0 and 59",
946 -1);
947 }
948 } else if (type == HOUR) {
949 if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
950 throw new ParseException(
951 "Hour values must be between 0 and 23", -1);
952 }
953 } else if (type == DAY_OF_MONTH) {
954 if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
955 && (val != NO_SPEC_INT)) {
956 throw new ParseException(
957 "Day of month values must be between 1 and 31", -1);
958 }
959 } else if (type == MONTH) {
960 if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
961 throw new ParseException(
962 "Month values must be between 1 and 12", -1);
963 }
964 } else if (type == DAY_OF_WEEK) {
965 if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
966 && (val != NO_SPEC_INT)) {
967 throw new ParseException(
968 "Day-of-Week values must be between 1 and 7", -1);
969 }
970 }
971
972 if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
973 if (val != -1) {
974 set.add(val);
975 } else {
976 set.add(NO_SPEC);
977 }
978
979 return;
980 }
981
982 int startAt = val;
983 int stopAt = end;
984
985 if (val == ALL_SPEC_INT && incr <= 0) {
986 incr = 1;
987 set.add(ALL_SPEC);
988 }
989
990 if (type == SECOND || type == MINUTE) {
991 if (stopAt == -1) {
992 stopAt = 59;
993 }
994 if (startAt == -1 || startAt == ALL_SPEC_INT) {
995 startAt = 0;
996 }
997 } else if (type == HOUR) {
998 if (stopAt == -1) {
999 stopAt = 23;
1000 }
1001 if (startAt == -1 || startAt == ALL_SPEC_INT) {
1002 startAt = 0;
1003 }
1004 } else if (type == DAY_OF_MONTH) {
1005 if (stopAt == -1) {
1006 stopAt = 31;
1007 }
1008 if (startAt == -1 || startAt == ALL_SPEC_INT) {
1009 startAt = 1;
1010 }
1011 } else if (type == MONTH) {
1012 if (stopAt == -1) {
1013 stopAt = 12;
1014 }
1015 if (startAt == -1 || startAt == ALL_SPEC_INT) {
1016 startAt = 1;
1017 }
1018 } else if (type == DAY_OF_WEEK) {
1019 if (stopAt == -1) {
1020 stopAt = 7;
1021 }
1022 if (startAt == -1 || startAt == ALL_SPEC_INT) {
1023 startAt = 1;
1024 }
1025 } else if (type == YEAR) {
1026 if (stopAt == -1) {
1027 stopAt = MAX_YEAR;
1028 }
1029 if (startAt == -1 || startAt == ALL_SPEC_INT) {
1030 startAt = 1970;
1031 }
1032 }
1033
1034
1035
1036
1037 int max = -1;
1038 if (stopAt < startAt) {
1039 switch (type) {
1040 case SECOND:
1041 max = 60;
1042 break;
1043 case MINUTE:
1044 max = 60;
1045 break;
1046 case HOUR:
1047 max = 24;
1048 break;
1049 case MONTH:
1050 max = 12;
1051 break;
1052 case DAY_OF_WEEK:
1053 max = 7;
1054 break;
1055 case DAY_OF_MONTH:
1056 max = 31;
1057 break;
1058 case YEAR:
1059 throw new IllegalArgumentException("Start year must be less than stop year");
1060 default:
1061 throw new IllegalArgumentException("Unexpected type encountered");
1062 }
1063 stopAt += max;
1064 }
1065
1066 for (int i = startAt; i <= stopAt; i += incr) {
1067 if (max == -1) {
1068
1069 set.add(i);
1070 } else {
1071
1072 int i2 = i % max;
1073
1074
1075 if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) {
1076 i2 = max;
1077 }
1078
1079 set.add(i2);
1080 }
1081 }
1082 }
1083
1084 TreeSet<Integer> getSet(final int type) {
1085 switch (type) {
1086 case SECOND:
1087 return seconds;
1088 case MINUTE:
1089 return minutes;
1090 case HOUR:
1091 return hours;
1092 case DAY_OF_MONTH:
1093 return daysOfMonth;
1094 case MONTH:
1095 return months;
1096 case DAY_OF_WEEK:
1097 return daysOfWeek;
1098 case YEAR:
1099 return years;
1100 default:
1101 return null;
1102 }
1103 }
1104
1105 protected ValueSet getValue(final int v, final String s, int i) {
1106 char c = s.charAt(i);
1107 final StringBuilder s1 = new StringBuilder(String.valueOf(v));
1108 while (c >= '0' && c <= '9') {
1109 s1.append(c);
1110 i++;
1111 if (i >= s.length()) {
1112 break;
1113 }
1114 c = s.charAt(i);
1115 }
1116 final ValueSet val = new ValueSet();
1117
1118 val.pos = (i < s.length()) ? i : i + 1;
1119 val.value = Integer.parseInt(s1.toString());
1120 return val;
1121 }
1122
1123 protected int getNumericValue(final String s, final int i) {
1124 final int endOfVal = findNextWhiteSpace(i, s);
1125 final String val = s.substring(i, endOfVal);
1126 return Integer.parseInt(val);
1127 }
1128
1129 protected int getMonthNumber(final String s) {
1130 final Integer integer = monthMap.get(s);
1131
1132 if (integer == null) {
1133 return -1;
1134 }
1135
1136 return integer;
1137 }
1138
1139 protected int getDayOfWeekNumber(final String s) {
1140 final Integer integer = dayMap.get(s);
1141
1142 if (integer == null) {
1143 return -1;
1144 }
1145
1146 return integer;
1147 }
1148
1149
1150
1151
1152
1153
1154
1155 public Date getTimeAfter(Date afterTime) {
1156
1157
1158 final Calendar cl = new java.util.GregorianCalendar(getTimeZone());
1159
1160
1161
1162 afterTime = new Date(afterTime.getTime() + 1000);
1163
1164 cl.setTime(afterTime);
1165 cl.set(Calendar.MILLISECOND, 0);
1166
1167 boolean gotOne = false;
1168
1169 while (!gotOne) {
1170
1171 if (cl.get(Calendar.YEAR) > 2999) {
1172 return null;
1173 }
1174
1175 SortedSet<Integer> st = null;
1176 int t = 0;
1177
1178 int sec = cl.get(Calendar.SECOND);
1179 int min = cl.get(Calendar.MINUTE);
1180
1181
1182 st = seconds.tailSet(sec);
1183 if (st != null && st.size() != 0) {
1184 sec = st.first();
1185 } else {
1186 sec = seconds.first();
1187 min++;
1188 cl.set(Calendar.MINUTE, min);
1189 }
1190 cl.set(Calendar.SECOND, sec);
1191
1192 min = cl.get(Calendar.MINUTE);
1193 int hr = cl.get(Calendar.HOUR_OF_DAY);
1194 t = -1;
1195
1196
1197 st = minutes.tailSet(min);
1198 if (st != null && st.size() != 0) {
1199 t = min;
1200 min = st.first();
1201 } else {
1202 min = minutes.first();
1203 hr++;
1204 }
1205 if (min != t) {
1206 cl.set(Calendar.SECOND, 0);
1207 cl.set(Calendar.MINUTE, min);
1208 setCalendarHour(cl, hr);
1209 continue;
1210 }
1211 cl.set(Calendar.MINUTE, min);
1212
1213 hr = cl.get(Calendar.HOUR_OF_DAY);
1214 int day = cl.get(Calendar.DAY_OF_MONTH);
1215 t = -1;
1216
1217
1218 st = hours.tailSet(hr);
1219 if (st != null && st.size() != 0) {
1220 t = hr;
1221 hr = st.first();
1222 } else {
1223 hr = hours.first();
1224 day++;
1225 }
1226 if (hr != t) {
1227 cl.set(Calendar.SECOND, 0);
1228 cl.set(Calendar.MINUTE, 0);
1229 cl.set(Calendar.DAY_OF_MONTH, day);
1230 setCalendarHour(cl, hr);
1231 continue;
1232 }
1233 cl.set(Calendar.HOUR_OF_DAY, hr);
1234
1235 day = cl.get(Calendar.DAY_OF_MONTH);
1236 int mon = cl.get(Calendar.MONTH) + 1;
1237
1238
1239 t = -1;
1240 int tmon = mon;
1241
1242
1243 final boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
1244 final boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
1245 if (dayOfMSpec && !dayOfWSpec) {
1246 st = daysOfMonth.tailSet(day);
1247 if (lastdayOfMonth) {
1248 if (!nearestWeekday) {
1249 t = day;
1250 day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1251 day -= lastdayOffset;
1252 if (t > day) {
1253 mon++;
1254 if (mon > 12) {
1255 mon = 1;
1256 tmon = 3333;
1257 cl.add(Calendar.YEAR, 1);
1258 }
1259 day = 1;
1260 }
1261 } else {
1262 t = day;
1263 day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1264 day -= lastdayOffset;
1265
1266 final java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
1267 tcal.set(Calendar.SECOND, 0);
1268 tcal.set(Calendar.MINUTE, 0);
1269 tcal.set(Calendar.HOUR_OF_DAY, 0);
1270 tcal.set(Calendar.DAY_OF_MONTH, day);
1271 tcal.set(Calendar.MONTH, mon - 1);
1272 tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1273
1274 final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1275 final int dow = tcal.get(Calendar.DAY_OF_WEEK);
1276
1277 if (dow == Calendar.SATURDAY && day == 1) {
1278 day += 2;
1279 } else if (dow == Calendar.SATURDAY) {
1280 day -= 1;
1281 } else if (dow == Calendar.SUNDAY && day == ldom) {
1282 day -= 2;
1283 } else if (dow == Calendar.SUNDAY) {
1284 day += 1;
1285 }
1286
1287 tcal.set(Calendar.SECOND, sec);
1288 tcal.set(Calendar.MINUTE, min);
1289 tcal.set(Calendar.HOUR_OF_DAY, hr);
1290 tcal.set(Calendar.DAY_OF_MONTH, day);
1291 tcal.set(Calendar.MONTH, mon - 1);
1292 final Date nTime = tcal.getTime();
1293 if (nTime.before(afterTime)) {
1294 day = 1;
1295 mon++;
1296 }
1297 }
1298 } else if (nearestWeekday) {
1299 t = day;
1300 day = daysOfMonth.first();
1301
1302 final java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
1303 tcal.set(Calendar.SECOND, 0);
1304 tcal.set(Calendar.MINUTE, 0);
1305 tcal.set(Calendar.HOUR_OF_DAY, 0);
1306 tcal.set(Calendar.DAY_OF_MONTH, day);
1307 tcal.set(Calendar.MONTH, mon - 1);
1308 tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1309
1310 final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1311 final int dow = tcal.get(Calendar.DAY_OF_WEEK);
1312
1313 if (dow == Calendar.SATURDAY && day == 1) {
1314 day += 2;
1315 } else if (dow == Calendar.SATURDAY) {
1316 day -= 1;
1317 } else if (dow == Calendar.SUNDAY && day == ldom) {
1318 day -= 2;
1319 } else if (dow == Calendar.SUNDAY) {
1320 day += 1;
1321 }
1322
1323
1324 tcal.set(Calendar.SECOND, sec);
1325 tcal.set(Calendar.MINUTE, min);
1326 tcal.set(Calendar.HOUR_OF_DAY, hr);
1327 tcal.set(Calendar.DAY_OF_MONTH, day);
1328 tcal.set(Calendar.MONTH, mon - 1);
1329 final Date nTime = tcal.getTime();
1330 if (nTime.before(afterTime)) {
1331 day = daysOfMonth.first();
1332 mon++;
1333 }
1334 } else if (st != null && st.size() != 0) {
1335 t = day;
1336 day = st.first();
1337
1338 final int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1339 if (day > lastDay) {
1340 day = daysOfMonth.first();
1341 mon++;
1342 }
1343 } else {
1344 day = daysOfMonth.first();
1345 mon++;
1346 }
1347
1348 if (day != t || mon != tmon) {
1349 cl.set(Calendar.SECOND, 0);
1350 cl.set(Calendar.MINUTE, 0);
1351 cl.set(Calendar.HOUR_OF_DAY, 0);
1352 cl.set(Calendar.DAY_OF_MONTH, day);
1353 cl.set(Calendar.MONTH, mon - 1);
1354
1355
1356 continue;
1357 }
1358 } else if (dayOfWSpec && !dayOfMSpec) {
1359 if (lastdayOfWeek) {
1360
1361 final int dow = daysOfWeek.first();
1362
1363 final int cDow = cl.get(Calendar.DAY_OF_WEEK);
1364 int daysToAdd = 0;
1365 if (cDow < dow) {
1366 daysToAdd = dow - cDow;
1367 }
1368 if (cDow > dow) {
1369 daysToAdd = dow + (7 - cDow);
1370 }
1371
1372 final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1373
1374 if (day + daysToAdd > lDay) {
1375
1376 cl.set(Calendar.SECOND, 0);
1377 cl.set(Calendar.MINUTE, 0);
1378 cl.set(Calendar.HOUR_OF_DAY, 0);
1379 cl.set(Calendar.DAY_OF_MONTH, 1);
1380 cl.set(Calendar.MONTH, mon);
1381
1382 continue;
1383 }
1384
1385
1386 while ((day + daysToAdd + 7) <= lDay) {
1387 daysToAdd += 7;
1388 }
1389
1390 day += daysToAdd;
1391
1392 if (daysToAdd > 0) {
1393 cl.set(Calendar.SECOND, 0);
1394 cl.set(Calendar.MINUTE, 0);
1395 cl.set(Calendar.HOUR_OF_DAY, 0);
1396 cl.set(Calendar.DAY_OF_MONTH, day);
1397 cl.set(Calendar.MONTH, mon - 1);
1398
1399 continue;
1400 }
1401
1402 } else if (nthdayOfWeek != 0) {
1403
1404 final int dow = daysOfWeek.first();
1405
1406 final int cDow = cl.get(Calendar.DAY_OF_WEEK);
1407 int daysToAdd = 0;
1408 if (cDow < dow) {
1409 daysToAdd = dow - cDow;
1410 } else if (cDow > dow) {
1411 daysToAdd = dow + (7 - cDow);
1412 }
1413
1414 boolean dayShifted = false;
1415 if (daysToAdd > 0) {
1416 dayShifted = true;
1417 }
1418
1419 day += daysToAdd;
1420 int weekOfMonth = day / 7;
1421 if (day % 7 > 0) {
1422 weekOfMonth++;
1423 }
1424
1425 daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
1426 day += daysToAdd;
1427 if (daysToAdd < 0
1428 || day > getLastDayOfMonth(mon, cl
1429 .get(Calendar.YEAR))) {
1430 cl.set(Calendar.SECOND, 0);
1431 cl.set(Calendar.MINUTE, 0);
1432 cl.set(Calendar.HOUR_OF_DAY, 0);
1433 cl.set(Calendar.DAY_OF_MONTH, 1);
1434 cl.set(Calendar.MONTH, mon);
1435
1436 continue;
1437 } else if (daysToAdd > 0 || dayShifted) {
1438 cl.set(Calendar.SECOND, 0);
1439 cl.set(Calendar.MINUTE, 0);
1440 cl.set(Calendar.HOUR_OF_DAY, 0);
1441 cl.set(Calendar.DAY_OF_MONTH, day);
1442 cl.set(Calendar.MONTH, mon - 1);
1443
1444 continue;
1445 }
1446 } else {
1447 final int cDow = cl.get(Calendar.DAY_OF_WEEK);
1448 int dow = daysOfWeek.first();
1449
1450 st = daysOfWeek.tailSet(cDow);
1451 if (st != null && st.size() > 0) {
1452 dow = st.first();
1453 }
1454
1455 int daysToAdd = 0;
1456 if (cDow < dow) {
1457 daysToAdd = dow - cDow;
1458 }
1459 if (cDow > dow) {
1460 daysToAdd = dow + (7 - cDow);
1461 }
1462
1463 final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1464
1465 if (day + daysToAdd > lDay) {
1466
1467 cl.set(Calendar.SECOND, 0);
1468 cl.set(Calendar.MINUTE, 0);
1469 cl.set(Calendar.HOUR_OF_DAY, 0);
1470 cl.set(Calendar.DAY_OF_MONTH, 1);
1471 cl.set(Calendar.MONTH, mon);
1472
1473 continue;
1474 } else if (daysToAdd > 0) {
1475 cl.set(Calendar.SECOND, 0);
1476 cl.set(Calendar.MINUTE, 0);
1477 cl.set(Calendar.HOUR_OF_DAY, 0);
1478 cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
1479 cl.set(Calendar.MONTH, mon - 1);
1480
1481
1482 continue;
1483 }
1484 }
1485 } else {
1486 throw new UnsupportedOperationException(
1487 "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
1488 }
1489 cl.set(Calendar.DAY_OF_MONTH, day);
1490
1491 mon = cl.get(Calendar.MONTH) + 1;
1492
1493
1494 int year = cl.get(Calendar.YEAR);
1495 t = -1;
1496
1497
1498
1499 if (year > MAX_YEAR) {
1500 return null;
1501 }
1502
1503
1504 st = months.tailSet(mon);
1505 if (st != null && st.size() != 0) {
1506 t = mon;
1507 mon = st.first();
1508 } else {
1509 mon = months.first();
1510 year++;
1511 }
1512 if (mon != t) {
1513 cl.set(Calendar.SECOND, 0);
1514 cl.set(Calendar.MINUTE, 0);
1515 cl.set(Calendar.HOUR_OF_DAY, 0);
1516 cl.set(Calendar.DAY_OF_MONTH, 1);
1517 cl.set(Calendar.MONTH, mon - 1);
1518
1519
1520 cl.set(Calendar.YEAR, year);
1521 continue;
1522 }
1523 cl.set(Calendar.MONTH, mon - 1);
1524
1525
1526
1527 year = cl.get(Calendar.YEAR);
1528 t = -1;
1529
1530
1531 st = years.tailSet(year);
1532 if (st != null && st.size() != 0) {
1533 t = year;
1534 year = st.first();
1535 } else {
1536 return null;
1537 }
1538
1539 if (year != t) {
1540 cl.set(Calendar.SECOND, 0);
1541 cl.set(Calendar.MINUTE, 0);
1542 cl.set(Calendar.HOUR_OF_DAY, 0);
1543 cl.set(Calendar.DAY_OF_MONTH, 1);
1544 cl.set(Calendar.MONTH, 0);
1545
1546
1547 cl.set(Calendar.YEAR, year);
1548 continue;
1549 }
1550 cl.set(Calendar.YEAR, year);
1551
1552 gotOne = true;
1553 }
1554
1555 return cl.getTime();
1556 }
1557
1558
1559
1560
1561
1562
1563
1564
1565 protected void setCalendarHour(final Calendar cal, final int hour) {
1566 cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
1567 if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
1568 cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
1569 }
1570 }
1571
1572 protected Date getTimeBefore(final Date targetDate) {
1573 final Calendar cl = Calendar.getInstance(getTimeZone());
1574
1575
1576 Date start = targetDate;
1577 final long minIncrement = findMinIncrement();
1578 Date prevFireTime;
1579 do {
1580 final Date prevCheckDate = new Date(start.getTime() - minIncrement);
1581 prevFireTime = getTimeAfter(prevCheckDate);
1582 if (prevFireTime == null || prevFireTime.before(MIN_DATE)) {
1583 return null;
1584 }
1585 start = prevCheckDate;
1586 } while (prevFireTime.compareTo(targetDate) >= 0);
1587 return prevFireTime;
1588 }
1589
1590 public Date getPrevFireTime(final Date targetDate) {
1591 return getTimeBefore(targetDate);
1592 }
1593
1594 private long findMinIncrement() {
1595 if (seconds.size() != 1) {
1596 return minInSet(seconds) * 1000;
1597 } else if (seconds.first() == ALL_SPEC_INT) {
1598 return 1000;
1599 }
1600 if (minutes.size() != 1) {
1601 return minInSet(minutes) * 60000;
1602 } else if (minutes.first() == ALL_SPEC_INT) {
1603 return 60000;
1604 }
1605 if (hours.size() != 1) {
1606 return minInSet(hours) * 3600000;
1607 } else if (hours.first() == ALL_SPEC_INT) {
1608 return 3600000;
1609 }
1610 return 86400000;
1611 }
1612
1613 private int minInSet(final TreeSet<Integer> set) {
1614 int previous = 0;
1615 int min = Integer.MAX_VALUE;
1616 boolean first = true;
1617 for (final int value : set) {
1618 if (first) {
1619 previous = value;
1620 first = false;
1621 continue;
1622 } else {
1623 final int diff = value - previous;
1624 if (diff < min) {
1625 min = diff;
1626 }
1627 }
1628 }
1629 return min;
1630 }
1631
1632
1633
1634
1635
1636 public Date getFinalFireTime() {
1637
1638 return null;
1639 }
1640
1641 protected boolean isLeapYear(final int year) {
1642 return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
1643 }
1644
1645 protected int getLastDayOfMonth(final int monthNum, final int year) {
1646
1647 switch (monthNum) {
1648 case 1:
1649 return 31;
1650 case 2:
1651 return (isLeapYear(year)) ? 29 : 28;
1652 case 3:
1653 return 31;
1654 case 4:
1655 return 30;
1656 case 5:
1657 return 31;
1658 case 6:
1659 return 30;
1660 case 7:
1661 return 31;
1662 case 8:
1663 return 31;
1664 case 9:
1665 return 30;
1666 case 10:
1667 return 31;
1668 case 11:
1669 return 30;
1670 case 12:
1671 return 31;
1672 default:
1673 throw new IllegalArgumentException("Illegal month number: "
1674 + monthNum);
1675 }
1676 }
1677
1678
1679 private class ValueSet {
1680 public int value;
1681
1682 public int pos;
1683 }
1684
1685
1686 }