001/* 002 * Copyright (C) 2014 Jörg Prante 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.xbib.elasticsearch.plugin.jdbc.cron; 017 018import java.text.ParseException; 019import java.util.Calendar; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027import java.util.SortedSet; 028import java.util.StringTokenizer; 029import java.util.TimeZone; 030import java.util.TreeSet; 031 032public final class CronExpression implements Cloneable { 033 034 protected static final int SECOND = 0; 035 protected static final int MINUTE = 1; 036 protected static final int HOUR = 2; 037 protected static final int DAY_OF_MONTH = 3; 038 protected static final int MONTH = 4; 039 protected static final int DAY_OF_WEEK = 5; 040 protected static final int YEAR = 6; 041 protected static final int ALL_SPEC_INT = 99; // '*' 042 protected static final int NO_SPEC_INT = 98; // '?' 043 protected static final Integer ALL_SPEC = ALL_SPEC_INT; 044 protected static final Integer NO_SPEC = NO_SPEC_INT; 045 046 protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20); 047 protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60); 048 049 static { 050 monthMap.put("JAN", 0); 051 monthMap.put("FEB", 1); 052 monthMap.put("MAR", 2); 053 monthMap.put("APR", 3); 054 monthMap.put("MAY", 4); 055 monthMap.put("JUN", 5); 056 monthMap.put("JUL", 6); 057 monthMap.put("AUG", 7); 058 monthMap.put("SEP", 8); 059 monthMap.put("OCT", 9); 060 monthMap.put("NOV", 10); 061 monthMap.put("DEC", 11); 062 063 dayMap.put("SUN", 1); 064 dayMap.put("MON", 2); 065 dayMap.put("TUE", 3); 066 dayMap.put("WED", 4); 067 dayMap.put("THU", 5); 068 dayMap.put("FRI", 6); 069 dayMap.put("SAT", 7); 070 } 071 072 private final String cronExpression; 073 074 private TimeZone timeZone = null; 075 076 protected transient TreeSet<Integer> seconds; 077 078 protected transient TreeSet<Integer> minutes; 079 080 protected transient TreeSet<Integer> hours; 081 082 protected transient TreeSet<Integer> daysOfMonth; 083 084 protected transient TreeSet<Integer> months; 085 086 protected transient TreeSet<Integer> daysOfWeek; 087 088 protected transient TreeSet<Integer> years; 089 090 protected transient boolean lastdayOfWeek = false; 091 092 protected transient int nthdayOfWeek = 0; 093 094 protected transient boolean lastdayOfMonth = false; 095 096 protected transient boolean nearestWeekday = false; 097 098 protected transient int lastdayOffset = 0; 099 100 protected transient boolean expressionParsed = false; 101 102 public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; 103 104 /** 105 * Constructs a new <code>CronExpression</code> based on the specified 106 * parameter. 107 * 108 * @param cronExpression String representation of the cron expression the 109 * new object should represent 110 */ 111 public CronExpression(String cronExpression) { 112 if (cronExpression == null) { 113 throw new IllegalArgumentException("cron expression cannot be null"); 114 } 115 this.cronExpression = cronExpression.toUpperCase(Locale.US); 116 buildExpression(this.cronExpression); 117 } 118 119 /** 120 * Constructs a new {@code CronExpression} as a copy of an existing 121 * instance. 122 * 123 * @param expression The existing cron expression to be copied 124 */ 125 public CronExpression(CronExpression expression) { 126 if (expression == null) { 127 throw new IllegalArgumentException("cron expression cannot be null"); 128 } 129 this.cronExpression = expression.getCronExpression(); 130 buildExpression(cronExpression); 131 if (expression.getTimeZone() != null) { 132 setTimeZone((TimeZone) expression.getTimeZone().clone()); 133 } 134 } 135 136 /** 137 * Indicates whether the given date satisfies the cron expression. Note that 138 * milliseconds are ignored, so two Dates falling on different milliseconds 139 * of the same second will always have the same result here. 140 * 141 * @param date the date to evaluate 142 * @return a boolean indicating whether the given date satisfies the cron 143 * expression 144 */ 145 public boolean isSatisfiedBy(Date date) { 146 Calendar testDateCal = Calendar.getInstance(getTimeZone()); 147 testDateCal.setTime(date); 148 testDateCal.set(Calendar.MILLISECOND, 0); 149 Date originalDate = testDateCal.getTime(); 150 testDateCal.add(Calendar.SECOND, -1); 151 Date timeAfter = getTimeAfter(testDateCal.getTime()); 152 return ((timeAfter != null) && (timeAfter.equals(originalDate))); 153 } 154 155 /** 156 * Returns the next date/time <I>after</I> the given date/time which 157 * satisfies the cron expression. 158 * 159 * @param date the date/time at which to begin the search for the next valid 160 * date/time 161 * @return the next valid date/time 162 */ 163 public Date getNextValidTimeAfter(Date date) { 164 return getTimeAfter(date); 165 } 166 167 /** 168 * Returns the next date/time <I>after</I> the given date/time which does 169 * <I>not</I> satisfy the expression 170 * 171 * @param date the date/time at which to begin the search for the next 172 * invalid date/time 173 * @return the next valid date/time 174 */ 175 public Date getNextInvalidTimeAfter(Date date) { 176 long difference = 1000; 177 Calendar adjustCal = Calendar.getInstance(getTimeZone()); 178 adjustCal.setTime(date); 179 adjustCal.set(Calendar.MILLISECOND, 0); 180 Date lastDate = adjustCal.getTime(); 181 Date newDate; 182 while (difference == 1000) { 183 newDate = getTimeAfter(lastDate); 184 if (newDate == null) { 185 break; 186 } 187 difference = newDate.getTime() - lastDate.getTime(); 188 if (difference == 1000) { 189 lastDate = newDate; 190 } 191 } 192 return new Date(lastDate.getTime() + 1000); 193 } 194 195 /** 196 * Returns the time zone for which this <code>CronExpression</code> 197 * will be resolved. 198 * 199 * @return the time zone 200 */ 201 public TimeZone getTimeZone() { 202 if (timeZone == null) { 203 timeZone = TimeZone.getDefault(); 204 } 205 return timeZone; 206 } 207 208 /** 209 * Sets the time zone for which this <code>CronExpression</code> 210 * will be resolved. 211 * 212 * @param timeZone the time zone 213 */ 214 public void setTimeZone(TimeZone timeZone) { 215 this.timeZone = timeZone; 216 } 217 218 /** 219 * Returns the string representation of the <code>CronExpression</code> 220 * 221 * @return a string representation of the <code>CronExpression</code> 222 */ 223 @Override 224 public String toString() { 225 return cronExpression; 226 } 227 228 /** 229 * Indicates whether the specified cron expression can be parsed into a 230 * valid cron expression 231 * 232 * @param cronExpression the expression to evaluate 233 * @return a boolean indicating whether the given expression is a valid cron 234 * expression 235 */ 236 public static boolean isValidExpression(String cronExpression) { 237 new CronExpression(cronExpression); 238 return true; 239 } 240 241 protected void buildExpression(String expression) { 242 expressionParsed = true; 243 try { 244 if (seconds == null) { 245 seconds = new TreeSet<Integer>(); 246 } 247 if (minutes == null) { 248 minutes = new TreeSet<Integer>(); 249 } 250 if (hours == null) { 251 hours = new TreeSet<Integer>(); 252 } 253 if (daysOfMonth == null) { 254 daysOfMonth = new TreeSet<Integer>(); 255 } 256 if (months == null) { 257 months = new TreeSet<Integer>(); 258 } 259 if (daysOfWeek == null) { 260 daysOfWeek = new TreeSet<Integer>(); 261 } 262 if (years == null) { 263 years = new TreeSet<Integer>(); 264 } 265 int exprOn = SECOND; 266 StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); 267 while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { 268 String expr = exprsTok.nextToken().trim(); 269 if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { 270 throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); 271 } 272 if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { 273 throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); 274 } 275 if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) { 276 throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1); 277 } 278 StringTokenizer vTok = new StringTokenizer(expr, ","); 279 while (vTok.hasMoreTokens()) { 280 String v = vTok.nextToken(); 281 storeExpressionVals(0, v, exprOn); 282 } 283 exprOn++; 284 } 285 if (exprOn <= DAY_OF_WEEK) { 286 throw new ParseException("Unexpected end of expression.", 287 expression.length()); 288 } 289 if (exprOn <= YEAR) { 290 storeExpressionVals(0, "*", YEAR); 291 } 292 TreeSet<Integer> dow = getSet(DAY_OF_WEEK); 293 TreeSet<Integer> dom = getSet(DAY_OF_MONTH); 294 boolean dayOfMSpec = !dom.contains(NO_SPEC); 295 boolean dayOfWSpec = !dow.contains(NO_SPEC); 296 if (!dayOfMSpec || dayOfWSpec) { 297 if (!dayOfWSpec || dayOfMSpec) { 298 throw new ParseException("support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); 299 } 300 } 301 } catch (Exception e) { 302 throw new IllegalArgumentException("invalid cron expression format: " + e.getMessage()); 303 } 304 } 305 306 protected int storeExpressionVals(int pos, String s, int type) 307 throws ParseException { 308 int incr = 0; 309 int i = skipWhiteSpace(pos, s); 310 if (i >= s.length()) { 311 return i; 312 } 313 char c = s.charAt(i); 314 if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) { 315 String sub = s.substring(i, i + 3); 316 int sval = -1; 317 int eval = -1; 318 if (type == MONTH) { 319 sval = getMonthNumber(sub) + 1; 320 if (sval <= 0) { 321 throw new ParseException("invalid Month value: '" + sub + "'", i); 322 } 323 if (s.length() > i + 3) { 324 c = s.charAt(i + 3); 325 if (c == '-') { 326 i += 4; 327 sub = s.substring(i, i + 3); 328 eval = getMonthNumber(sub) + 1; 329 if (eval <= 0) { 330 throw new ParseException("invalid Mmnth value: '" + sub + "'", i); 331 } 332 } 333 } 334 } else if (type == DAY_OF_WEEK) { 335 sval = getDayOfWeekNumber(sub); 336 if (sval < 0) { 337 throw new ParseException("invalid day-of-week value: '" 338 + sub + "'", i); 339 } 340 if (s.length() > i + 3) { 341 c = s.charAt(i + 3); 342 if (c == '-') { 343 i += 4; 344 sub = s.substring(i, i + 3); 345 eval = getDayOfWeekNumber(sub); 346 if (eval < 0) { 347 throw new ParseException("invalid day-of-Week value: '" + sub + "'", i); 348 } 349 } else if (c == '#') { 350 try { 351 i += 4; 352 nthdayOfWeek = Integer.parseInt(s.substring(i)); 353 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { 354 throw new Exception(); 355 } 356 } catch (Exception e) { 357 throw new ParseException("numeric value between 1 and 5 must follow the '#' option", i); 358 } 359 } else if (c == 'L') { 360 lastdayOfWeek = true; 361 i++; 362 } 363 } 364 365 } else { 366 throw new ParseException("illegal characters for this position: '" + sub + "'", i); 367 } 368 if (eval != -1) { 369 incr = 1; 370 } 371 addToSet(sval, eval, incr, type); 372 return (i + 3); 373 } 374 375 if (c == '?') { 376 i++; 377 if ((i + 1) < s.length() 378 && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { 379 throw new ParseException("illegal character after '?': " + s.charAt(i), i); 380 } 381 if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { 382 throw new ParseException("'?' can only be specfied for day-of-month or day-of-week", i); 383 } 384 if (type == DAY_OF_WEEK && !lastdayOfMonth) { 385 int val = daysOfMonth.last(); 386 if (val == NO_SPEC_INT) { 387 throw new ParseException("'?' can only be specfied for day-of-month or day-of-week", i); 388 } 389 } 390 391 addToSet(NO_SPEC_INT, -1, 0, type); 392 return i; 393 } 394 if (c == '*' || c == '/') { 395 if (c == '*' && (i + 1) >= s.length()) { 396 addToSet(ALL_SPEC_INT, -1, incr, type); 397 return i + 1; 398 } else if (c == '/' 399 && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s 400 .charAt(i + 1) == '\t')) { 401 throw new ParseException("'/' must be followed by an integer", i); 402 } else if (c == '*') { 403 i++; 404 } 405 c = s.charAt(i); 406 if (c == '/') { 407 i++; 408 if (i >= s.length()) { 409 throw new ParseException("unexpected end of string", i); 410 } 411 412 incr = getNumericValue(s, i); 413 414 i++; 415 if (incr > 10) { 416 i++; 417 } 418 if (incr > 59 && (type == SECOND || type == MINUTE)) { 419 throw new ParseException("increment > 60 : " + incr, i); 420 } else if (incr > 23 && (type == HOUR)) { 421 throw new ParseException("increment > 24 : " + incr, i); 422 } else if (incr > 31 && (type == DAY_OF_MONTH)) { 423 throw new ParseException("increment > 31 : " + incr, i); 424 } else if (incr > 7 && (type == DAY_OF_WEEK)) { 425 throw new ParseException("increment > 7 : " + incr, i); 426 } else if (incr > 12 && (type == MONTH)) { 427 throw new ParseException("increment > 12 : " + incr, i); 428 } 429 } else { 430 incr = 1; 431 } 432 addToSet(ALL_SPEC_INT, -1, incr, type); 433 return i; 434 } else if (c == 'L') { 435 i++; 436 if (type == DAY_OF_MONTH) { 437 lastdayOfMonth = true; 438 } 439 if (type == DAY_OF_WEEK) { 440 addToSet(7, 7, 0, type); 441 } 442 if (type == DAY_OF_MONTH && s.length() > i) { 443 c = s.charAt(i); 444 if (c == '-') { 445 ValueSet vs = getValue(0, s, i + 1); 446 lastdayOffset = vs.value; 447 if (lastdayOffset > 30) { 448 throw new ParseException("offset from last day must be <= 30", i + 1); 449 } 450 i = vs.pos; 451 } 452 if (s.length() > i) { 453 c = s.charAt(i); 454 if (c == 'W') { 455 nearestWeekday = true; 456 i++; 457 } 458 } 459 } 460 return i; 461 } else if (c >= '0' && c <= '9') { 462 int val = Integer.parseInt(String.valueOf(c)); 463 i++; 464 if (i >= s.length()) { 465 addToSet(val, -1, -1, type); 466 } else { 467 c = s.charAt(i); 468 if (c >= '0' && c <= '9') { 469 ValueSet vs = getValue(val, s, i); 470 val = vs.value; 471 i = vs.pos; 472 } 473 i = checkNext(i, s, val, type); 474 return i; 475 } 476 } else { 477 throw new ParseException("unexpected character: " + c, i); 478 } 479 return i; 480 } 481 482 protected int checkNext(int pos, String s, int val, int type) 483 throws ParseException { 484 int end = -1; 485 int i = pos; 486 if (i >= s.length()) { 487 addToSet(val, end, -1, type); 488 return i; 489 } 490 char c = s.charAt(pos); 491 if (c == 'L') { 492 if (type == DAY_OF_WEEK) { 493 if (val < 1 || val > 7) { 494 throw new ParseException("day-of-week values must be between 1 and 7", -1); 495 } 496 lastdayOfWeek = true; 497 } else { 498 throw new ParseException("'L' option is not valid here (pos=" + i + ")", i); 499 } 500 TreeSet<Integer> set = getSet(type); 501 set.add(val); 502 i++; 503 return i; 504 } 505 506 if (c == 'W') { 507 if (type == DAY_OF_MONTH) { 508 nearestWeekday = true; 509 } else { 510 throw new ParseException("'W' option is not valid here (pos=" + i + ")", i); 511 } 512 if (val > 31) { 513 throw new ParseException("'W' option does not make sense with values larger than 31 (max number of days in a month)", i); 514 } 515 TreeSet<Integer> set = getSet(type); 516 set.add(val); 517 i++; 518 return i; 519 } 520 if (c == '#') { 521 if (type != DAY_OF_WEEK) { 522 throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); 523 } 524 i++; 525 try { 526 nthdayOfWeek = Integer.parseInt(s.substring(i)); 527 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { 528 throw new Exception(); 529 } 530 } catch (Exception e) { 531 throw new ParseException("numeric value between 1 and 5 must follow the '#' option", i); 532 } 533 TreeSet<Integer> set = getSet(type); 534 set.add(val); 535 i++; 536 return i; 537 } 538 539 if (c == '-') { 540 i++; 541 c = s.charAt(i); 542 int v = Integer.parseInt(String.valueOf(c)); 543 end = v; 544 i++; 545 if (i >= s.length()) { 546 addToSet(val, end, 1, type); 547 return i; 548 } 549 c = s.charAt(i); 550 if (c >= '0' && c <= '9') { 551 ValueSet vs = getValue(v, s, i); 552 end = vs.value; 553 i = vs.pos; 554 } 555 if (i < s.length() && ((c = s.charAt(i)) == '/')) { 556 i++; 557 c = s.charAt(i); 558 int v2 = Integer.parseInt(String.valueOf(c)); 559 i++; 560 if (i >= s.length()) { 561 addToSet(val, end, v2, type); 562 return i; 563 } 564 c = s.charAt(i); 565 if (c >= '0' && c <= '9') { 566 ValueSet vs = getValue(v2, s, i); 567 int v3 = vs.value; 568 addToSet(val, end, v3, type); 569 i = vs.pos; 570 return i; 571 } else { 572 addToSet(val, end, v2, type); 573 return i; 574 } 575 } else { 576 addToSet(val, end, 1, type); 577 return i; 578 } 579 } 580 if (c == '/') { 581 i++; 582 c = s.charAt(i); 583 int v2 = Integer.parseInt(String.valueOf(c)); 584 i++; 585 if (i >= s.length()) { 586 addToSet(val, end, v2, type); 587 return i; 588 } 589 c = s.charAt(i); 590 if (c >= '0' && c <= '9') { 591 ValueSet vs = getValue(v2, s, i); 592 int v3 = vs.value; 593 addToSet(val, end, v3, type); 594 i = vs.pos; 595 return i; 596 } else { 597 throw new ParseException("unexpected character '" + c + "' after '/'", i); 598 } 599 } 600 addToSet(val, end, 0, type); 601 i++; 602 return i; 603 } 604 605 public String getCronExpression() { 606 return cronExpression; 607 } 608 609 public String getExpressionSummary() { 610 StringBuilder buf = new StringBuilder(); 611 buf.append("seconds: "); 612 buf.append(getExpressionSetSummary(seconds)); 613 buf.append("\n"); 614 buf.append("minutes: "); 615 buf.append(getExpressionSetSummary(minutes)); 616 buf.append("\n"); 617 buf.append("hours: "); 618 buf.append(getExpressionSetSummary(hours)); 619 buf.append("\n"); 620 buf.append("daysOfMonth: "); 621 buf.append(getExpressionSetSummary(daysOfMonth)); 622 buf.append("\n"); 623 buf.append("months: "); 624 buf.append(getExpressionSetSummary(months)); 625 buf.append("\n"); 626 buf.append("daysOfWeek: "); 627 buf.append(getExpressionSetSummary(daysOfWeek)); 628 buf.append("\n"); 629 buf.append("lastdayOfWeek: "); 630 buf.append(lastdayOfWeek); 631 buf.append("\n"); 632 buf.append("nearestWeekday: "); 633 buf.append(nearestWeekday); 634 buf.append("\n"); 635 buf.append("NthDayOfWeek: "); 636 buf.append(nthdayOfWeek); 637 buf.append("\n"); 638 buf.append("lastdayOfMonth: "); 639 buf.append(lastdayOfMonth); 640 buf.append("\n"); 641 buf.append("years: "); 642 buf.append(getExpressionSetSummary(years)); 643 buf.append("\n"); 644 return buf.toString(); 645 } 646 647 protected String getExpressionSetSummary(Set<Integer> set) { 648 if (set.contains(NO_SPEC)) { 649 return "?"; 650 } 651 if (set.contains(ALL_SPEC)) { 652 return "*"; 653 } 654 StringBuilder buf = new StringBuilder(); 655 Iterator<Integer> itr = set.iterator(); 656 boolean first = true; 657 while (itr.hasNext()) { 658 Integer iVal = itr.next(); 659 String val = iVal.toString(); 660 if (!first) { 661 buf.append(","); 662 } 663 buf.append(val); 664 first = false; 665 } 666 667 return buf.toString(); 668 } 669 670 protected String getExpressionSetSummary(List<Integer> list) { 671 if (list.contains(NO_SPEC)) { 672 return "?"; 673 } 674 if (list.contains(ALL_SPEC)) { 675 return "*"; 676 } 677 StringBuilder buf = new StringBuilder(); 678 Iterator<Integer> itr = list.iterator(); 679 boolean first = true; 680 while (itr.hasNext()) { 681 Integer iVal = itr.next(); 682 String val = iVal.toString(); 683 if (!first) { 684 buf.append(","); 685 } 686 buf.append(val); 687 first = false; 688 } 689 return buf.toString(); 690 } 691 692 protected int skipWhiteSpace(int i, String s) { 693 for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { 694 ; 695 } 696 return i; 697 } 698 699 protected int findNextWhiteSpace(int i, String s) { 700 for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { 701 ; 702 } 703 return i; 704 } 705 706 protected void addToSet(int val, int end, int incr, int type) 707 throws ParseException { 708 TreeSet<Integer> set = getSet(type); 709 if (type == SECOND || type == MINUTE) { 710 if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { 711 throw new ParseException( 712 "Minute and Second values must be between 0 and 59", 713 -1); 714 } 715 } else if (type == HOUR) { 716 if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { 717 throw new ParseException( 718 "Hour values must be between 0 and 23", -1); 719 } 720 } else if (type == DAY_OF_MONTH) { 721 if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) 722 && (val != NO_SPEC_INT)) { 723 throw new ParseException( 724 "Day of month values must be between 1 and 31", -1); 725 } 726 } else if (type == MONTH) { 727 if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { 728 throw new ParseException( 729 "Month values must be between 1 and 12", -1); 730 } 731 } else if (type == DAY_OF_WEEK) { 732 if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) 733 && (val != NO_SPEC_INT)) { 734 throw new ParseException( 735 "Day-of-Week values must be between 1 and 7", -1); 736 } 737 } 738 if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { 739 if (val != -1) { 740 set.add(val); 741 } else { 742 set.add(NO_SPEC); 743 } 744 745 return; 746 } 747 int startAt = val; 748 int stopAt = end; 749 750 if (val == ALL_SPEC_INT && incr <= 0) { 751 incr = 1; 752 set.add(ALL_SPEC); 753 } 754 if (type == SECOND || type == MINUTE) { 755 if (stopAt == -1) { 756 stopAt = 59; 757 } 758 if (startAt == -1 || startAt == ALL_SPEC_INT) { 759 startAt = 0; 760 } 761 } else if (type == HOUR) { 762 if (stopAt == -1) { 763 stopAt = 23; 764 } 765 if (startAt == -1 || startAt == ALL_SPEC_INT) { 766 startAt = 0; 767 } 768 } else if (type == DAY_OF_MONTH) { 769 if (stopAt == -1) { 770 stopAt = 31; 771 } 772 if (startAt == -1 || startAt == ALL_SPEC_INT) { 773 startAt = 1; 774 } 775 } else if (type == MONTH) { 776 if (stopAt == -1) { 777 stopAt = 12; 778 } 779 if (startAt == -1 || startAt == ALL_SPEC_INT) { 780 startAt = 1; 781 } 782 } else if (type == DAY_OF_WEEK) { 783 if (stopAt == -1) { 784 stopAt = 7; 785 } 786 if (startAt == -1 || startAt == ALL_SPEC_INT) { 787 startAt = 1; 788 } 789 } else if (type == YEAR) { 790 if (stopAt == -1) { 791 stopAt = MAX_YEAR; 792 } 793 if (startAt == -1 || startAt == ALL_SPEC_INT) { 794 startAt = 1970; 795 } 796 } 797 int max = -1; 798 if (stopAt < startAt) { 799 switch (type) { 800 case SECOND: 801 max = 60; 802 break; 803 case MINUTE: 804 max = 60; 805 break; 806 case HOUR: 807 max = 24; 808 break; 809 case MONTH: 810 max = 12; 811 break; 812 case DAY_OF_WEEK: 813 max = 7; 814 break; 815 case DAY_OF_MONTH: 816 max = 31; 817 break; 818 case YEAR: 819 throw new IllegalArgumentException("Start year must be less than stop year"); 820 default: 821 throw new IllegalArgumentException("Unexpected type encountered"); 822 } 823 stopAt += max; 824 } 825 826 for (int i = startAt; i <= stopAt; i += incr) { 827 if (max == -1) { 828 set.add(i); 829 } else { 830 int i2 = i % max; 831 if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { 832 i2 = max; 833 } 834 set.add(i2); 835 } 836 } 837 } 838 839 private TreeSet<Integer> getSet(int type) { 840 switch (type) { 841 case SECOND: 842 return seconds; 843 case MINUTE: 844 return minutes; 845 case HOUR: 846 return hours; 847 case DAY_OF_MONTH: 848 return daysOfMonth; 849 case MONTH: 850 return months; 851 case DAY_OF_WEEK: 852 return daysOfWeek; 853 case YEAR: 854 return years; 855 default: 856 return null; 857 } 858 } 859 860 protected ValueSet getValue(int v, String s, int i) { 861 char c = s.charAt(i); 862 StringBuilder s1 = new StringBuilder(String.valueOf(v)); 863 while (c >= '0' && c <= '9') { 864 s1.append(c); 865 i++; 866 if (i >= s.length()) { 867 break; 868 } 869 c = s.charAt(i); 870 } 871 ValueSet val = new ValueSet(); 872 873 val.pos = (i < s.length()) ? i : i + 1; 874 val.value = Integer.parseInt(s1.toString()); 875 return val; 876 } 877 878 protected int getNumericValue(String s, int i) { 879 int endOfVal = findNextWhiteSpace(i, s); 880 String val = s.substring(i, endOfVal); 881 return Integer.parseInt(val); 882 } 883 884 protected int getMonthNumber(String s) { 885 Integer integer = monthMap.get(s); 886 887 if (integer == null) { 888 return -1; 889 } 890 891 return integer; 892 } 893 894 protected int getDayOfWeekNumber(String s) { 895 Integer integer = dayMap.get(s); 896 897 if (integer == null) { 898 return -1; 899 } 900 901 return integer; 902 } 903 904 public Date getTimeAfter(Date afterTime) { 905 Calendar cl = new java.util.GregorianCalendar(getTimeZone()); 906 afterTime = new Date(afterTime.getTime() + 1000); 907 cl.setTime(afterTime); 908 cl.set(Calendar.MILLISECOND, 0); 909 boolean gotOne = false; 910 while (!gotOne) { 911 if (cl.get(Calendar.YEAR) > 2999) { 912 return null; 913 } 914 SortedSet<Integer> st; 915 int t; 916 int sec = cl.get(Calendar.SECOND); 917 int min = cl.get(Calendar.MINUTE); 918 st = seconds.tailSet(sec); 919 if (st.size() != 0) { 920 sec = st.first(); 921 } else { 922 sec = seconds.first(); 923 min++; 924 cl.set(Calendar.MINUTE, min); 925 } 926 cl.set(Calendar.SECOND, sec); 927 min = cl.get(Calendar.MINUTE); 928 int hr = cl.get(Calendar.HOUR_OF_DAY); 929 t = -1; 930 st = minutes.tailSet(min); 931 if (st.size() != 0) { 932 t = min; 933 min = st.first(); 934 } else { 935 min = minutes.first(); 936 hr++; 937 } 938 if (min != t) { 939 cl.set(Calendar.SECOND, 0); 940 cl.set(Calendar.MINUTE, min); 941 setCalendarHour(cl, hr); 942 continue; 943 } 944 cl.set(Calendar.MINUTE, min); 945 hr = cl.get(Calendar.HOUR_OF_DAY); 946 int day = cl.get(Calendar.DAY_OF_MONTH); 947 t = -1; 948 st = hours.tailSet(hr); 949 if (st.size() != 0) { 950 t = hr; 951 hr = st.first(); 952 } else { 953 hr = hours.first(); 954 day++; 955 } 956 if (hr != t) { 957 cl.set(Calendar.SECOND, 0); 958 cl.set(Calendar.MINUTE, 0); 959 cl.set(Calendar.DAY_OF_MONTH, day); 960 setCalendarHour(cl, hr); 961 continue; 962 } 963 cl.set(Calendar.HOUR_OF_DAY, hr); 964 day = cl.get(Calendar.DAY_OF_MONTH); 965 int mon = cl.get(Calendar.MONTH) + 1; 966 t = -1; 967 int tmon = mon; 968 boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); 969 boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); 970 if (dayOfMSpec && !dayOfWSpec) { 971 st = daysOfMonth.tailSet(day); 972 if (lastdayOfMonth) { 973 if (!nearestWeekday) { 974 t = day; 975 day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 976 day -= lastdayOffset; 977 if (t > day) { 978 mon++; 979 if (mon > 12) { 980 mon = 1; 981 tmon = 3333; 982 cl.add(Calendar.YEAR, 1); 983 } 984 day = 1; 985 } 986 } else { 987 t = day; 988 day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 989 day -= lastdayOffset; 990 Calendar tcal = Calendar.getInstance(getTimeZone()); 991 tcal.set(Calendar.SECOND, 0); 992 tcal.set(Calendar.MINUTE, 0); 993 tcal.set(Calendar.HOUR_OF_DAY, 0); 994 tcal.set(Calendar.DAY_OF_MONTH, day); 995 tcal.set(Calendar.MONTH, mon - 1); 996 tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); 997 int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 998 int dow = tcal.get(Calendar.DAY_OF_WEEK); 999 if (dow == Calendar.SATURDAY && day == 1) { 1000 day += 2; 1001 } else if (dow == Calendar.SATURDAY) { 1002 day -= 1; 1003 } else if (dow == Calendar.SUNDAY && day == ldom) { 1004 day -= 2; 1005 } else if (dow == Calendar.SUNDAY) { 1006 day += 1; 1007 } 1008 tcal.set(Calendar.SECOND, sec); 1009 tcal.set(Calendar.MINUTE, min); 1010 tcal.set(Calendar.HOUR_OF_DAY, hr); 1011 tcal.set(Calendar.DAY_OF_MONTH, day); 1012 tcal.set(Calendar.MONTH, mon - 1); 1013 Date nTime = tcal.getTime(); 1014 if (nTime.before(afterTime)) { 1015 day = 1; 1016 mon++; 1017 } 1018 } 1019 } else if (nearestWeekday) { 1020 t = day; 1021 day = daysOfMonth.first(); 1022 Calendar tcal = Calendar.getInstance(getTimeZone()); 1023 tcal.set(Calendar.SECOND, 0); 1024 tcal.set(Calendar.MINUTE, 0); 1025 tcal.set(Calendar.HOUR_OF_DAY, 0); 1026 tcal.set(Calendar.DAY_OF_MONTH, day); 1027 tcal.set(Calendar.MONTH, mon - 1); 1028 tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); 1029 int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 1030 int dow = tcal.get(Calendar.DAY_OF_WEEK); 1031 if (dow == Calendar.SATURDAY && day == 1) { 1032 day += 2; 1033 } else if (dow == Calendar.SATURDAY) { 1034 day -= 1; 1035 } else if (dow == Calendar.SUNDAY && day == ldom) { 1036 day -= 2; 1037 } else if (dow == Calendar.SUNDAY) { 1038 day += 1; 1039 } 1040 tcal.set(Calendar.SECOND, sec); 1041 tcal.set(Calendar.MINUTE, min); 1042 tcal.set(Calendar.HOUR_OF_DAY, hr); 1043 tcal.set(Calendar.DAY_OF_MONTH, day); 1044 tcal.set(Calendar.MONTH, mon - 1); 1045 Date nTime = tcal.getTime(); 1046 if (nTime.before(afterTime)) { 1047 day = daysOfMonth.first(); 1048 mon++; 1049 } 1050 } else if (st.size() != 0) { 1051 t = day; 1052 day = st.first(); 1053 int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 1054 if (day > lastDay) { 1055 day = daysOfMonth.first(); 1056 mon++; 1057 } 1058 } else { 1059 day = daysOfMonth.first(); 1060 mon++; 1061 } 1062 if (day != t || mon != tmon) { 1063 cl.set(Calendar.SECOND, 0); 1064 cl.set(Calendar.MINUTE, 0); 1065 cl.set(Calendar.HOUR_OF_DAY, 0); 1066 cl.set(Calendar.DAY_OF_MONTH, day); 1067 cl.set(Calendar.MONTH, mon - 1); 1068 continue; 1069 } 1070 } else if (dayOfWSpec && !dayOfMSpec) { 1071 if (lastdayOfWeek) { 1072 int dow = daysOfWeek.first(); 1073 int cDow = cl.get(Calendar.DAY_OF_WEEK); 1074 int daysToAdd = 0; 1075 if (cDow < dow) { 1076 daysToAdd = dow - cDow; 1077 } 1078 if (cDow > dow) { 1079 daysToAdd = dow + (7 - cDow); 1080 } 1081 int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 1082 if (day + daysToAdd > lDay) { 1083 cl.set(Calendar.SECOND, 0); 1084 cl.set(Calendar.MINUTE, 0); 1085 cl.set(Calendar.HOUR_OF_DAY, 0); 1086 cl.set(Calendar.DAY_OF_MONTH, 1); 1087 cl.set(Calendar.MONTH, mon); 1088 continue; 1089 } 1090 while ((day + daysToAdd + 7) <= lDay) { 1091 daysToAdd += 7; 1092 } 1093 day += daysToAdd; 1094 if (daysToAdd > 0) { 1095 cl.set(Calendar.SECOND, 0); 1096 cl.set(Calendar.MINUTE, 0); 1097 cl.set(Calendar.HOUR_OF_DAY, 0); 1098 cl.set(Calendar.DAY_OF_MONTH, day); 1099 cl.set(Calendar.MONTH, mon - 1); 1100 continue; 1101 } 1102 } else if (nthdayOfWeek != 0) { 1103 int dow = daysOfWeek.first(); 1104 int cDow = cl.get(Calendar.DAY_OF_WEEK); 1105 int daysToAdd = 0; 1106 if (cDow < dow) { 1107 daysToAdd = dow - cDow; 1108 } else if (cDow > dow) { 1109 daysToAdd = dow + (7 - cDow); 1110 } 1111 boolean dayShifted = false; 1112 if (daysToAdd > 0) { 1113 dayShifted = true; 1114 } 1115 day += daysToAdd; 1116 int weekOfMonth = day / 7; 1117 if (day % 7 > 0) { 1118 weekOfMonth++; 1119 } 1120 daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; 1121 day += daysToAdd; 1122 if (daysToAdd < 0 1123 || day > getLastDayOfMonth(mon, cl 1124 .get(Calendar.YEAR))) { 1125 cl.set(Calendar.SECOND, 0); 1126 cl.set(Calendar.MINUTE, 0); 1127 cl.set(Calendar.HOUR_OF_DAY, 0); 1128 cl.set(Calendar.DAY_OF_MONTH, 1); 1129 cl.set(Calendar.MONTH, mon); 1130 // no '- 1' here because we are promoting the month 1131 continue; 1132 } else if (daysToAdd > 0 || dayShifted) { 1133 cl.set(Calendar.SECOND, 0); 1134 cl.set(Calendar.MINUTE, 0); 1135 cl.set(Calendar.HOUR_OF_DAY, 0); 1136 cl.set(Calendar.DAY_OF_MONTH, day); 1137 cl.set(Calendar.MONTH, mon - 1); 1138 // '- 1' here because we are NOT promoting the month 1139 continue; 1140 } 1141 } else { 1142 int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w 1143 int dow = daysOfWeek.first(); // desired 1144 // d-o-w 1145 st = daysOfWeek.tailSet(cDow); 1146 if (st.size() > 0) { 1147 dow = st.first(); 1148 } 1149 1150 int daysToAdd = 0; 1151 if (cDow < dow) { 1152 daysToAdd = dow - cDow; 1153 } 1154 if (cDow > dow) { 1155 daysToAdd = dow + (7 - cDow); 1156 } 1157 1158 int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); 1159 1160 if (day + daysToAdd > lDay) { // will we pass the end of 1161 // the month? 1162 cl.set(Calendar.SECOND, 0); 1163 cl.set(Calendar.MINUTE, 0); 1164 cl.set(Calendar.HOUR_OF_DAY, 0); 1165 cl.set(Calendar.DAY_OF_MONTH, 1); 1166 cl.set(Calendar.MONTH, mon); 1167 continue; 1168 } else if (daysToAdd > 0) { // are we swithing days? 1169 cl.set(Calendar.SECOND, 0); 1170 cl.set(Calendar.MINUTE, 0); 1171 cl.set(Calendar.HOUR_OF_DAY, 0); 1172 cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); 1173 cl.set(Calendar.MONTH, mon - 1); 1174 continue; 1175 } 1176 } 1177 } else { 1178 throw new UnsupportedOperationException( 1179 "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); 1180 } 1181 cl.set(Calendar.DAY_OF_MONTH, day); 1182 1183 mon = cl.get(Calendar.MONTH) + 1; 1184 int year = cl.get(Calendar.YEAR); 1185 t = -1; 1186 if (year > MAX_YEAR) { 1187 return null; 1188 } 1189 st = months.tailSet(mon); 1190 if (st.size() != 0) { 1191 t = mon; 1192 mon = st.first(); 1193 } else { 1194 mon = months.first(); 1195 year++; 1196 } 1197 if (mon != t) { 1198 cl.set(Calendar.SECOND, 0); 1199 cl.set(Calendar.MINUTE, 0); 1200 cl.set(Calendar.HOUR_OF_DAY, 0); 1201 cl.set(Calendar.DAY_OF_MONTH, 1); 1202 cl.set(Calendar.MONTH, mon - 1); 1203 cl.set(Calendar.YEAR, year); 1204 continue; 1205 } 1206 cl.set(Calendar.MONTH, mon - 1); 1207 year = cl.get(Calendar.YEAR); 1208 st = years.tailSet(year); 1209 if (st.size() != 0) { 1210 t = year; 1211 year = st.first(); 1212 } else { 1213 return null; 1214 } 1215 if (year != t) { 1216 cl.set(Calendar.SECOND, 0); 1217 cl.set(Calendar.MINUTE, 0); 1218 cl.set(Calendar.HOUR_OF_DAY, 0); 1219 cl.set(Calendar.DAY_OF_MONTH, 1); 1220 cl.set(Calendar.MONTH, 0); 1221 cl.set(Calendar.YEAR, year); 1222 continue; 1223 } 1224 cl.set(Calendar.YEAR, year); 1225 gotOne = true; 1226 } 1227 return cl.getTime(); 1228 } 1229 1230 /** 1231 * Advance the calendar to the particular hour paying particular attention 1232 * to daylight saving problems. 1233 * 1234 * @param cal the calendar to operate on 1235 * @param hour the hour to set 1236 */ 1237 protected void setCalendarHour(Calendar cal, int hour) { 1238 cal.set(Calendar.HOUR_OF_DAY, hour); 1239 if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { 1240 cal.set(Calendar.HOUR_OF_DAY, hour + 1); 1241 } 1242 } 1243 1244 protected boolean isLeapYear(int year) { 1245 return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); 1246 } 1247 1248 protected int getLastDayOfMonth(int monthNum, int year) { 1249 1250 switch (monthNum) { 1251 case 1: 1252 return 31; 1253 case 2: 1254 return (isLeapYear(year)) ? 29 : 28; 1255 case 3: 1256 return 31; 1257 case 4: 1258 return 30; 1259 case 5: 1260 return 31; 1261 case 6: 1262 return 30; 1263 case 7: 1264 return 31; 1265 case 8: 1266 return 31; 1267 case 9: 1268 return 30; 1269 case 10: 1270 return 31; 1271 case 11: 1272 return 30; 1273 case 12: 1274 return 31; 1275 default: 1276 throw new IllegalArgumentException("Illegal month number: " 1277 + monthNum); 1278 } 1279 } 1280 1281 private static class ValueSet { 1282 int value; 1283 int pos; 1284 } 1285 1286} 1287