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