PageRenderTime 662ms CodeModel.GetById 101ms app.highlight 447ms RepoModel.GetById 96ms app.codeStats 1ms

/src/java/grails/plugin/jesque/CronExpression.java

https://github.com/dnoseda/grails-jesque
Java | 1645 lines | 1159 code | 176 blank | 310 comment | 493 complexity | 006c2bfd632e609909707da0fd49c728 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1package grails.plugin.jesque;
   2
   3import org.joda.time.DateTime;
   4import org.joda.time.DateTimeZone;
   5
   6import java.io.Serializable;
   7import java.text.ParseException;
   8import java.util.Calendar;
   9import java.util.Date;
  10import java.util.HashMap;
  11import java.util.Iterator;
  12import java.util.Locale;
  13import java.util.Map;
  14import java.util.SortedSet;
  15import java.util.StringTokenizer;
  16import java.util.TimeZone;
  17import java.util.TreeSet;
  18
  19/**
  20 * Provides a parser and evaluator for unix-like cron expressions. Cron
  21 * expressions provide the ability to specify complex time combinations such as
  22 * "At 8:00am every Monday through Friday" or "At 1:30am every
  23 * last Friday of the month".
  24 * <P>
  25 * Cron expressions are comprised of 6 required fields and one optional field
  26 * separated by white space. The fields respectively are described as follows:
  27 *
  28 * <table cellspacing="8">
  29 * <tr>
  30 * <th align="left">Field Name</th>
  31 * <th align="left">&nbsp;</th>
  32 * <th align="left">Allowed Values</th>
  33 * <th align="left">&nbsp;</th>
  34 * <th align="left">Allowed Special Characters</th>
  35 * </tr>
  36 * <tr>
  37 * <td align="left"><code>Seconds</code></td>
  38 * <td align="left">&nbsp;</th>
  39 * <td align="left"><code>0-59</code></td>
  40 * <td align="left">&nbsp;</th>
  41 * <td align="left"><code>, - * /</code></td>
  42 * </tr>
  43 * <tr>
  44 * <td align="left"><code>Minutes</code></td>
  45 * <td align="left">&nbsp;</th>
  46 * <td align="left"><code>0-59</code></td>
  47 * <td align="left">&nbsp;</th>
  48 * <td align="left"><code>, - * /</code></td>
  49 * </tr>
  50 * <tr>
  51 * <td align="left"><code>Hours</code></td>
  52 * <td align="left">&nbsp;</th>
  53 * <td align="left"><code>0-23</code></td>
  54 * <td align="left">&nbsp;</th>
  55 * <td align="left"><code>, - * /</code></td>
  56 * </tr>
  57 * <tr>
  58 * <td align="left"><code>Day-of-month</code></td>
  59 * <td align="left">&nbsp;</th>
  60 * <td align="left"><code>1-31</code></td>
  61 * <td align="left">&nbsp;</th>
  62 * <td align="left"><code>, - * ? / L W</code></td>
  63 * </tr>
  64 * <tr>
  65 * <td align="left"><code>Month</code></td>
  66 * <td align="left">&nbsp;</th>
  67 * <td align="left"><code>1-12 or JAN-DEC</code></td>
  68 * <td align="left">&nbsp;</th>
  69 * <td align="left"><code>, - * /</code></td>
  70 * </tr>
  71 * <tr>
  72 * <td align="left"><code>Day-of-Week</code></td>
  73 * <td align="left">&nbsp;</th>
  74 * <td align="left"><code>1-7 or SUN-SAT</code></td>
  75 * <td align="left">&nbsp;</th>
  76 * <td align="left"><code>, - * ? / L #</code></td>
  77 * </tr>
  78 * <tr>
  79 * <td align="left"><code>Year (Optional)</code></td>
  80 * <td align="left">&nbsp;</th>
  81 * <td align="left"><code>empty, 1970-2199</code></td>
  82 * <td align="left">&nbsp;</th>
  83 * <td align="left"><code>, - * /</code></td>
  84 * </tr>
  85 * </table>
  86 * <P>
  87 * The '*' character is used to specify all values. For example, &quot;*&quot;
  88 * in the minute field means &quot;every minute&quot;.
  89 * <P>
  90 * The '?' character is allowed for the day-of-month and day-of-week fields. It
  91 * is used to specify 'no specific value'. This is useful when you need to
  92 * specify something in one of the two fields, but not the other.
  93 * <P>
  94 * The '-' character is used to specify ranges For example &quot;10-12&quot; in
  95 * the hour field means &quot;the hours 10, 11 and 12&quot;.
  96 * <P>
  97 * The ',' character is used to specify additional values. For example
  98 * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
  99 * Wednesday, and Friday&quot;.
 100 * <P>
 101 * The '/' character is used to specify increments. For example &quot;0/15&quot;
 102 * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And
 103 * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
 104 * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
 105 * the value to start with. Essentially, for each field in the expression, there
 106 * is a set of numbers that can be turned on or off. For seconds and minutes,
 107 * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
 108 * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
 109 * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
 110 * month field only turns on month &quot;7&quot;, it does NOT mean every 6th
 111 * month, please note that subtlety.
 112 * <P>
 113 * The 'L' character is allowed for the day-of-month and day-of-week fields.
 114 * This character is short-hand for &quot;last&quot;, but it has different
 115 * meaning in each of the two fields. For example, the value &quot;L&quot; in
 116 * the day-of-month field means &quot;the last day of the month&quot; - day 31
 117 * for January, day 28 for February on non-leap years. If used in the
 118 * day-of-week field by itself, it simply means &quot;7&quot; or
 119 * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
 120 * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
 121 * means &quot;the last friday of the month&quot;. You can also specify an offset
 122 * from the last day of the month, such as "L-3" which would mean the third-to-last
 123 * day of the calendar month. <i>When using the 'L' option, it is important not to
 124 * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i>
 125 * <P>
 126 * The 'W' character is allowed for the day-of-month field.  This character
 127 * is used to specify the weekday (Monday-Friday) nearest the given day.  As an
 128 * example, if you were to specify &quot;15W&quot; as the value for the
 129 * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
 130 * the month&quot;. So if the 15th is a Saturday, the trigger will fire on
 131 * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
 132 * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
 133 * However if you specify &quot;1W&quot; as the value for day-of-month, and the
 134 * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
 135 * 'jump' over the boundary of a month's days.  The 'W' character can only be
 136 * specified when the day-of-month is a single day, not a range or list of days.
 137 * <P>
 138 * The 'L' and 'W' characters can also be combined for the day-of-month
 139 * expression to yield 'LW', which translates to &quot;last weekday of the
 140 * month&quot;.
 141 * <P>
 142 * The '#' character is allowed for the day-of-week field. This character is
 143 * used to specify &quot;the nth&quot; XXX day of the month. For example, the
 144 * value of &quot;6#3&quot; in the day-of-week field means the third Friday of
 145 * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month).
 146 * Other examples: &quot;2#1&quot; = the first Monday of the month and
 147 * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
 148 * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
 149 * no firing will occur that month.  If the '#' character is used, there can
 150 * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is
 151 * not valid, since there are two expressions).
 152 * <P>
 153 * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
 154 * This character is short-hand for "calendar". This means values are
 155 * calculated against the associated calendar, if any. If no calendar is
 156 * associated, then it is equivalent to having an all-inclusive calendar. A
 157 * value of "5C" in the day-of-month field means "the first day included by the
 158 * calendar on or after the 5th". A value of "1C" in the day-of-week field
 159 * means "the first day included by the calendar on or after Sunday".-->
 160 * <P>
 161 * The legal characters and the names of months and days of the week are not
 162 * case sensitive.
 163 *
 164 * <p>
 165 * <b>NOTES:</b>
 166 * <ul>
 167 * <li>Support for specifying both a day-of-week and a day-of-month value is
 168 * not complete (you'll need to use the '?' character in one of these fields).
 169 * </li>
 170 * <li>Overflowing ranges is supported - that is, having a larger number on
 171 * the left hand side than the right. You might do 22-2 to catch 10 o'clock
 172 * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
 173 * very important to note that overuse of overflowing ranges creates ranges
 174 * that don't make sense and no effort has been made to determine which
 175 * interpretation CronExpression chooses. An example would be
 176 * "0 0 14-6 ? * FRI-MON". </li>
 177 * </ul>
 178 * </p>
 179 *
 180 *
 181 * @author Sharada Jambula, James House
 182 * @author Contributions from Mads Henderson
 183 * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
 184 */
 185public class CronExpression implements Serializable, Cloneable {
 186
 187    private static final long serialVersionUID = 12423409423L;
 188
 189    protected static final int SECOND = 0;
 190    protected static final int MINUTE = 1;
 191    protected static final int HOUR = 2;
 192    protected static final int DAY_OF_MONTH = 3;
 193    protected static final int MONTH = 4;
 194    protected static final int DAY_OF_WEEK = 5;
 195    protected static final int YEAR = 6;
 196    protected static final int ALL_SPEC_INT = 99; // '*'
 197    protected static final int NO_SPEC_INT = 98; // '?'
 198    protected static final Integer ALL_SPEC = Integer.valueOf(ALL_SPEC_INT);
 199    protected static final Integer NO_SPEC = Integer.valueOf(NO_SPEC_INT);
 200
 201    protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
 202    protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
 203    static {
 204        monthMap.put("JAN", Integer.valueOf(0));
 205        monthMap.put("FEB", Integer.valueOf(1));
 206        monthMap.put("MAR", Integer.valueOf(2));
 207        monthMap.put("APR", Integer.valueOf(3));
 208        monthMap.put("MAY", Integer.valueOf(4));
 209        monthMap.put("JUN", Integer.valueOf(5));
 210        monthMap.put("JUL", Integer.valueOf(6));
 211        monthMap.put("AUG", Integer.valueOf(7));
 212        monthMap.put("SEP", Integer.valueOf(8));
 213        monthMap.put("OCT", Integer.valueOf(9));
 214        monthMap.put("NOV", Integer.valueOf(10));
 215        monthMap.put("DEC", Integer.valueOf(11));
 216
 217        dayMap.put("SUN", Integer.valueOf(1));
 218        dayMap.put("MON", Integer.valueOf(2));
 219        dayMap.put("TUE", Integer.valueOf(3));
 220        dayMap.put("WED", Integer.valueOf(4));
 221        dayMap.put("THU", Integer.valueOf(5));
 222        dayMap.put("FRI", Integer.valueOf(6));
 223        dayMap.put("SAT", Integer.valueOf(7));
 224    }
 225
 226    private String cronExpression = null;
 227    private TimeZone timeZone = null;
 228    protected transient TreeSet<Integer> seconds;
 229    protected transient TreeSet<Integer> minutes;
 230    protected transient TreeSet<Integer> hours;
 231    protected transient TreeSet<Integer> daysOfMonth;
 232    protected transient TreeSet<Integer> months;
 233    protected transient TreeSet<Integer> daysOfWeek;
 234    protected transient TreeSet<Integer> years;
 235
 236    protected transient boolean lastdayOfWeek = false;
 237    protected transient int nthdayOfWeek = 0;
 238    protected transient boolean lastdayOfMonth = false;
 239    protected transient boolean nearestWeekday = false;
 240    protected transient int lastdayOffset = 0;
 241    protected transient boolean expressionParsed = false;
 242
 243    public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
 244
 245    /**
 246     * Constructs a new <CODE>CronExpression</CODE> based on the specified
 247     * parameter.
 248     *
 249     * @param cronExpression String representation of the cron expression the
 250     *                       new object should represent
 251     * @throws java.text.ParseException
 252     *         if the string expression cannot be parsed into a valid
 253     *         <CODE>CronExpression</CODE>
 254     */
 255    public CronExpression(String cronExpression) throws ParseException {
 256        if (cronExpression == null) {
 257            throw new IllegalArgumentException("cronExpression cannot be null");
 258        }
 259
 260        this.cronExpression = cronExpression.toUpperCase(Locale.US);
 261
 262        buildExpression(this.cronExpression);
 263    }
 264
 265    public CronExpression(String cronExpression, TimeZone timeZone) throws ParseException {
 266        this(cronExpression);
 267
 268        setTimeZone(timeZone);
 269    }
 270
 271    public CronExpression(String cronExpression, DateTimeZone timeZone) throws ParseException {
 272        this(cronExpression);
 273
 274        setTimeZone(timeZone);
 275    }
 276
 277    public boolean isSatisfiedBy(DateTime date) {
 278        return isSatisfiedBy(date.toDate());
 279    }
 280
 281    /**
 282     * Indicates whether the given date satisfies the cron expression. Note that
 283     * milliseconds are ignored, so two Dates falling on different milliseconds
 284     * of the same second will always have the same result here.
 285     *
 286     * @param date the date to evaluate
 287     * @return a boolean indicating whether the given date satisfies the cron
 288     *         expression
 289     */
 290    public boolean isSatisfiedBy(Date date) {
 291        Calendar testDateCal = Calendar.getInstance(getTimeZone());
 292        testDateCal.setTime(date);
 293        testDateCal.set(Calendar.MILLISECOND, 0);
 294        Date originalDate = testDateCal.getTime();
 295
 296        testDateCal.add(Calendar.SECOND, -1);
 297
 298        Date timeAfter = getTimeAfter(testDateCal.getTime());
 299
 300        return ((timeAfter != null) && (timeAfter.equals(originalDate)));
 301    }
 302
 303    public DateTime getNextValidTimeAfter(DateTime date) {
 304        return new DateTime(getTimeAfter(date.toDate()));
 305    }
 306
 307    /**
 308     * Returns the next date/time <I>after</I> the given date/time which
 309     * satisfies the cron expression.
 310     *
 311     * @param date the date/time at which to begin the search for the next valid
 312     *             date/time
 313     * @return the next valid date/time
 314     */
 315    public Date getNextValidTimeAfter(Date date) {
 316        return getTimeAfter(date);
 317    }
 318
 319    /**
 320     * Returns the next date/time <I>after</I> the given date/time which does
 321     * <I>not</I> satisfy the expression
 322     *
 323     * @param date the date/time at which to begin the search for the next
 324     *             invalid date/time
 325     * @return the next valid date/time
 326     */
 327    public Date getNextInvalidTimeAfter(Date date) {
 328        long difference = 1000;
 329
 330        //move back to the nearest second so differences will be accurate
 331        Calendar adjustCal = Calendar.getInstance(getTimeZone());
 332        adjustCal.setTime(date);
 333        adjustCal.set(Calendar.MILLISECOND, 0);
 334        Date lastDate = adjustCal.getTime();
 335
 336        Date newDate = null;
 337
 338        //TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
 339
 340        //keep getting the next included time until it's farther than one second
 341        // apart. At that point, lastDate is the last valid fire time. We return
 342        // the second immediately following it.
 343        while (difference == 1000) {
 344            newDate = getTimeAfter(lastDate);
 345            if(newDate == null)
 346                break;
 347
 348            difference = newDate.getTime() - lastDate.getTime();
 349
 350            if (difference == 1000) {
 351                lastDate = newDate;
 352            }
 353        }
 354
 355        return new Date(lastDate.getTime() + 1000);
 356    }
 357
 358    /**
 359     * Returns the time zone for which this <code>CronExpression</code>
 360     * will be resolved.
 361     */
 362    public TimeZone getTimeZone() {
 363        if (timeZone == null) {
 364            timeZone = TimeZone.getDefault();
 365        }
 366
 367        return timeZone;
 368    }
 369
 370    /**
 371     * Sets the time zone for which  this <code>CronExpression</code>
 372     * will be resolved.
 373     */
 374    public void setTimeZone(TimeZone timeZone) {
 375        this.timeZone = timeZone;
 376    }
 377
 378    /**
 379     * Sets the time zone for which  this <code>CronExpression</code>
 380     * will be resolved.
 381     */
 382    public void setTimeZone(DateTimeZone timeZone) {
 383        this.timeZone = timeZone.toTimeZone();
 384    }
 385
 386    /**
 387     * Returns the string representation of the <CODE>CronExpression</CODE>
 388     *
 389     * @return a string representation of the <CODE>CronExpression</CODE>
 390     */
 391    @Override
 392    public String toString() {
 393        return cronExpression;
 394    }
 395
 396    /**
 397     * Indicates whether the specified cron expression can be parsed into a
 398     * valid cron expression
 399     *
 400     * @param cronExpression the expression to evaluate
 401     * @return a boolean indicating whether the given expression is a valid cron
 402     *         expression
 403     */
 404    public static boolean isValidExpression(String cronExpression) {
 405
 406        try {
 407            new CronExpression(cronExpression);
 408        } catch (ParseException pe) {
 409            return false;
 410        }
 411
 412        return true;
 413    }
 414
 415    public static void validateExpression(String cronExpression) throws ParseException {
 416
 417        new CronExpression(cronExpression);
 418    }
 419
 420
 421    ////////////////////////////////////////////////////////////////////////////
 422    //
 423    // Expression Parsing Functions
 424    //
 425    ////////////////////////////////////////////////////////////////////////////
 426
 427    protected void buildExpression(String expression) throws ParseException {
 428        expressionParsed = true;
 429
 430        try {
 431
 432            if (seconds == null) {
 433                seconds = new TreeSet<Integer>();
 434            }
 435            if (minutes == null) {
 436                minutes = new TreeSet<Integer>();
 437            }
 438            if (hours == null) {
 439                hours = new TreeSet<Integer>();
 440            }
 441            if (daysOfMonth == null) {
 442                daysOfMonth = new TreeSet<Integer>();
 443            }
 444            if (months == null) {
 445                months = new TreeSet<Integer>();
 446            }
 447            if (daysOfWeek == null) {
 448                daysOfWeek = new TreeSet<Integer>();
 449            }
 450            if (years == null) {
 451                years = new TreeSet<Integer>();
 452            }
 453
 454            int exprOn = SECOND;
 455
 456            StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
 457                    false);
 458
 459            while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
 460                String expr = exprsTok.nextToken().trim();
 461
 462                // throw an exception if L is used with other days of the month
 463                if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) {
 464                    throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
 465                }
 466                // throw an exception if L is used with other days of the week
 467                if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1  && expr.indexOf(",") >= 0) {
 468                    throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
 469                }
 470                if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
 471                    throw new ParseException("Support for specifying multiple \"nth\" days is not imlemented.", -1);
 472                }
 473
 474                StringTokenizer vTok = new StringTokenizer(expr, ",");
 475                while (vTok.hasMoreTokens()) {
 476                    String v = vTok.nextToken();
 477                    storeExpressionVals(0, v, exprOn);
 478                }
 479
 480                exprOn++;
 481            }
 482
 483            if (exprOn <= DAY_OF_WEEK) {
 484                throw new ParseException("Unexpected end of expression.",
 485                            expression.length());
 486            }
 487
 488            if (exprOn <= YEAR) {
 489                storeExpressionVals(0, "*", YEAR);
 490            }
 491
 492            TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
 493            TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
 494
 495            // Copying the logic from the UnsupportedOperationException below
 496            boolean dayOfMSpec = !dom.contains(NO_SPEC);
 497            boolean dayOfWSpec = !dow.contains(NO_SPEC);
 498
 499            if (dayOfMSpec && !dayOfWSpec) {
 500                // skip
 501            } else if (dayOfWSpec && !dayOfMSpec) {
 502                // skip
 503            } else {
 504                throw new ParseException(
 505                        "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
 506            }
 507        } catch (ParseException pe) {
 508            throw pe;
 509        } catch (Exception e) {
 510            throw new ParseException("Illegal cron expression format ("
 511                    + e.toString() + ")", 0);
 512        }
 513    }
 514
 515    protected int storeExpressionVals(int pos, String s, int type)
 516        throws ParseException {
 517
 518        int incr = 0;
 519        int i = skipWhiteSpace(pos, s);
 520        if (i >= s.length()) {
 521            return i;
 522        }
 523        char c = s.charAt(i);
 524        if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
 525            String sub = s.substring(i, i + 3);
 526            int sval = -1;
 527            int eval = -1;
 528            if (type == MONTH) {
 529                sval = getMonthNumber(sub) + 1;
 530                if (sval <= 0) {
 531                    throw new ParseException("Invalid Month value: '" + sub + "'", i);
 532                }
 533                if (s.length() > i + 3) {
 534                    c = s.charAt(i + 3);
 535                    if (c == '-') {
 536                        i += 4;
 537                        sub = s.substring(i, i + 3);
 538                        eval = getMonthNumber(sub) + 1;
 539                        if (eval <= 0) {
 540                            throw new ParseException("Invalid Month value: '" + sub + "'", i);
 541                        }
 542                    }
 543                }
 544            } else if (type == DAY_OF_WEEK) {
 545                sval = getDayOfWeekNumber(sub);
 546                if (sval < 0) {
 547                    throw new ParseException("Invalid Day-of-Week value: '"
 548                                + sub + "'", i);
 549                }
 550                if (s.length() > i + 3) {
 551                    c = s.charAt(i + 3);
 552                    if (c == '-') {
 553                        i += 4;
 554                        sub = s.substring(i, i + 3);
 555                        eval = getDayOfWeekNumber(sub);
 556                        if (eval < 0) {
 557                            throw new ParseException(
 558                                    "Invalid Day-of-Week value: '" + sub
 559                                        + "'", i);
 560                        }
 561                    } else if (c == '#') {
 562                        try {
 563                            i += 4;
 564                            nthdayOfWeek = Integer.parseInt(s.substring(i));
 565                            if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
 566                                throw new Exception();
 567                            }
 568                        } catch (Exception e) {
 569                            throw new ParseException(
 570                                    "A numeric value between 1 and 5 must follow the '#' option",
 571                                    i);
 572                        }
 573                    } else if (c == 'L') {
 574                        lastdayOfWeek = true;
 575                        i++;
 576                    }
 577                }
 578
 579            } else {
 580                throw new ParseException(
 581                        "Illegal characters for this position: '" + sub + "'",
 582                        i);
 583            }
 584            if (eval != -1) {
 585                incr = 1;
 586            }
 587            addToSet(sval, eval, incr, type);
 588            return (i + 3);
 589        }
 590
 591        if (c == '?') {
 592            i++;
 593            if ((i + 1) < s.length()
 594                    && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
 595                throw new ParseException("Illegal character after '?': "
 596                            + s.charAt(i), i);
 597            }
 598            if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
 599                throw new ParseException(
 600                            "'?' can only be specfied for Day-of-Month or Day-of-Week.",
 601                            i);
 602            }
 603            if (type == DAY_OF_WEEK && !lastdayOfMonth) {
 604                int val = ((Integer) daysOfMonth.last()).intValue();
 605                if (val == NO_SPEC_INT) {
 606                    throw new ParseException(
 607                                "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
 608                                i);
 609                }
 610            }
 611
 612            addToSet(NO_SPEC_INT, -1, 0, type);
 613            return i;
 614        }
 615
 616        if (c == '*' || c == '/') {
 617            if (c == '*' && (i + 1) >= s.length()) {
 618                addToSet(ALL_SPEC_INT, -1, incr, type);
 619                return i + 1;
 620            } else if (c == '/'
 621                    && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
 622                            .charAt(i + 1) == '\t')) {
 623                throw new ParseException("'/' must be followed by an integer.", i);
 624            } else if (c == '*') {
 625                i++;
 626            }
 627            c = s.charAt(i);
 628            if (c == '/') { // is an increment specified?
 629                i++;
 630                if (i >= s.length()) {
 631                    throw new ParseException("Unexpected end of string.", i);
 632                }
 633
 634                incr = getNumericValue(s, i);
 635
 636                i++;
 637                if (incr > 10) {
 638                    i++;
 639                }
 640                if (incr > 59 && (type == SECOND || type == MINUTE)) {
 641                    throw new ParseException("Increment > 60 : " + incr, i);
 642                } else if (incr > 23 && (type == HOUR)) {
 643                    throw new ParseException("Increment > 24 : " + incr, i);
 644                } else if (incr > 31 && (type == DAY_OF_MONTH)) {
 645                    throw new ParseException("Increment > 31 : " + incr, i);
 646                } else if (incr > 7 && (type == DAY_OF_WEEK)) {
 647                    throw new ParseException("Increment > 7 : " + incr, i);
 648                } else if (incr > 12 && (type == MONTH)) {
 649                    throw new ParseException("Increment > 12 : " + incr, i);
 650                }
 651            } else {
 652                incr = 1;
 653            }
 654
 655            addToSet(ALL_SPEC_INT, -1, incr, type);
 656            return i;
 657        } else if (c == 'L') {
 658            i++;
 659            if (type == DAY_OF_MONTH) {
 660                lastdayOfMonth = true;
 661            }
 662            if (type == DAY_OF_WEEK) {
 663                addToSet(7, 7, 0, type);
 664            }
 665            if(type == DAY_OF_MONTH && s.length() > i) {
 666                c = s.charAt(i);
 667                if(c == '-') {
 668                    ValueSet vs = getValue(0, s, i+1);
 669                    lastdayOffset = vs.value;
 670                    if(lastdayOffset > 30)
 671                        throw new ParseException("Offset from last day must be <= 30", i+1);
 672                    i = vs.pos;
 673                }
 674                if(s.length() > i) {
 675                    c = s.charAt(i);
 676                    if(c == 'W') {
 677                        nearestWeekday = true;
 678                        i++;
 679                    }
 680                }
 681            }
 682            return i;
 683        } else if (c >= '0' && c <= '9') {
 684            int val = Integer.parseInt(String.valueOf(c));
 685            i++;
 686            if (i >= s.length()) {
 687                addToSet(val, -1, -1, type);
 688            } else {
 689                c = s.charAt(i);
 690                if (c >= '0' && c <= '9') {
 691                    ValueSet vs = getValue(val, s, i);
 692                    val = vs.value;
 693                    i = vs.pos;
 694                }
 695                i = checkNext(i, s, val, type);
 696                return i;
 697            }
 698        } else {
 699            throw new ParseException("Unexpected character: " + c, i);
 700        }
 701
 702        return i;
 703    }
 704
 705    protected int checkNext(int pos, String s, int val, int type)
 706        throws ParseException {
 707
 708        int end = -1;
 709        int i = pos;
 710
 711        if (i >= s.length()) {
 712            addToSet(val, end, -1, type);
 713            return i;
 714        }
 715
 716        char c = s.charAt(pos);
 717
 718        if (c == 'L') {
 719            if (type == DAY_OF_WEEK) {
 720                if(val < 1 || val > 7)
 721                    throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
 722                lastdayOfWeek = true;
 723            } else {
 724                throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
 725            }
 726            TreeSet<Integer> set = getSet(type);
 727            set.add(Integer.valueOf(val));
 728            i++;
 729            return i;
 730        }
 731
 732        if (c == 'W') {
 733            if (type == DAY_OF_MONTH) {
 734                nearestWeekday = true;
 735            } else {
 736                throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
 737            }
 738            if(val > 31)
 739                throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
 740            TreeSet<Integer> set = getSet(type);
 741            set.add(Integer.valueOf(val));
 742            i++;
 743            return i;
 744        }
 745
 746        if (c == '#') {
 747            if (type != DAY_OF_WEEK) {
 748                throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
 749            }
 750            i++;
 751            try {
 752                nthdayOfWeek = Integer.parseInt(s.substring(i));
 753                if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
 754                    throw new Exception();
 755                }
 756            } catch (Exception e) {
 757                throw new ParseException(
 758                        "A numeric value between 1 and 5 must follow the '#' option",
 759                        i);
 760            }
 761
 762            TreeSet<Integer> set = getSet(type);
 763            set.add(Integer.valueOf(val));
 764            i++;
 765            return i;
 766        }
 767
 768        if (c == '-') {
 769            i++;
 770            c = s.charAt(i);
 771            int v = Integer.parseInt(String.valueOf(c));
 772            end = v;
 773            i++;
 774            if (i >= s.length()) {
 775                addToSet(val, end, 1, type);
 776                return i;
 777            }
 778            c = s.charAt(i);
 779            if (c >= '0' && c <= '9') {
 780                ValueSet vs = getValue(v, s, i);
 781                int v1 = vs.value;
 782                end = v1;
 783                i = vs.pos;
 784            }
 785            if (i < s.length() && ((c = s.charAt(i)) == '/')) {
 786                i++;
 787                c = s.charAt(i);
 788                int v2 = Integer.parseInt(String.valueOf(c));
 789                i++;
 790                if (i >= s.length()) {
 791                    addToSet(val, end, v2, type);
 792                    return i;
 793                }
 794                c = s.charAt(i);
 795                if (c >= '0' && c <= '9') {
 796                    ValueSet vs = getValue(v2, s, i);
 797                    int v3 = vs.value;
 798                    addToSet(val, end, v3, type);
 799                    i = vs.pos;
 800                    return i;
 801                } else {
 802                    addToSet(val, end, v2, type);
 803                    return i;
 804                }
 805            } else {
 806                addToSet(val, end, 1, type);
 807                return i;
 808            }
 809        }
 810
 811        if (c == '/') {
 812            i++;
 813            c = s.charAt(i);
 814            int v2 = Integer.parseInt(String.valueOf(c));
 815            i++;
 816            if (i >= s.length()) {
 817                addToSet(val, end, v2, type);
 818                return i;
 819            }
 820            c = s.charAt(i);
 821            if (c >= '0' && c <= '9') {
 822                ValueSet vs = getValue(v2, s, i);
 823                int v3 = vs.value;
 824                addToSet(val, end, v3, type);
 825                i = vs.pos;
 826                return i;
 827            } else {
 828                throw new ParseException("Unexpected character '" + c + "' after '/'", i);
 829            }
 830        }
 831
 832        addToSet(val, end, 0, type);
 833        i++;
 834        return i;
 835    }
 836
 837    public String getCronExpression() {
 838        return cronExpression;
 839    }
 840
 841    public String getExpressionSummary() {
 842        StringBuffer buf = new StringBuffer();
 843
 844        buf.append("seconds: ");
 845        buf.append(getExpressionSetSummary(seconds));
 846        buf.append("\n");
 847        buf.append("minutes: ");
 848        buf.append(getExpressionSetSummary(minutes));
 849        buf.append("\n");
 850        buf.append("hours: ");
 851        buf.append(getExpressionSetSummary(hours));
 852        buf.append("\n");
 853        buf.append("daysOfMonth: ");
 854        buf.append(getExpressionSetSummary(daysOfMonth));
 855        buf.append("\n");
 856        buf.append("months: ");
 857        buf.append(getExpressionSetSummary(months));
 858        buf.append("\n");
 859        buf.append("daysOfWeek: ");
 860        buf.append(getExpressionSetSummary(daysOfWeek));
 861        buf.append("\n");
 862        buf.append("lastdayOfWeek: ");
 863        buf.append(lastdayOfWeek);
 864        buf.append("\n");
 865        buf.append("nearestWeekday: ");
 866        buf.append(nearestWeekday);
 867        buf.append("\n");
 868        buf.append("NthDayOfWeek: ");
 869        buf.append(nthdayOfWeek);
 870        buf.append("\n");
 871        buf.append("lastdayOfMonth: ");
 872        buf.append(lastdayOfMonth);
 873        buf.append("\n");
 874        buf.append("years: ");
 875        buf.append(getExpressionSetSummary(years));
 876        buf.append("\n");
 877
 878        return buf.toString();
 879    }
 880
 881    protected String getExpressionSetSummary(java.util.Set<Integer> set) {
 882
 883        if (set.contains(NO_SPEC)) {
 884            return "?";
 885        }
 886        if (set.contains(ALL_SPEC)) {
 887            return "*";
 888        }
 889
 890        StringBuffer buf = new StringBuffer();
 891
 892        Iterator<Integer> itr = set.iterator();
 893        boolean first = true;
 894        while (itr.hasNext()) {
 895            Integer iVal = itr.next();
 896            String val = iVal.toString();
 897            if (!first) {
 898                buf.append(",");
 899            }
 900            buf.append(val);
 901            first = false;
 902        }
 903
 904        return buf.toString();
 905    }
 906
 907    protected String getExpressionSetSummary(java.util.ArrayList<Integer> list) {
 908
 909        if (list.contains(NO_SPEC)) {
 910            return "?";
 911        }
 912        if (list.contains(ALL_SPEC)) {
 913            return "*";
 914        }
 915
 916        StringBuffer buf = new StringBuffer();
 917
 918        Iterator<Integer> itr = list.iterator();
 919        boolean first = true;
 920        while (itr.hasNext()) {
 921            Integer iVal = itr.next();
 922            String val = iVal.toString();
 923            if (!first) {
 924                buf.append(",");
 925            }
 926            buf.append(val);
 927            first = false;
 928        }
 929
 930        return buf.toString();
 931    }
 932
 933    protected int skipWhiteSpace(int i, String s) {
 934        for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
 935            ;
 936        }
 937
 938        return i;
 939    }
 940
 941    protected int findNextWhiteSpace(int i, String s) {
 942        for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
 943            ;
 944        }
 945
 946        return i;
 947    }
 948
 949    protected void addToSet(int val, int end, int incr, int type)
 950        throws ParseException {
 951
 952        TreeSet<Integer> set = getSet(type);
 953
 954        if (type == SECOND || type == MINUTE) {
 955            if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
 956                throw new ParseException(
 957                        "Minute and Second values must be between 0 and 59",
 958                        -1);
 959            }
 960        } else if (type == HOUR) {
 961            if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
 962                throw new ParseException(
 963                        "Hour values must be between 0 and 23", -1);
 964            }
 965        } else if (type == DAY_OF_MONTH) {
 966            if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
 967                    && (val != NO_SPEC_INT)) {
 968                throw new ParseException(
 969                        "Day of month values must be between 1 and 31", -1);
 970            }
 971        } else if (type == MONTH) {
 972            if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
 973                throw new ParseException(
 974                        "Month values must be between 1 and 12", -1);
 975            }
 976        } else if (type == DAY_OF_WEEK) {
 977            if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
 978                    && (val != NO_SPEC_INT)) {
 979                throw new ParseException(
 980                        "Day-of-Week values must be between 1 and 7", -1);
 981            }
 982        }
 983
 984        if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
 985            if (val != -1) {
 986                set.add(Integer.valueOf(val));
 987            } else {
 988                set.add(NO_SPEC);
 989            }
 990
 991            return;
 992        }
 993
 994        int startAt = val;
 995        int stopAt = end;
 996
 997        if (val == ALL_SPEC_INT && incr <= 0) {
 998            incr = 1;
 999            set.add(ALL_SPEC); // put in a marker, but also fill values
1000        }
1001
1002        if (type == SECOND || type == MINUTE) {
1003            if (stopAt == -1) {
1004                stopAt = 59;
1005            }
1006            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1007                startAt = 0;
1008            }
1009        } else if (type == HOUR) {
1010            if (stopAt == -1) {
1011                stopAt = 23;
1012            }
1013            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1014                startAt = 0;
1015            }
1016        } else if (type == DAY_OF_MONTH) {
1017            if (stopAt == -1) {
1018                stopAt = 31;
1019            }
1020            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1021                startAt = 1;
1022            }
1023        } else if (type == MONTH) {
1024            if (stopAt == -1) {
1025                stopAt = 12;
1026            }
1027            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1028                startAt = 1;
1029            }
1030        } else if (type == DAY_OF_WEEK) {
1031            if (stopAt == -1) {
1032                stopAt = 7;
1033            }
1034            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1035                startAt = 1;
1036            }
1037        } else if (type == YEAR) {
1038            if (stopAt == -1) {
1039                stopAt = MAX_YEAR;
1040            }
1041            if (startAt == -1 || startAt == ALL_SPEC_INT) {
1042                startAt = 1970;
1043            }
1044        }
1045
1046        // if the end of the range is before the start, then we need to overflow into
1047        // the next day, month etc. This is done by adding the maximum amount for that
1048        // type, and using modulus max to determine the value being added.
1049        int max = -1;
1050        if (stopAt < startAt) {
1051            switch (type) {
1052              case       SECOND : max = 60; break;
1053              case       MINUTE : max = 60; break;
1054              case         HOUR : max = 24; break;
1055              case        MONTH : max = 12; break;
1056              case  DAY_OF_WEEK : max = 7;  break;
1057              case DAY_OF_MONTH : max = 31; break;
1058              case         YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
1059              default           : throw new IllegalArgumentException("Unexpected type encountered");
1060            }
1061            stopAt += max;
1062        }
1063
1064        for (int i = startAt; i <= stopAt; i += incr) {
1065            if (max == -1) {
1066                // ie: there's no max to overflow over
1067                set.add(Integer.valueOf(i));
1068            } else {
1069                // take the modulus to get the real value
1070                int i2 = i % max;
1071
1072                // 1-indexed ranges should not include 0, and should include their max
1073                if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
1074                    i2 = max;
1075                }
1076
1077                set.add(Integer.valueOf(i2));
1078            }
1079        }
1080    }
1081
1082    protected TreeSet<Integer> getSet(int type) {
1083        switch (type) {
1084            case SECOND:
1085                return seconds;
1086            case MINUTE:
1087                return minutes;
1088            case HOUR:
1089                return hours;
1090            case DAY_OF_MONTH:
1091                return daysOfMonth;
1092            case MONTH:
1093                return months;
1094            case DAY_OF_WEEK:
1095                return daysOfWeek;
1096            case YEAR:
1097                return years;
1098            default:
1099                return null;
1100        }
1101    }
1102
1103    protected ValueSet getValue(int v, String s, int i) {
1104        char c = s.charAt(i);
1105        StringBuilder s1 = new StringBuilder(String.valueOf(v));
1106        while (c >= '0' && c <= '9') {
1107            s1.append(c);
1108            i++;
1109            if (i >= s.length()) {
1110                break;
1111            }
1112            c = s.charAt(i);
1113        }
1114        ValueSet val = new ValueSet();
1115
1116        val.pos = (i < s.length()) ? i : i + 1;
1117        val.value = Integer.parseInt(s1.toString());
1118        return val;
1119    }
1120
1121    protected int getNumericValue(String s, int i) {
1122        int endOfVal = findNextWhiteSpace(i, s);
1123        String val = s.substring(i, endOfVal);
1124        return Integer.parseInt(val);
1125    }
1126
1127    protected int getMonthNumber(String s) {
1128        Integer integer = (Integer) monthMap.get(s);
1129
1130        if (integer == null) {
1131            return -1;
1132        }
1133
1134        return integer.intValue();
1135    }
1136
1137    protected int getDayOfWeekNumber(String s) {
1138        Integer integer = (Integer) dayMap.get(s);
1139
1140        if (integer == null) {
1141            return -1;
1142        }
1143
1144        return integer.intValue();
1145    }
1146
1147    ////////////////////////////////////////////////////////////////////////////
1148    //
1149    // Computation Functions
1150    //
1151    ////////////////////////////////////////////////////////////////////////////
1152
1153    public Date getTimeAfter(Date afterTime) {
1154
1155        // Computation is based on Gregorian year only.
1156        Calendar cl = new java.util.GregorianCalendar(getTimeZone());
1157
1158        // move ahead one second, since we're computing the time *after* the
1159        // given time
1160        afterTime = new Date(afterTime.getTime() + 1000);
1161        // CronTrigger does not deal with milliseconds
1162        cl.setTime(afterTime);
1163        cl.set(Calendar.MILLISECOND, 0);
1164
1165        boolean gotOne = false;
1166        // loop until we've computed the next time, or we've past the endTime
1167        while (!gotOne) {
1168
1169            //if (endTime != null && cl.getTime().after(endTime)) return null;
1170            if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
1171                return null;
1172            }
1173
1174            SortedSet<Integer> st = null;
1175            int t = 0;
1176
1177            int sec = cl.get(Calendar.SECOND);
1178            int min = cl.get(Calendar.MINUTE);
1179
1180            // get second.................................................
1181            st = seconds.tailSet(Integer.valueOf(sec));
1182            if (st != null && st.size() != 0) {
1183                sec = st.first().intValue();
1184            } else {
1185                sec = seconds.first().intValue();
1186                min++;
1187                cl.set(Calendar.MINUTE, min);
1188            }
1189            cl.set(Calendar.SECOND, sec);
1190
1191            min = cl.get(Calendar.MINUTE);
1192            int hr = cl.get(Calendar.HOUR_OF_DAY);
1193            t = -1;
1194
1195            // get minute.................................................
1196            st = minutes.tailSet(Integer.valueOf(min));
1197            if (st != null && st.size() != 0) {
1198                t = min;
1199                min = st.first().intValue();
1200            } else {
1201                min = minutes.first().intValue();
1202                hr++;
1203            }
1204            if (min != t) {
1205                cl.set(Calendar.SECOND, 0);
1206                cl.set(Calendar.MINUTE, min);
1207                setCalendarHour(cl, hr);
1208                continue;
1209            }
1210            cl.set(Calendar.MINUTE, min);
1211
1212            hr = cl.get(Calendar.HOUR_OF_DAY);
1213            int day = cl.get(Calendar.DAY_OF_MONTH);
1214            t = -1;
1215
1216            // get hour...................................................
1217            st = hours.tailSet(Integer.valueOf(hr));
1218            if (st != null && st.size() != 0) {
1219                t = hr;
1220                hr = st.first().intValue();
1221            } else {
1222                hr = hours.first().intValue();
1223                day++;
1224            }
1225            if (hr != t) {
1226                cl.set(Calendar.SECOND, 0);
1227                cl.set(Calendar.MINUTE, 0);
1228                cl.set(Calendar.DAY_OF_MONTH, day);
1229                setCalendarHour(cl, hr);
1230                continue;
1231            }
1232            cl.set(Calendar.HOUR_OF_DAY, hr);
1233
1234            day = cl.get(Calendar.DAY_OF_MONTH);
1235            int mon = cl.get(Calendar.MONTH) + 1;
1236            // '+ 1' because calendar is 0-based for this field, and we are
1237            // 1-based
1238            t = -1;
1239            int tmon = mon;
1240
1241            // get day...................................................
1242            boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
1243            boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
1244            if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
1245                st = daysOfMonth.tailSet(Integer.valueOf(day));
1246                if (lastdayOfMonth) {
1247                    if(!nearestWeekday) {
1248                        t = day;
1249                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1250                        day -= lastdayOffset;
1251                    } else {
1252                        t = day;
1253                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1254                        day -= lastdayOffset;
1255
1256                        java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
1257                        tcal.set(Calendar.SECOND, 0);
1258                        tcal.set(Calendar.MINUTE, 0);
1259                        tcal.set(Calendar.HOUR_OF_DAY, 0);
1260                        tcal.set(Calendar.DAY_OF_MONTH, day);
1261                        tcal.set(Calendar.MONTH, mon - 1);
1262                        tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1263
1264                        int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1265                        int dow = tcal.get(Calendar.DAY_OF_WEEK);
1266
1267                        if(dow == Calendar.SATURDAY && day == 1) {
1268                            day += 2;
1269                        } else if(dow == Calendar.SATURDAY) {
1270                            day -= 1;
1271                        } else if(dow == Calendar.SUNDAY && day == ldom) {
1272                            day -= 2;
1273                        } else if(dow == Calendar.SUNDAY) {
1274                            day += 1;
1275                        }
1276
1277                        tcal.set(Calendar.SECOND, sec);
1278                        tcal.set(Calendar.MINUTE, min);
1279                        tcal.set(Calendar.HOUR_OF_DAY, hr);
1280                        tcal.set(Calendar.DAY_OF_MONTH, day);
1281                        tcal.set(Calendar.MONTH, mon - 1);
1282                        Date nTime = tcal.getTime();
1283                        if(nTime.before(afterTime)) {
1284                            day = 1;
1285                            mon++;
1286                        }
1287                    }
1288                } else if(nearestWeekday) {
1289                    t = day;
1290                    day = ((Integer) daysOfMonth.first()).intValue();
1291
1292                    java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
1293                    tcal.set(Calendar.SECOND, 0);
1294                    tcal.set(Calendar.MINUTE, 0);
1295                    tcal.set(Calendar.HOUR_OF_DAY, 0);
1296                    tcal.set(Calendar.DAY_OF_MONTH, day);
1297                    tcal.set(Calendar.MONTH, mon - 1);
1298                    tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1299
1300                    int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1301                    int dow = tcal.get(Calendar.DAY_OF_WEEK);
1302
1303                    if(dow == Calendar.SATURDAY && day == 1) {
1304                        day += 2;
1305                    } else if(dow == Calendar.SATURDAY) {
1306                        day -= 1;
1307                    } else if(dow == Calendar.SUNDAY && day == ldom) {
1308                        day -= 2;
1309                    } else if(dow == Calendar.SUNDAY) {
1310                        day += 1;
1311                    }
1312
1313
1314                    tcal.set(Calendar.SECOND, sec);
1315                    tcal.set(Calendar.MINUTE, min);
1316                    tcal.set(Calendar.HOUR_OF_DAY, hr);
1317                    tcal.set(Calendar.DAY_OF_MONTH, day);
1318                    tcal.set(Calendar.MONTH, mon - 1);
1319                    Date nTime = tcal.getTime();
1320                    if(nTime.before(afterTime)) {
1321                        day = ((Integer) daysOfMonth.first()).intValue();
1322                        mon++;
1323                    }
1324                } else if (st != null && st.size() != 0) {
1325                    t = day;
1326                    day = st.first().intValue();
1327                    // make sure we don't over-run a short month, such as february
1328                    int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1329                    if (day > lastDay) {
1330                        day = ((Integer) daysOfMonth.first()).intValue();
1331                        mon++;
1332                    }
1333                } else {
1334                    day = ((Integer) daysOfMonth.first()).intValue();
1335                    mon++;
1336                }
1337
1338                if (day != t || mon != tmon) {
1339                    cl.set(Calendar.SECOND, 0);
1340                    cl.set(Calendar.MINUTE, 0);
1341                    cl.set(Calendar.HOUR_OF_DAY, 0);
1342                    cl.set(Calendar.DAY_OF_MONTH, day);
1343                    cl.set(Calendar.MONTH, mon - 1);
1344                    // '- 1' because calendar is 0-based for this field, and we
1345                    // are 1-based
1346                    continue;
1347                }
1348            } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
1349                if (lastdayOfWeek) { // are we looking for the last XXX day of
1350                    // the month?
1351                    int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
1352

Large files files are truncated, but you can click here to view the full file