• 定时器CronExpression配置说明详解


          项目中经常要使用到定时器,其中CronExpression配置非常重要。现在就配置说明详细解说一下:

    CronExpression表达式是由6个必需字段(秒、分、时、日、月、周)和一个可选字段(年)通过空格组成。

    cronExpression表达式组成说明
    序号 字段名 允许值 允许特殊字符
    1 0-59 , - * /
    2 0-59 , - * /
    3 0-23 , - * /
    4 1-31 , - * ? / L W
    5 1-12 or JAN-DEC , - * /
    6 1-7 or SUN-SAT , - * ? / L #
    7 年(可选字段) empty, 1970-2199 , - * /

    下面对特殊字符表达意思进行解说:

    * 代表所有值,比如一分钟里代表每一个分钟。

    ? 只允许使用在日和周的表达式中,代表不定值。使用的场景为不需要关心当前设置这个字段的值,例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?

    -  代表在一个范围里,比如10-12  代表10、11、12

    ,代表指定多个值,比如 在周字段配置 1,3,5 代表周一、周三、周五

    /  代表递增触发,比如在秒字段 0/15  代表0,15,30,45  每15秒执行一次。 5/15代表 5,20,35,50

    L  只允许使用在日和周的表达式中,L是last的简称,配置在日中代表一个月的最后一天,比如1月则为31日,2月非闰年则为28日,闰年为29日那天。  

        配置在周字段里,代表7或者星期六。6L代表这个月的最后一个星期五。

    W 只允许使用在日字段里,代表最近的工作日(周一到周五),比如15w, 这个月的15号是星期六,则14号(星期五)触发,如果是星期日,则16号(星期一)触发,如果是星期二,则星期二触发。

    LW也可以联合使用,如果配置LW,则代表这个月最后的工作日。

    # 值允许使用在周字段里,比如6#3 代表这个月的第三个星期五 (6代表星期五,#3代表这个月的第三个)

    注意:月和周字段是不区分大小写(JAN Jan  MON mon)

              配置指定日和周字段里,不完整,需要在其中一个用?代替配置。

             溢出范围是支持的,但是溢出范围过大可能会出问题。比如配置22-2  晚上10点到凌晨2点, NOV-FEB 代表11月-2月。这个是支持的,但14-6,这个就溢出太对,可能会出问题。

    下面就例子进行说明:

    0 0 12 * * ?   每天中午十二点触发

    0 15 10 ? * *   每天早上10:15触发 

    0 15 10 * * ?  每天早上10:15触发

    0 15 10 * * ? *   每天早上10:15触发

    0 15 10 * * ? 2005   2005年的每天早上10:15触发

    0 * 14 * * ?   每天从下午2点开始到2点59分每分钟一次触发

    0 0/5 14 * * ?  每天从下午2点开始到2:55分结束每5分钟一次触发

    0 0/5 14,18 * * ?  每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发 

    0 0-5 14 * * ?  每天14:00至14:05每分钟一次触发

    0 10,44 14 ? 3 WED   三月的每周三的14:10和14:44触发 

    0 15 10 ? * MON-FRI   每个周一、周二、周三、周四、周五的10:15触发 

     0 15 10 15 * ?     每月15号上午10点15分触发

    0 15 10 L * ?   每月最后一天的10点15分触发 

    0 15 10 ? * 6L  每月最后一周的星期五的10点15分触发

    0 15 10 ? * 6L 2002-2005    从2002年到2005年每月最后一周的星期五的10点15分触发

    0 15 10 ? * 6#3  每月的第三周的星期五10点15分触发开始触发 

    具体可参照quartz源码

    CronTrigger类

      1 /*
      2  * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
      3  * 
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
      5  * use this file except in compliance with the License. You may obtain a copy 
      6  * of the License at 
      7  * 
      8  *   http://www.apache.org/licenses/LICENSE-2.0 
      9  *   
     10  * Unless required by applicable law or agreed to in writing, software 
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
     13  * License for the specific language governing permissions and limitations 
     14  * under the License.
     15  * 
     16  */
     17 
     18 package org.quartz;
     19 
     20 import java.util.Calendar;
     21 import java.util.TimeZone;
     22 
     23 /**
     24  * The public interface for inspecting settings specific to a CronTrigger, .
     25  * which is used to fire a <code>{@link org.quartz.Job}</code>
     26  * at given moments in time, defined with Unix 'cron-like' schedule definitions.
     27  * 
     28  * <p>
     29  * For those unfamiliar with "cron", this means being able to create a firing
     30  * schedule such as: "At 8:00am every Monday through Friday" or "At 1:30am
     31  * every last Friday of the month".
     32  * </p>
     33  * 
     34  * <p>
     35  * The format of a "Cron-Expression" string is documented on the 
     36  * {@link org.quartz.CronExpression} class.
     37  * </p>
     38  * 
     39  * <p>
     40  * Here are some full examples: <br><table cellspacing="8">
     41  * <tr>
     42  * <th align="left">Expression</th>
     43  * <th align="left">&nbsp;</th>
     44  * <th align="left">Meaning</th>
     45  * </tr>
     46  * <tr>
     47  * <td align="left"><code>"0 0 12 * * ?"</code></td>
     48  * <td align="left">&nbsp;</th>
     49  * <td align="left"><code>Fire at 12pm (noon) every day</code></td>
     50  * </tr>
     51  * <tr>
     52  * <td align="left"><code>"0 15 10 ? * *"</code></td>
     53  * <td align="left">&nbsp;</th>
     54  * <td align="left"><code>Fire at 10:15am every day</code></td>
     55  * </tr>
     56  * <tr>
     57  * <td align="left"><code>"0 15 10 * * ?"</code></td>
     58  * <td align="left">&nbsp;</th>
     59  * <td align="left"><code>Fire at 10:15am every day</code></td>
     60  * </tr>
     61  * <tr>
     62  * <td align="left"><code>"0 15 10 * * ? *"</code></td>
     63  * <td align="left">&nbsp;</th>
     64  * <td align="left"><code>Fire at 10:15am every day</code></td>
     65  * </tr>
     66  * <tr>
     67  * <td align="left"><code>"0 15 10 * * ? 2005"</code></td>
     68  * <td align="left">&nbsp;</th>
     69  * <td align="left"><code>Fire at 10:15am every day during the year 2005</code>
     70  * </td>
     71  * </tr>
     72  * <tr>
     73  * <td align="left"><code>"0 * 14 * * ?"</code></td>
     74  * <td align="left">&nbsp;</th>
     75  * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:59pm, every day</code>
     76  * </td>
     77  * </tr>
     78  * <tr>
     79  * <td align="left"><code>"0 0/5 14 * * ?"</code></td>
     80  * <td align="left">&nbsp;</th>
     81  * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day</code>
     82  * </td>
     83  * </tr>
     84  * <tr>
     85  * <td align="left"><code>"0 0/5 14,18 * * ?"</code></td>
     86  * <td align="left">&nbsp;</th>
     87  * <td align="left"><code>Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day</code>
     88  * </td>
     89  * </tr>
     90  * <tr>
     91  * <td align="left"><code>"0 0-5 14 * * ?"</code></td>
     92  * <td align="left">&nbsp;</th>
     93  * <td align="left"><code>Fire every minute starting at 2pm and ending at 2:05pm, every day</code>
     94  * </td>
     95  * </tr>
     96  * <tr>
     97  * <td align="left"><code>"0 10,44 14 ? 3 WED"</code></td>
     98  * <td align="left">&nbsp;</th>
     99  * <td align="left"><code>Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.</code>
    100  * </td>
    101  * </tr>
    102  * <tr>
    103  * <td align="left"><code>"0 15 10 ? * MON-FRI"</code></td>
    104  * <td align="left">&nbsp;</th>
    105  * <td align="left"><code>Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday</code>
    106  * </td>
    107  * </tr>
    108  * <tr>
    109  * <td align="left"><code>"0 15 10 15 * ?"</code></td>
    110  * <td align="left">&nbsp;</th>
    111  * <td align="left"><code>Fire at 10:15am on the 15th day of every month</code>
    112  * </td>
    113  * </tr>
    114  * <tr>
    115  * <td align="left"><code>"0 15 10 L * ?"</code></td>
    116  * <td align="left">&nbsp;</th>
    117  * <td align="left"><code>Fire at 10:15am on the last day of every month</code>
    118  * </td>
    119  * </tr>
    120  * <tr>
    121  * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
    122  * <td align="left">&nbsp;</th>
    123  * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
    124  * </td>
    125  * </tr>
    126  * <tr>
    127  * <td align="left"><code>"0 15 10 ? * 6L"</code></td>
    128  * <td align="left">&nbsp;</th>
    129  * <td align="left"><code>Fire at 10:15am on the last Friday of every month</code>
    130  * </td>
    131  * </tr>
    132  * <tr>
    133  * <td align="left"><code>"0 15 10 ? * 6L 2002-2005"</code></td>
    134  * <td align="left">&nbsp;</th>
    135  * <td align="left"><code>Fire at 10:15am on every last Friday of every month during the years 2002, 2003, 2004 and 2005</code>
    136  * </td>
    137  * </tr>
    138  * <tr>
    139  * <td align="left"><code>"0 15 10 ? * 6#3"</code></td>
    140  * <td align="left">&nbsp;</th>
    141  * <td align="left"><code>Fire at 10:15am on the third Friday of every month</code>
    142  * </td>
    143  * </tr>
    144  * </table>
    145  * </p>
    146  * 
    147  * <p>
    148  * Pay attention to the effects of '?' and '*' in the day-of-week and
    149  * day-of-month fields!
    150  * </p>
    151  * 
    152  * <p>
    153  * <b>NOTES:</b>
    154  * <ul>
    155  * <li>Support for specifying both a day-of-week and a day-of-month value is
    156  * not complete (you'll need to use the '?' character in on of these fields).
    157  * </li>
    158  * <li>Be careful when setting fire times between mid-night and 1:00 AM -
    159  * "daylight savings" can cause a skip or a repeat depending on whether the
    160  * time moves back or jumps forward.</li>
    161  * </ul>
    162  * </p>
    163  * 
    164  * @see CronScheduleBuilder
    165  * @see TriggerBuilder
    166  * 
    167  * @author jhouse
    168  * @author Contributions from Mads Henderson
    169  */
    170 public interface CronTrigger extends Trigger {
    171 
    172     public static final long serialVersionUID = -8644953146451592766L;
    173     
    174     /**
    175      * <p>
    176      * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
    177      * situation, the <code>{@link CronTrigger}</code> wants to be fired now
    178      * by <code>Scheduler</code>.
    179      * </p>
    180      */
    181     public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
    182     
    183     /**
    184      * <p>
    185      * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
    186      * situation, the <code>{@link CronTrigger}</code> wants to have it's
    187      * next-fire-time updated to the next time in the schedule after the
    188      * current time (taking into account any associated <code>{@link Calendar}</code>,
    189      * but it does not want to be fired now.
    190      * </p>
    191      */
    192     public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
    193 
    194     public String getCronExpression();
    195 
    196     /**
    197      * <p>
    198      * Returns the time zone for which the <code>cronExpression</code> of
    199      * this <code>CronTrigger</code> will be resolved.
    200      * </p>
    201      */
    202     public TimeZone getTimeZone();
    203 
    204     public String getExpressionSummary();
    205 
    206     TriggerBuilder<CronTrigger> getTriggerBuilder();
    207 }
    View Code

    CronExpression 类

       1 /*
       2  * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
       3  * 
       4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
       5  * use this file except in compliance with the License. You may obtain a copy 
       6  * of the License at 
       7  * 
       8  *   http://www.apache.org/licenses/LICENSE-2.0 
       9  *   
      10  * Unless required by applicable law or agreed to in writing, software 
      11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
      12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
      13  * License for the specific language governing permissions and limitations 
      14  * under the License.
      15  * 
      16  */
      17 
      18 package org.quartz;
      19 
      20 import java.io.Serializable;
      21 import java.text.ParseException;
      22 import java.util.Calendar;
      23 import java.util.Date;
      24 import java.util.HashMap;
      25 import java.util.Iterator;
      26 import java.util.Locale;
      27 import java.util.Map;
      28 import java.util.SortedSet;
      29 import java.util.StringTokenizer;
      30 import java.util.TimeZone;
      31 import java.util.TreeSet;
      32 
      33 /**
      34  * Provides a parser and evaluator for unix-like cron expressions. Cron 
      35  * expressions provide the ability to specify complex time combinations such as
      36  * &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every 
      37  * last Friday of the month&quot;. 
      38  * <P>
      39  * Cron expressions are comprised of 6 required fields and one optional field
      40  * separated by white space. The fields respectively are described as follows:
      41  * 
      42  * <table cellspacing="8">
      43  * <tr>
      44  * <th align="left">Field Name</th>
      45  * <th align="left">&nbsp;</th>
      46  * <th align="left">Allowed Values</th>
      47  * <th align="left">&nbsp;</th>
      48  * <th align="left">Allowed Special Characters</th>
      49  * </tr>
      50  * <tr>
      51  * <td align="left"><code>Seconds</code></td>
      52  * <td align="left">&nbsp;</th>
      53  * <td align="left"><code>0-59</code></td>
      54  * <td align="left">&nbsp;</th>
      55  * <td align="left"><code>, - * /</code></td>
      56  * </tr>
      57  * <tr>
      58  * <td align="left"><code>Minutes</code></td>
      59  * <td align="left">&nbsp;</th>
      60  * <td align="left"><code>0-59</code></td>
      61  * <td align="left">&nbsp;</th>
      62  * <td align="left"><code>, - * /</code></td>
      63  * </tr>
      64  * <tr>
      65  * <td align="left"><code>Hours</code></td>
      66  * <td align="left">&nbsp;</th>
      67  * <td align="left"><code>0-23</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-month</code></td>
      73  * <td align="left">&nbsp;</th>
      74  * <td align="left"><code>1-31</code></td>
      75  * <td align="left">&nbsp;</th>
      76  * <td align="left"><code>, - * ? / L W</code></td>
      77  * </tr>
      78  * <tr>
      79  * <td align="left"><code>Month</code></td>
      80  * <td align="left">&nbsp;</th>
      81  * <td align="left"><code>1-12 or JAN-DEC</code></td>
      82  * <td align="left">&nbsp;</th>
      83  * <td align="left"><code>, - * /</code></td>
      84  * </tr>
      85  * <tr>
      86  * <td align="left"><code>Day-of-Week</code></td>
      87  * <td align="left">&nbsp;</th>
      88  * <td align="left"><code>1-7 or SUN-SAT</code></td>
      89  * <td align="left">&nbsp;</th>
      90  * <td align="left"><code>, - * ? / L #</code></td>
      91  * </tr>
      92  * <tr>
      93  * <td align="left"><code>Year (Optional)</code></td>
      94  * <td align="left">&nbsp;</th>
      95  * <td align="left"><code>empty, 1970-2199</code></td>
      96  * <td align="left">&nbsp;</th>
      97  * <td align="left"><code>, - * /</code></td>
      98  * </tr>
      99  * </table>
     100  * <P>
     101  * The '*' character is used to specify all values. For example, &quot;*&quot; 
     102  * in the minute field means &quot;every minute&quot;.
     103  * <P>
     104  * The '?' character is allowed for the day-of-month and day-of-week fields. It
     105  * is used to specify 'no specific value'. This is useful when you need to
     106  * specify something in one of the two fields, but not the other.
     107  * <P>
     108  * The '-' character is used to specify ranges For example &quot;10-12&quot; in
     109  * the hour field means &quot;the hours 10, 11 and 12&quot;.
     110  * <P>
     111  * The ',' character is used to specify additional values. For example
     112  * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
     113  * Wednesday, and Friday&quot;.
     114  * <P>
     115  * The '/' character is used to specify increments. For example &quot;0/15&quot;
     116  * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And 
     117  * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
     118  * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
     119  * the value to start with. Essentially, for each field in the expression, there
     120  * is a set of numbers that can be turned on or off. For seconds and minutes, 
     121  * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
     122  * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
     123  * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
     124  * month field only turns on month &quot;7&quot;, it does NOT mean every 6th 
     125  * month, please note that subtlety.  
     126  * <P>
     127  * The 'L' character is allowed for the day-of-month and day-of-week fields.
     128  * This character is short-hand for &quot;last&quot;, but it has different 
     129  * meaning in each of the two fields. For example, the value &quot;L&quot; in 
     130  * the day-of-month field means &quot;the last day of the month&quot; - day 31 
     131  * for January, day 28 for February on non-leap years. If used in the 
     132  * day-of-week field by itself, it simply means &quot;7&quot; or 
     133  * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
     134  * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
     135  * means &quot;the last friday of the month&quot;. You can also specify an offset 
     136  * from the last day of the month, such as "L-3" which would mean the third-to-last 
     137  * day of the calendar month. <i>When using the 'L' option, it is important not to 
     138  * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i>
     139  * <P>
     140  * The 'W' character is allowed for the day-of-month field.  This character 
     141  * is used to specify the weekday (Monday-Friday) nearest the given day.  As an 
     142  * example, if you were to specify &quot;15W&quot; as the value for the 
     143  * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
     144  * the month&quot;. So if the 15th is a Saturday, the trigger will fire on 
     145  * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
     146  * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. 
     147  * However if you specify &quot;1W&quot; as the value for day-of-month, and the
     148  * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 
     149  * 'jump' over the boundary of a month's days.  The 'W' character can only be 
     150  * specified when the day-of-month is a single day, not a range or list of days.
     151  * <P>
     152  * The 'L' and 'W' characters can also be combined for the day-of-month 
     153  * expression to yield 'LW', which translates to &quot;last weekday of the 
     154  * month&quot;.
     155  * <P>
     156  * The '#' character is allowed for the day-of-week field. This character is
     157  * used to specify &quot;the nth&quot; XXX day of the month. For example, the 
     158  * value of &quot;6#3&quot; in the day-of-week field means the third Friday of 
     159  * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month). 
     160  * Other examples: &quot;2#1&quot; = the first Monday of the month and 
     161  * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
     162  * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
     163  * no firing will occur that month.  If the '#' character is used, there can
     164  * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is 
     165  * not valid, since there are two expressions).
     166  * <P>
     167  * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
     168  * This character is short-hand for "calendar". This means values are
     169  * calculated against the associated calendar, if any. If no calendar is
     170  * associated, then it is equivalent to having an all-inclusive calendar. A
     171  * value of "5C" in the day-of-month field means "the first day included by the
     172  * calendar on or after the 5th". A value of "1C" in the day-of-week field
     173  * means "the first day included by the calendar on or after Sunday".-->
     174  * <P>
     175  * The legal characters and the names of months and days of the week are not
     176  * case sensitive.
     177  * 
     178  * <p>
     179  * <b>NOTES:</b>
     180  * <ul>
     181  * <li>Support for specifying both a day-of-week and a day-of-month value is
     182  * not complete (you'll need to use the '?' character in one of these fields).
     183  * </li>
     184  * <li>Overflowing ranges is supported - that is, having a larger number on 
     185  * the left hand side than the right. You might do 22-2 to catch 10 o'clock 
     186  * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is 
     187  * very important to note that overuse of overflowing ranges creates ranges 
     188  * that don't make sense and no effort has been made to determine which 
     189  * interpretation CronExpression chooses. An example would be 
     190  * "0 0 14-6 ? * FRI-MON". </li>
     191  * </ul>
     192  * </p>
     193  * 
     194  * 
     195  * @author Sharada Jambula, James House
     196  * @author Contributions from Mads Henderson
     197  * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
     198  */
     199 public final class CronExpression implements Serializable, Cloneable {
     200 
     201     private static final long serialVersionUID = 12423409423L;
     202     
     203     protected static final int SECOND = 0;
     204     protected static final int MINUTE = 1;
     205     protected static final int HOUR = 2;
     206     protected static final int DAY_OF_MONTH = 3;
     207     protected static final int MONTH = 4;
     208     protected static final int DAY_OF_WEEK = 5;
     209     protected static final int YEAR = 6;
     210     protected static final int ALL_SPEC_INT = 99; // '*'
     211     protected static final int NO_SPEC_INT = 98; // '?'
     212     protected static final Integer ALL_SPEC = ALL_SPEC_INT;
     213     protected static final Integer NO_SPEC = NO_SPEC_INT;
     214     
     215     protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
     216     protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
     217     static {
     218         monthMap.put("JAN", 0);
     219         monthMap.put("FEB", 1);
     220         monthMap.put("MAR", 2);
     221         monthMap.put("APR", 3);
     222         monthMap.put("MAY", 4);
     223         monthMap.put("JUN", 5);
     224         monthMap.put("JUL", 6);
     225         monthMap.put("AUG", 7);
     226         monthMap.put("SEP", 8);
     227         monthMap.put("OCT", 9);
     228         monthMap.put("NOV", 10);
     229         monthMap.put("DEC", 11);
     230 
     231         dayMap.put("SUN", 1);
     232         dayMap.put("MON", 2);
     233         dayMap.put("TUE", 3);
     234         dayMap.put("WED", 4);
     235         dayMap.put("THU", 5);
     236         dayMap.put("FRI", 6);
     237         dayMap.put("SAT", 7);
     238     }
     239 
     240     private final String cronExpression;
     241     private TimeZone timeZone = null;
     242     protected transient TreeSet<Integer> seconds;
     243     protected transient TreeSet<Integer> minutes;
     244     protected transient TreeSet<Integer> hours;
     245     protected transient TreeSet<Integer> daysOfMonth;
     246     protected transient TreeSet<Integer> months;
     247     protected transient TreeSet<Integer> daysOfWeek;
     248     protected transient TreeSet<Integer> years;
     249 
     250     protected transient boolean lastdayOfWeek = false;
     251     protected transient int nthdayOfWeek = 0;
     252     protected transient boolean lastdayOfMonth = false;
     253     protected transient boolean nearestWeekday = false;
     254     protected transient int lastdayOffset = 0;
     255     protected transient boolean expressionParsed = false;
     256     
     257     public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
     258 
     259     /**
     260      * Constructs a new <CODE>CronExpression</CODE> based on the specified 
     261      * parameter.
     262      * 
     263      * @param cronExpression String representation of the cron expression the
     264      *                       new object should represent
     265      * @throws java.text.ParseException
     266      *         if the string expression cannot be parsed into a valid 
     267      *         <CODE>CronExpression</CODE>
     268      */
     269     public CronExpression(String cronExpression) throws ParseException {
     270         if (cronExpression == null) {
     271             throw new IllegalArgumentException("cronExpression cannot be null");
     272         }
     273         
     274         this.cronExpression = cronExpression.toUpperCase(Locale.US);
     275         
     276         buildExpression(this.cronExpression);
     277     }
     278     
     279     /**
     280      * Constructs a new {@code CronExpression} as a copy of an existing
     281      * instance.
     282      * 
     283      * @param expression
     284      *            The existing cron expression to be copied
     285      */
     286     public CronExpression(CronExpression expression) {
     287         /*
     288          * We don't call the other constructor here since we need to swallow the
     289          * ParseException. We also elide some of the sanity checking as it is
     290          * not logically trippable.
     291          */
     292         this.cronExpression = expression.getCronExpression();
     293         try {
     294             buildExpression(cronExpression);
     295         } catch (ParseException ex) {
     296             throw new AssertionError();
     297         }
     298         if (expression.getTimeZone() != null) {
     299             setTimeZone((TimeZone) expression.getTimeZone().clone());
     300         }
     301     }
     302 
     303     /**
     304      * Indicates whether the given date satisfies the cron expression. Note that
     305      * milliseconds are ignored, so two Dates falling on different milliseconds
     306      * of the same second will always have the same result here.
     307      * 
     308      * @param date the date to evaluate
     309      * @return a boolean indicating whether the given date satisfies the cron
     310      *         expression
     311      */
     312     public boolean isSatisfiedBy(Date date) {
     313         Calendar testDateCal = Calendar.getInstance(getTimeZone());
     314         testDateCal.setTime(date);
     315         testDateCal.set(Calendar.MILLISECOND, 0);
     316         Date originalDate = testDateCal.getTime();
     317         
     318         testDateCal.add(Calendar.SECOND, -1);
     319         
     320         Date timeAfter = getTimeAfter(testDateCal.getTime());
     321 
     322         return ((timeAfter != null) && (timeAfter.equals(originalDate)));
     323     }
     324     
     325     /**
     326      * Returns the next date/time <I>after</I> the given date/time which
     327      * satisfies the cron expression.
     328      * 
     329      * @param date the date/time at which to begin the search for the next valid
     330      *             date/time
     331      * @return the next valid date/time
     332      */
     333     public Date getNextValidTimeAfter(Date date) {
     334         return getTimeAfter(date);
     335     }
     336     
     337     /**
     338      * Returns the next date/time <I>after</I> the given date/time which does
     339      * <I>not</I> satisfy the expression
     340      * 
     341      * @param date the date/time at which to begin the search for the next 
     342      *             invalid date/time
     343      * @return the next valid date/time
     344      */
     345     public Date getNextInvalidTimeAfter(Date date) {
     346         long difference = 1000;
     347         
     348         //move back to the nearest second so differences will be accurate
     349         Calendar adjustCal = Calendar.getInstance(getTimeZone());
     350         adjustCal.setTime(date);
     351         adjustCal.set(Calendar.MILLISECOND, 0);
     352         Date lastDate = adjustCal.getTime();
     353         
     354         Date newDate;
     355         
     356         //FUTURE_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.
     357         
     358         //keep getting the next included time until it's farther than one second
     359         // apart. At that point, lastDate is the last valid fire time. We return
     360         // the second immediately following it.
     361         while (difference == 1000) {
     362             newDate = getTimeAfter(lastDate);
     363             if(newDate == null)
     364                 break;
     365             
     366             difference = newDate.getTime() - lastDate.getTime();
     367             
     368             if (difference == 1000) {
     369                 lastDate = newDate;
     370             }
     371         }
     372         
     373         return new Date(lastDate.getTime() + 1000);
     374     }
     375     
     376     /**
     377      * Returns the time zone for which this <code>CronExpression</code> 
     378      * will be resolved.
     379      */
     380     public TimeZone getTimeZone() {
     381         if (timeZone == null) {
     382             timeZone = TimeZone.getDefault();
     383         }
     384 
     385         return timeZone;
     386     }
     387 
     388     /**
     389      * Sets the time zone for which  this <code>CronExpression</code> 
     390      * will be resolved.
     391      */
     392     public void setTimeZone(TimeZone timeZone) {
     393         this.timeZone = timeZone;
     394     }
     395     
     396     /**
     397      * Returns the string representation of the <CODE>CronExpression</CODE>
     398      * 
     399      * @return a string representation of the <CODE>CronExpression</CODE>
     400      */
     401     @Override
     402     public String toString() {
     403         return cronExpression;
     404     }
     405 
     406     /**
     407      * Indicates whether the specified cron expression can be parsed into a 
     408      * valid cron expression
     409      * 
     410      * @param cronExpression the expression to evaluate
     411      * @return a boolean indicating whether the given expression is a valid cron
     412      *         expression
     413      */
     414     public static boolean isValidExpression(String cronExpression) {
     415         
     416         try {
     417             new CronExpression(cronExpression);
     418         } catch (ParseException pe) {
     419             return false;
     420         }
     421         
     422         return true;
     423     }
     424 
     425     public static void validateExpression(String cronExpression) throws ParseException {
     426         
     427         new CronExpression(cronExpression);
     428     }
     429     
     430     
     431     ////////////////////////////////////////////////////////////////////////////
     432     //
     433     // Expression Parsing Functions
     434     //
     435     ////////////////////////////////////////////////////////////////////////////
     436 
     437     protected void buildExpression(String expression) throws ParseException {
     438         expressionParsed = true;
     439 
     440         try {
     441 
     442             if (seconds == null) {
     443                 seconds = new TreeSet<Integer>();
     444             }
     445             if (minutes == null) {
     446                 minutes = new TreeSet<Integer>();
     447             }
     448             if (hours == null) {
     449                 hours = new TreeSet<Integer>();
     450             }
     451             if (daysOfMonth == null) {
     452                 daysOfMonth = new TreeSet<Integer>();
     453             }
     454             if (months == null) {
     455                 months = new TreeSet<Integer>();
     456             }
     457             if (daysOfWeek == null) {
     458                 daysOfWeek = new TreeSet<Integer>();
     459             }
     460             if (years == null) {
     461                 years = new TreeSet<Integer>();
     462             }
     463 
     464             int exprOn = SECOND;
     465 
     466             StringTokenizer exprsTok = new StringTokenizer(expression, " 	",
     467                     false);
     468 
     469             while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
     470                 String expr = exprsTok.nextToken().trim();
     471 
     472                 // throw an exception if L is used with other days of the month
     473                 if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
     474                     throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
     475                 }
     476                 // throw an exception if L is used with other days of the week
     477                 if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1  && expr.contains(",")) {
     478                     throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
     479                 }
     480                 if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
     481                     throw new ParseException("Support for specifying multiple "nth" days is not implemented.", -1);
     482                 }
     483                 
     484                 StringTokenizer vTok = new StringTokenizer(expr, ",");
     485                 while (vTok.hasMoreTokens()) {
     486                     String v = vTok.nextToken();
     487                     storeExpressionVals(0, v, exprOn);
     488                 }
     489 
     490                 exprOn++;
     491             }
     492 
     493             if (exprOn <= DAY_OF_WEEK) {
     494                 throw new ParseException("Unexpected end of expression.",
     495                             expression.length());
     496             }
     497 
     498             if (exprOn <= YEAR) {
     499                 storeExpressionVals(0, "*", YEAR);
     500             }
     501 
     502             TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
     503             TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
     504 
     505             // Copying the logic from the UnsupportedOperationException below
     506             boolean dayOfMSpec = !dom.contains(NO_SPEC);
     507             boolean dayOfWSpec = !dow.contains(NO_SPEC);
     508 
     509             if (!dayOfMSpec || dayOfWSpec) {
     510                 if (!dayOfWSpec || dayOfMSpec) {
     511                     throw new ParseException(
     512                             "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
     513                 }
     514             }
     515         } catch (ParseException pe) {
     516             throw pe;
     517         } catch (Exception e) {
     518             throw new ParseException("Illegal cron expression format ("
     519                     + e.toString() + ")", 0);
     520         }
     521     }
     522 
     523     protected int storeExpressionVals(int pos, String s, int type)
     524         throws ParseException {
     525 
     526         int incr = 0;
     527         int i = skipWhiteSpace(pos, s);
     528         if (i >= s.length()) {
     529             return i;
     530         }
     531         char c = s.charAt(i);
     532         if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
     533             String sub = s.substring(i, i + 3);
     534             int sval = -1;
     535             int eval = -1;
     536             if (type == MONTH) {
     537                 sval = getMonthNumber(sub) + 1;
     538                 if (sval <= 0) {
     539                     throw new ParseException("Invalid Month value: '" + sub + "'", i);
     540                 }
     541                 if (s.length() > i + 3) {
     542                     c = s.charAt(i + 3);
     543                     if (c == '-') {
     544                         i += 4;
     545                         sub = s.substring(i, i + 3);
     546                         eval = getMonthNumber(sub) + 1;
     547                         if (eval <= 0) {
     548                             throw new ParseException("Invalid Month value: '" + sub + "'", i);
     549                         }
     550                     }
     551                 }
     552             } else if (type == DAY_OF_WEEK) {
     553                 sval = getDayOfWeekNumber(sub);
     554                 if (sval < 0) {
     555                     throw new ParseException("Invalid Day-of-Week value: '"
     556                                 + sub + "'", i);
     557                 }
     558                 if (s.length() > i + 3) {
     559                     c = s.charAt(i + 3);
     560                     if (c == '-') {
     561                         i += 4;
     562                         sub = s.substring(i, i + 3);
     563                         eval = getDayOfWeekNumber(sub);
     564                         if (eval < 0) {
     565                             throw new ParseException(
     566                                     "Invalid Day-of-Week value: '" + sub
     567                                         + "'", i);
     568                         }
     569                     } else if (c == '#') {
     570                         try {
     571                             i += 4;
     572                             nthdayOfWeek = Integer.parseInt(s.substring(i));
     573                             if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
     574                                 throw new Exception();
     575                             }
     576                         } catch (Exception e) {
     577                             throw new ParseException(
     578                                     "A numeric value between 1 and 5 must follow the '#' option",
     579                                     i);
     580                         }
     581                     } else if (c == 'L') {
     582                         lastdayOfWeek = true;
     583                         i++;
     584                     }
     585                 }
     586 
     587             } else {
     588                 throw new ParseException(
     589                         "Illegal characters for this position: '" + sub + "'",
     590                         i);
     591             }
     592             if (eval != -1) {
     593                 incr = 1;
     594             }
     595             addToSet(sval, eval, incr, type);
     596             return (i + 3);
     597         }
     598 
     599         if (c == '?') {
     600             i++;
     601             if ((i + 1) < s.length() 
     602                     && (s.charAt(i) != ' ' && s.charAt(i + 1) != '	')) {
     603                 throw new ParseException("Illegal character after '?': "
     604                             + s.charAt(i), i);
     605             }
     606             if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
     607                 throw new ParseException(
     608                             "'?' can only be specfied for Day-of-Month or Day-of-Week.",
     609                             i);
     610             }
     611             if (type == DAY_OF_WEEK && !lastdayOfMonth) {
     612                 int val = daysOfMonth.last();
     613                 if (val == NO_SPEC_INT) {
     614                     throw new ParseException(
     615                                 "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
     616                                 i);
     617                 }
     618             }
     619 
     620             addToSet(NO_SPEC_INT, -1, 0, type);
     621             return i;
     622         }
     623 
     624         if (c == '*' || c == '/') {
     625             if (c == '*' && (i + 1) >= s.length()) {
     626                 addToSet(ALL_SPEC_INT, -1, incr, type);
     627                 return i + 1;
     628             } else if (c == '/'
     629                     && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
     630                             .charAt(i + 1) == '	')) { 
     631                 throw new ParseException("'/' must be followed by an integer.", i);
     632             } else if (c == '*') {
     633                 i++;
     634             }
     635             c = s.charAt(i);
     636             if (c == '/') { // is an increment specified?
     637                 i++;
     638                 if (i >= s.length()) {
     639                     throw new ParseException("Unexpected end of string.", i);
     640                 }
     641 
     642                 incr = getNumericValue(s, i);
     643 
     644                 i++;
     645                 if (incr > 10) {
     646                     i++;
     647                 }
     648                 if (incr > 59 && (type == SECOND || type == MINUTE)) {
     649                     throw new ParseException("Increment > 60 : " + incr, i);
     650                 } else if (incr > 23 && (type == HOUR)) { 
     651                     throw new ParseException("Increment > 24 : " + incr, i);
     652                 } else if (incr > 31 && (type == DAY_OF_MONTH)) { 
     653                     throw new ParseException("Increment > 31 : " + incr, i);
     654                 } else if (incr > 7 && (type == DAY_OF_WEEK)) { 
     655                     throw new ParseException("Increment > 7 : " + incr, i);
     656                 } else if (incr > 12 && (type == MONTH)) {
     657                     throw new ParseException("Increment > 12 : " + incr, i);
     658                 }
     659             } else {
     660                 incr = 1;
     661             }
     662 
     663             addToSet(ALL_SPEC_INT, -1, incr, type);
     664             return i;
     665         } else if (c == 'L') {
     666             i++;
     667             if (type == DAY_OF_MONTH) {
     668                 lastdayOfMonth = true;
     669             }
     670             if (type == DAY_OF_WEEK) {
     671                 addToSet(7, 7, 0, type);
     672             }
     673             if(type == DAY_OF_MONTH && s.length() > i) {
     674                 c = s.charAt(i);
     675                 if(c == '-') {
     676                     ValueSet vs = getValue(0, s, i+1);
     677                     lastdayOffset = vs.value;
     678                     if(lastdayOffset > 30)
     679                         throw new ParseException("Offset from last day must be <= 30", i+1);
     680                     i = vs.pos;
     681                 }                        
     682                 if(s.length() > i) {
     683                     c = s.charAt(i);
     684                     if(c == 'W') {
     685                         nearestWeekday = true;
     686                         i++;
     687                     }
     688                 }
     689             }
     690             return i;
     691         } else if (c >= '0' && c <= '9') {
     692             int val = Integer.parseInt(String.valueOf(c));
     693             i++;
     694             if (i >= s.length()) {
     695                 addToSet(val, -1, -1, type);
     696             } else {
     697                 c = s.charAt(i);
     698                 if (c >= '0' && c <= '9') {
     699                     ValueSet vs = getValue(val, s, i);
     700                     val = vs.value;
     701                     i = vs.pos;
     702                 }
     703                 i = checkNext(i, s, val, type);
     704                 return i;
     705             }
     706         } else {
     707             throw new ParseException("Unexpected character: " + c, i);
     708         }
     709 
     710         return i;
     711     }
     712 
     713     protected int checkNext(int pos, String s, int val, int type)
     714         throws ParseException {
     715         
     716         int end = -1;
     717         int i = pos;
     718 
     719         if (i >= s.length()) {
     720             addToSet(val, end, -1, type);
     721             return i;
     722         }
     723 
     724         char c = s.charAt(pos);
     725 
     726         if (c == 'L') {
     727             if (type == DAY_OF_WEEK) {
     728                 if(val < 1 || val > 7)
     729                     throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
     730                 lastdayOfWeek = true;
     731             } else {
     732                 throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
     733             }
     734             TreeSet<Integer> set = getSet(type);
     735             set.add(val);
     736             i++;
     737             return i;
     738         }
     739         
     740         if (c == 'W') {
     741             if (type == DAY_OF_MONTH) {
     742                 nearestWeekday = true;
     743             } else {
     744                 throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
     745             }
     746             if(val > 31)
     747                 throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); 
     748             TreeSet<Integer> set = getSet(type);
     749             set.add(val);
     750             i++;
     751             return i;
     752         }
     753 
     754         if (c == '#') {
     755             if (type != DAY_OF_WEEK) {
     756                 throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
     757             }
     758             i++;
     759             try {
     760                 nthdayOfWeek = Integer.parseInt(s.substring(i));
     761                 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
     762                     throw new Exception();
     763                 }
     764             } catch (Exception e) {
     765                 throw new ParseException(
     766                         "A numeric value between 1 and 5 must follow the '#' option",
     767                         i);
     768             }
     769 
     770             TreeSet<Integer> set = getSet(type);
     771             set.add(val);
     772             i++;
     773             return i;
     774         }
     775 
     776         if (c == '-') {
     777             i++;
     778             c = s.charAt(i);
     779             int v = Integer.parseInt(String.valueOf(c));
     780             end = v;
     781             i++;
     782             if (i >= s.length()) {
     783                 addToSet(val, end, 1, type);
     784                 return i;
     785             }
     786             c = s.charAt(i);
     787             if (c >= '0' && c <= '9') {
     788                 ValueSet vs = getValue(v, s, i);
     789                 end = vs.value;
     790                 i = vs.pos;
     791             }
     792             if (i < s.length() && ((c = s.charAt(i)) == '/')) {
     793                 i++;
     794                 c = s.charAt(i);
     795                 int v2 = Integer.parseInt(String.valueOf(c));
     796                 i++;
     797                 if (i >= s.length()) {
     798                     addToSet(val, end, v2, type);
     799                     return i;
     800                 }
     801                 c = s.charAt(i);
     802                 if (c >= '0' && c <= '9') {
     803                     ValueSet vs = getValue(v2, s, i);
     804                     int v3 = vs.value;
     805                     addToSet(val, end, v3, type);
     806                     i = vs.pos;
     807                     return i;
     808                 } else {
     809                     addToSet(val, end, v2, type);
     810                     return i;
     811                 }
     812             } else {
     813                 addToSet(val, end, 1, type);
     814                 return i;
     815             }
     816         }
     817 
     818         if (c == '/') {
     819             i++;
     820             c = s.charAt(i);
     821             int v2 = Integer.parseInt(String.valueOf(c));
     822             i++;
     823             if (i >= s.length()) {
     824                 addToSet(val, end, v2, type);
     825                 return i;
     826             }
     827             c = s.charAt(i);
     828             if (c >= '0' && c <= '9') {
     829                 ValueSet vs = getValue(v2, s, i);
     830                 int v3 = vs.value;
     831                 addToSet(val, end, v3, type);
     832                 i = vs.pos;
     833                 return i;
     834             } else {
     835                 throw new ParseException("Unexpected character '" + c + "' after '/'", i);
     836             }
     837         }
     838 
     839         addToSet(val, end, 0, type);
     840         i++;
     841         return i;
     842     }
     843 
     844     public String getCronExpression() {
     845         return cronExpression;
     846     }
     847     
     848     public String getExpressionSummary() {
     849         StringBuilder buf = new StringBuilder();
     850 
     851         buf.append("seconds: ");
     852         buf.append(getExpressionSetSummary(seconds));
     853         buf.append("
    ");
     854         buf.append("minutes: ");
     855         buf.append(getExpressionSetSummary(minutes));
     856         buf.append("
    ");
     857         buf.append("hours: ");
     858         buf.append(getExpressionSetSummary(hours));
     859         buf.append("
    ");
     860         buf.append("daysOfMonth: ");
     861         buf.append(getExpressionSetSummary(daysOfMonth));
     862         buf.append("
    ");
     863         buf.append("months: ");
     864         buf.append(getExpressionSetSummary(months));
     865         buf.append("
    ");
     866         buf.append("daysOfWeek: ");
     867         buf.append(getExpressionSetSummary(daysOfWeek));
     868         buf.append("
    ");
     869         buf.append("lastdayOfWeek: ");
     870         buf.append(lastdayOfWeek);
     871         buf.append("
    ");
     872         buf.append("nearestWeekday: ");
     873         buf.append(nearestWeekday);
     874         buf.append("
    ");
     875         buf.append("NthDayOfWeek: ");
     876         buf.append(nthdayOfWeek);
     877         buf.append("
    ");
     878         buf.append("lastdayOfMonth: ");
     879         buf.append(lastdayOfMonth);
     880         buf.append("
    ");
     881         buf.append("years: ");
     882         buf.append(getExpressionSetSummary(years));
     883         buf.append("
    ");
     884 
     885         return buf.toString();
     886     }
     887 
     888     protected String getExpressionSetSummary(java.util.Set<Integer> set) {
     889 
     890         if (set.contains(NO_SPEC)) {
     891             return "?";
     892         }
     893         if (set.contains(ALL_SPEC)) {
     894             return "*";
     895         }
     896 
     897         StringBuilder buf = new StringBuilder();
     898 
     899         Iterator<Integer> itr = set.iterator();
     900         boolean first = true;
     901         while (itr.hasNext()) {
     902             Integer iVal = itr.next();
     903             String val = iVal.toString();
     904             if (!first) {
     905                 buf.append(",");
     906             }
     907             buf.append(val);
     908             first = false;
     909         }
     910 
     911         return buf.toString();
     912     }
     913 
     914     protected String getExpressionSetSummary(java.util.ArrayList<Integer> list) {
     915 
     916         if (list.contains(NO_SPEC)) {
     917             return "?";
     918         }
     919         if (list.contains(ALL_SPEC)) {
     920             return "*";
     921         }
     922 
     923         StringBuilder buf = new StringBuilder();
     924 
     925         Iterator<Integer> itr = list.iterator();
     926         boolean first = true;
     927         while (itr.hasNext()) {
     928             Integer iVal = itr.next();
     929             String val = iVal.toString();
     930             if (!first) {
     931                 buf.append(",");
     932             }
     933             buf.append(val);
     934             first = false;
     935         }
     936 
     937         return buf.toString();
     938     }
     939 
     940     protected int skipWhiteSpace(int i, String s) {
     941         for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '	'); i++) {
     942             ;
     943         }
     944 
     945         return i;
     946     }
     947 
     948     protected int findNextWhiteSpace(int i, String s) {
     949         for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '	'); i++) {
     950             ;
     951         }
     952 
     953         return i;
     954     }
     955 
     956     protected void addToSet(int val, int end, int incr, int type)
     957         throws ParseException {
     958         
     959         TreeSet<Integer> set = getSet(type);
     960 
     961         if (type == SECOND || type == MINUTE) {
     962             if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
     963                 throw new ParseException(
     964                         "Minute and Second values must be between 0 and 59",
     965                         -1);
     966             }
     967         } else if (type == HOUR) {
     968             if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
     969                 throw new ParseException(
     970                         "Hour values must be between 0 and 23", -1);
     971             }
     972         } else if (type == DAY_OF_MONTH) {
     973             if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) 
     974                     && (val != NO_SPEC_INT)) {
     975                 throw new ParseException(
     976                         "Day of month values must be between 1 and 31", -1);
     977             }
     978         } else if (type == MONTH) {
     979             if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
     980                 throw new ParseException(
     981                         "Month values must be between 1 and 12", -1);
     982             }
     983         } else if (type == DAY_OF_WEEK) {
     984             if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
     985                     && (val != NO_SPEC_INT)) {
     986                 throw new ParseException(
     987                         "Day-of-Week values must be between 1 and 7", -1);
     988             }
     989         }
     990 
     991         if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
     992             if (val != -1) {
     993                 set.add(val);
     994             } else {
     995                 set.add(NO_SPEC);
     996             }
     997             
     998             return;
     999         }
    1000 
    1001         int startAt = val;
    1002         int stopAt = end;
    1003 
    1004         if (val == ALL_SPEC_INT && incr <= 0) {
    1005             incr = 1;
    1006             set.add(ALL_SPEC); // put in a marker, but also fill values
    1007         }
    1008 
    1009         if (type == SECOND || type == MINUTE) {
    1010             if (stopAt == -1) {
    1011                 stopAt = 59;
    1012             }
    1013             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1014                 startAt = 0;
    1015             }
    1016         } else if (type == HOUR) {
    1017             if (stopAt == -1) {
    1018                 stopAt = 23;
    1019             }
    1020             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1021                 startAt = 0;
    1022             }
    1023         } else if (type == DAY_OF_MONTH) {
    1024             if (stopAt == -1) {
    1025                 stopAt = 31;
    1026             }
    1027             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1028                 startAt = 1;
    1029             }
    1030         } else if (type == MONTH) {
    1031             if (stopAt == -1) {
    1032                 stopAt = 12;
    1033             }
    1034             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1035                 startAt = 1;
    1036             }
    1037         } else if (type == DAY_OF_WEEK) {
    1038             if (stopAt == -1) {
    1039                 stopAt = 7;
    1040             }
    1041             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1042                 startAt = 1;
    1043             }
    1044         } else if (type == YEAR) {
    1045             if (stopAt == -1) {
    1046                 stopAt = MAX_YEAR;
    1047             }
    1048             if (startAt == -1 || startAt == ALL_SPEC_INT) {
    1049                 startAt = 1970;
    1050             }
    1051         }
    1052 
    1053         // if the end of the range is before the start, then we need to overflow into 
    1054         // the next day, month etc. This is done by adding the maximum amount for that 
    1055         // type, and using modulus max to determine the value being added.
    1056         int max = -1;
    1057         if (stopAt < startAt) {
    1058             switch (type) {
    1059               case       SECOND : max = 60; break;
    1060               case       MINUTE : max = 60; break;
    1061               case         HOUR : max = 24; break;
    1062               case        MONTH : max = 12; break;
    1063               case  DAY_OF_WEEK : max = 7;  break;
    1064               case DAY_OF_MONTH : max = 31; break;
    1065               case         YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
    1066               default           : throw new IllegalArgumentException("Unexpected type encountered");
    1067             }
    1068             stopAt += max;
    1069         }
    1070 
    1071         for (int i = startAt; i <= stopAt; i += incr) {
    1072             if (max == -1) {
    1073                 // ie: there's no max to overflow over
    1074                 set.add(i);
    1075             } else {
    1076                 // take the modulus to get the real value
    1077                 int i2 = i % max;
    1078 
    1079                 // 1-indexed ranges should not include 0, and should include their max
    1080                 if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
    1081                     i2 = max;
    1082                 }
    1083 
    1084                 set.add(i2);
    1085             }
    1086         }
    1087     }
    1088 
    1089     TreeSet<Integer> getSet(int type) {
    1090         switch (type) {
    1091             case SECOND:
    1092                 return seconds;
    1093             case MINUTE:
    1094                 return minutes;
    1095             case HOUR:
    1096                 return hours;
    1097             case DAY_OF_MONTH:
    1098                 return daysOfMonth;
    1099             case MONTH:
    1100                 return months;
    1101             case DAY_OF_WEEK:
    1102                 return daysOfWeek;
    1103             case YEAR:
    1104                 return years;
    1105             default:
    1106                 return null;
    1107         }
    1108     }
    1109 
    1110     protected ValueSet getValue(int v, String s, int i) {
    1111         char c = s.charAt(i);
    1112         StringBuilder s1 = new StringBuilder(String.valueOf(v));
    1113         while (c >= '0' && c <= '9') {
    1114             s1.append(c);
    1115             i++;
    1116             if (i >= s.length()) {
    1117                 break;
    1118             }
    1119             c = s.charAt(i);
    1120         }
    1121         ValueSet val = new ValueSet();
    1122         
    1123         val.pos = (i < s.length()) ? i : i + 1;
    1124         val.value = Integer.parseInt(s1.toString());
    1125         return val;
    1126     }
    1127 
    1128     protected int getNumericValue(String s, int i) {
    1129         int endOfVal = findNextWhiteSpace(i, s);
    1130         String val = s.substring(i, endOfVal);
    1131         return Integer.parseInt(val);
    1132     }
    1133 
    1134     protected int getMonthNumber(String s) {
    1135         Integer integer = monthMap.get(s);
    1136 
    1137         if (integer == null) {
    1138             return -1;
    1139         }
    1140 
    1141         return integer;
    1142     }
    1143 
    1144     protected int getDayOfWeekNumber(String s) {
    1145         Integer integer = dayMap.get(s);
    1146 
    1147         if (integer == null) {
    1148             return -1;
    1149         }
    1150 
    1151         return integer;
    1152     }
    1153 
    1154     ////////////////////////////////////////////////////////////////////////////
    1155     //
    1156     // Computation Functions
    1157     //
    1158     ////////////////////////////////////////////////////////////////////////////
    1159 
    1160     public Date getTimeAfter(Date afterTime) {
    1161 
    1162         // Computation is based on Gregorian year only.
    1163         Calendar cl = new java.util.GregorianCalendar(getTimeZone()); 
    1164 
    1165         // move ahead one second, since we're computing the time *after* the
    1166         // given time
    1167         afterTime = new Date(afterTime.getTime() + 1000);
    1168         // CronTrigger does not deal with milliseconds
    1169         cl.setTime(afterTime);
    1170         cl.set(Calendar.MILLISECOND, 0);
    1171 
    1172         boolean gotOne = false;
    1173         // loop until we've computed the next time, or we've past the endTime
    1174         while (!gotOne) {
    1175 
    1176             //if (endTime != null && cl.getTime().after(endTime)) return null;
    1177             if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
    1178                 return null;
    1179             }
    1180 
    1181             SortedSet<Integer> st = null;
    1182             int t = 0;
    1183 
    1184             int sec = cl.get(Calendar.SECOND);
    1185             int min = cl.get(Calendar.MINUTE);
    1186 
    1187             // get second.................................................
    1188             st = seconds.tailSet(sec);
    1189             if (st != null && st.size() != 0) {
    1190                 sec = st.first();
    1191             } else {
    1192                 sec = seconds.first();
    1193                 min++;
    1194                 cl.set(Calendar.MINUTE, min);
    1195             }
    1196             cl.set(Calendar.SECOND, sec);
    1197 
    1198             min = cl.get(Calendar.MINUTE);
    1199             int hr = cl.get(Calendar.HOUR_OF_DAY);
    1200             t = -1;
    1201 
    1202             // get minute.................................................
    1203             st = minutes.tailSet(min);
    1204             if (st != null && st.size() != 0) {
    1205                 t = min;
    1206                 min = st.first();
    1207             } else {
    1208                 min = minutes.first();
    1209                 hr++;
    1210             }
    1211             if (min != t) {
    1212                 cl.set(Calendar.SECOND, 0);
    1213                 cl.set(Calendar.MINUTE, min);
    1214                 setCalendarHour(cl, hr);
    1215                 continue;
    1216             }
    1217             cl.set(Calendar.MINUTE, min);
    1218 
    1219             hr = cl.get(Calendar.HOUR_OF_DAY);
    1220             int day = cl.get(Calendar.DAY_OF_MONTH);
    1221             t = -1;
    1222 
    1223             // get hour...................................................
    1224             st = hours.tailSet(hr);
    1225             if (st != null && st.size() != 0) {
    1226                 t = hr;
    1227                 hr = st.first();
    1228             } else {
    1229                 hr = hours.first();
    1230                 day++;
    1231             }
    1232             if (hr != t) {
    1233                 cl.set(Calendar.SECOND, 0);
    1234                 cl.set(Calendar.MINUTE, 0);
    1235                 cl.set(Calendar.DAY_OF_MONTH, day);
    1236                 setCalendarHour(cl, hr);
    1237                 continue;
    1238             }
    1239             cl.set(Calendar.HOUR_OF_DAY, hr);
    1240 
    1241             day = cl.get(Calendar.DAY_OF_MONTH);
    1242             int mon = cl.get(Calendar.MONTH) + 1;
    1243             // '+ 1' because calendar is 0-based for this field, and we are
    1244             // 1-based
    1245             t = -1;
    1246             int tmon = mon;
    1247             
    1248             // get day...................................................
    1249             boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
    1250             boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
    1251             if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
    1252                 st = daysOfMonth.tailSet(day);
    1253                 if (lastdayOfMonth) {
    1254                     if(!nearestWeekday) {
    1255                         t = day;
    1256                         day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1257                         day -= lastdayOffset;
    1258                         if(t > day) {
    1259                             mon++;
    1260                             if(mon > 12) { 
    1261                                 mon = 1;
    1262                                 tmon = 3333; // ensure test of mon != tmon further below fails
    1263                                 cl.add(Calendar.YEAR, 1);
    1264                             }
    1265                             day = 1;
    1266                         }
    1267                     } else {
    1268                         t = day;
    1269                         day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1270                         day -= lastdayOffset;
    1271                         
    1272                         java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
    1273                         tcal.set(Calendar.SECOND, 0);
    1274                         tcal.set(Calendar.MINUTE, 0);
    1275                         tcal.set(Calendar.HOUR_OF_DAY, 0);
    1276                         tcal.set(Calendar.DAY_OF_MONTH, day);
    1277                         tcal.set(Calendar.MONTH, mon - 1);
    1278                         tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
    1279                         
    1280                         int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1281                         int dow = tcal.get(Calendar.DAY_OF_WEEK);
    1282 
    1283                         if(dow == Calendar.SATURDAY && day == 1) {
    1284                             day += 2;
    1285                         } else if(dow == Calendar.SATURDAY) {
    1286                             day -= 1;
    1287                         } else if(dow == Calendar.SUNDAY && day == ldom) { 
    1288                             day -= 2;
    1289                         } else if(dow == Calendar.SUNDAY) { 
    1290                             day += 1;
    1291                         }
    1292                     
    1293                         tcal.set(Calendar.SECOND, sec);
    1294                         tcal.set(Calendar.MINUTE, min);
    1295                         tcal.set(Calendar.HOUR_OF_DAY, hr);
    1296                         tcal.set(Calendar.DAY_OF_MONTH, day);
    1297                         tcal.set(Calendar.MONTH, mon - 1);
    1298                         Date nTime = tcal.getTime();
    1299                         if(nTime.before(afterTime)) {
    1300                             day = 1;
    1301                             mon++;
    1302                         }
    1303                     }
    1304                 } else if(nearestWeekday) {
    1305                     t = day;
    1306                     day = daysOfMonth.first();
    1307 
    1308                     java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
    1309                     tcal.set(Calendar.SECOND, 0);
    1310                     tcal.set(Calendar.MINUTE, 0);
    1311                     tcal.set(Calendar.HOUR_OF_DAY, 0);
    1312                     tcal.set(Calendar.DAY_OF_MONTH, day);
    1313                     tcal.set(Calendar.MONTH, mon - 1);
    1314                     tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
    1315                     
    1316                     int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1317                     int dow = tcal.get(Calendar.DAY_OF_WEEK);
    1318 
    1319                     if(dow == Calendar.SATURDAY && day == 1) {
    1320                         day += 2;
    1321                     } else if(dow == Calendar.SATURDAY) {
    1322                         day -= 1;
    1323                     } else if(dow == Calendar.SUNDAY && day == ldom) { 
    1324                         day -= 2;
    1325                     } else if(dow == Calendar.SUNDAY) { 
    1326                         day += 1;
    1327                     }
    1328                         
    1329                 
    1330                     tcal.set(Calendar.SECOND, sec);
    1331                     tcal.set(Calendar.MINUTE, min);
    1332                     tcal.set(Calendar.HOUR_OF_DAY, hr);
    1333                     tcal.set(Calendar.DAY_OF_MONTH, day);
    1334                     tcal.set(Calendar.MONTH, mon - 1);
    1335                     Date nTime = tcal.getTime();
    1336                     if(nTime.before(afterTime)) {
    1337                         day = daysOfMonth.first();
    1338                         mon++;
    1339                     }
    1340                 } else if (st != null && st.size() != 0) {
    1341                     t = day;
    1342                     day = st.first();
    1343                     // make sure we don't over-run a short month, such as february
    1344                     int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1345                     if (day > lastDay) {
    1346                         day = daysOfMonth.first();
    1347                         mon++;
    1348                     }
    1349                 } else {
    1350                     day = daysOfMonth.first();
    1351                     mon++;
    1352                 }
    1353                 
    1354                 if (day != t || mon != tmon) {
    1355                     cl.set(Calendar.SECOND, 0);
    1356                     cl.set(Calendar.MINUTE, 0);
    1357                     cl.set(Calendar.HOUR_OF_DAY, 0);
    1358                     cl.set(Calendar.DAY_OF_MONTH, day);
    1359                     cl.set(Calendar.MONTH, mon - 1);
    1360                     // '- 1' because calendar is 0-based for this field, and we
    1361                     // are 1-based
    1362                     continue;
    1363                 }
    1364             } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
    1365                 if (lastdayOfWeek) { // are we looking for the last XXX day of
    1366                     // the month?
    1367                     int dow = daysOfWeek.first(); // desired
    1368                     // d-o-w
    1369                     int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
    1370                     int daysToAdd = 0;
    1371                     if (cDow < dow) {
    1372                         daysToAdd = dow - cDow;
    1373                     }
    1374                     if (cDow > dow) {
    1375                         daysToAdd = dow + (7 - cDow);
    1376                     }
    1377 
    1378                     int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1379 
    1380                     if (day + daysToAdd > lDay) { // did we already miss the
    1381                         // last one?
    1382                         cl.set(Calendar.SECOND, 0);
    1383                         cl.set(Calendar.MINUTE, 0);
    1384                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1385                         cl.set(Calendar.DAY_OF_MONTH, 1);
    1386                         cl.set(Calendar.MONTH, mon);
    1387                         // no '- 1' here because we are promoting the month
    1388                         continue;
    1389                     }
    1390 
    1391                     // find date of last occurrence of this day in this month...
    1392                     while ((day + daysToAdd + 7) <= lDay) {
    1393                         daysToAdd += 7;
    1394                     }
    1395 
    1396                     day += daysToAdd;
    1397 
    1398                     if (daysToAdd > 0) {
    1399                         cl.set(Calendar.SECOND, 0);
    1400                         cl.set(Calendar.MINUTE, 0);
    1401                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1402                         cl.set(Calendar.DAY_OF_MONTH, day);
    1403                         cl.set(Calendar.MONTH, mon - 1);
    1404                         // '- 1' here because we are not promoting the month
    1405                         continue;
    1406                     }
    1407 
    1408                 } else if (nthdayOfWeek != 0) {
    1409                     // are we looking for the Nth XXX day in the month?
    1410                     int dow = daysOfWeek.first(); // desired
    1411                     // d-o-w
    1412                     int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
    1413                     int daysToAdd = 0;
    1414                     if (cDow < dow) {
    1415                         daysToAdd = dow - cDow;
    1416                     } else if (cDow > dow) {
    1417                         daysToAdd = dow + (7 - cDow);
    1418                     }
    1419 
    1420                     boolean dayShifted = false;
    1421                     if (daysToAdd > 0) {
    1422                         dayShifted = true;
    1423                     }
    1424 
    1425                     day += daysToAdd;
    1426                     int weekOfMonth = day / 7;
    1427                     if (day % 7 > 0) {
    1428                         weekOfMonth++;
    1429                     }
    1430 
    1431                     daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
    1432                     day += daysToAdd;
    1433                     if (daysToAdd < 0
    1434                             || day > getLastDayOfMonth(mon, cl
    1435                                     .get(Calendar.YEAR))) {
    1436                         cl.set(Calendar.SECOND, 0);
    1437                         cl.set(Calendar.MINUTE, 0);
    1438                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1439                         cl.set(Calendar.DAY_OF_MONTH, 1);
    1440                         cl.set(Calendar.MONTH, mon);
    1441                         // no '- 1' here because we are promoting the month
    1442                         continue;
    1443                     } else if (daysToAdd > 0 || dayShifted) {
    1444                         cl.set(Calendar.SECOND, 0);
    1445                         cl.set(Calendar.MINUTE, 0);
    1446                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1447                         cl.set(Calendar.DAY_OF_MONTH, day);
    1448                         cl.set(Calendar.MONTH, mon - 1);
    1449                         // '- 1' here because we are NOT promoting the month
    1450                         continue;
    1451                     }
    1452                 } else {
    1453                     int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
    1454                     int dow = daysOfWeek.first(); // desired
    1455                     // d-o-w
    1456                     st = daysOfWeek.tailSet(cDow);
    1457                     if (st != null && st.size() > 0) {
    1458                         dow = st.first();
    1459                     }
    1460 
    1461                     int daysToAdd = 0;
    1462                     if (cDow < dow) {
    1463                         daysToAdd = dow - cDow;
    1464                     }
    1465                     if (cDow > dow) {
    1466                         daysToAdd = dow + (7 - cDow);
    1467                     }
    1468 
    1469                     int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
    1470 
    1471                     if (day + daysToAdd > lDay) { // will we pass the end of
    1472                         // the month?
    1473                         cl.set(Calendar.SECOND, 0);
    1474                         cl.set(Calendar.MINUTE, 0);
    1475                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1476                         cl.set(Calendar.DAY_OF_MONTH, 1);
    1477                         cl.set(Calendar.MONTH, mon);
    1478                         // no '- 1' here because we are promoting the month
    1479                         continue;
    1480                     } else if (daysToAdd > 0) { // are we swithing days?
    1481                         cl.set(Calendar.SECOND, 0);
    1482                         cl.set(Calendar.MINUTE, 0);
    1483                         cl.set(Calendar.HOUR_OF_DAY, 0);
    1484                         cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
    1485                         cl.set(Calendar.MONTH, mon - 1);
    1486                         // '- 1' because calendar is 0-based for this field,
    1487                         // and we are 1-based
    1488                         continue;
    1489                     }
    1490                 }
    1491             } else { // dayOfWSpec && !dayOfMSpec
    1492                 throw new UnsupportedOperationException(
    1493                         "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
    1494             }
    1495             cl.set(Calendar.DAY_OF_MONTH, day);
    1496 
    1497             mon = cl.get(Calendar.MONTH) + 1;
    1498             // '+ 1' because calendar is 0-based for this field, and we are
    1499             // 1-based
    1500             int year = cl.get(Calendar.YEAR);
    1501             t = -1;
    1502 
    1503             // test for expressions that never generate a valid fire date,
    1504             // but keep looping...
    1505             if (year > MAX_YEAR) {
    1506                 return null;
    1507             }
    1508 
    1509             // get month...................................................
    1510             st = months.tailSet(mon);
    1511             if (st != null && st.size() != 0) {
    1512                 t = mon;
    1513                 mon = st.first();
    1514             } else {
    1515                 mon = months.first();
    1516                 year++;
    1517             }
    1518             if (mon != t) {
    1519                 cl.set(Calendar.SECOND, 0);
    1520                 cl.set(Calendar.MINUTE, 0);
    1521                 cl.set(Calendar.HOUR_OF_DAY, 0);
    1522                 cl.set(Calendar.DAY_OF_MONTH, 1);
    1523                 cl.set(Calendar.MONTH, mon - 1);
    1524                 // '- 1' because calendar is 0-based for this field, and we are
    1525                 // 1-based
    1526                 cl.set(Calendar.YEAR, year);
    1527                 continue;
    1528             }
    1529             cl.set(Calendar.MONTH, mon - 1);
    1530             // '- 1' because calendar is 0-based for this field, and we are
    1531             // 1-based
    1532 
    1533             year = cl.get(Calendar.YEAR);
    1534             t = -1;
    1535 
    1536             // get year...................................................
    1537             st = years.tailSet(year);
    1538             if (st != null && st.size() != 0) {
    1539                 t = year;
    1540                 year = st.first();
    1541             } else {
    1542                 return null; // ran out of years...
    1543             }
    1544 
    1545             if (year != t) {
    1546                 cl.set(Calendar.SECOND, 0);
    1547                 cl.set(Calendar.MINUTE, 0);
    1548                 cl.set(Calendar.HOUR_OF_DAY, 0);
    1549                 cl.set(Calendar.DAY_OF_MONTH, 1);
    1550                 cl.set(Calendar.MONTH, 0);
    1551                 // '- 1' because calendar is 0-based for this field, and we are
    1552                 // 1-based
    1553                 cl.set(Calendar.YEAR, year);
    1554                 continue;
    1555             }
    1556             cl.set(Calendar.YEAR, year);
    1557 
    1558             gotOne = true;
    1559         } // while( !done )
    1560 
    1561         return cl.getTime();
    1562     }
    1563 
    1564     /**
    1565      * Advance the calendar to the particular hour paying particular attention
    1566      * to daylight saving problems.
    1567      * 
    1568      * @param cal the calendar to operate on
    1569      * @param hour the hour to set
    1570      */
    1571     protected void setCalendarHour(Calendar cal, int hour) {
    1572         cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
    1573         if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
    1574             cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
    1575         }
    1576     }
    1577 
    1578     /**
    1579      * NOT YET IMPLEMENTED: Returns the time before the given time
    1580      * that the <code>CronExpression</code> matches.
    1581      */ 
    1582     public Date getTimeBefore(Date endTime) { 
    1583         // FUTURE_TODO: implement QUARTZ-423
    1584         return null;
    1585     }
    1586 
    1587     /**
    1588      * NOT YET IMPLEMENTED: Returns the final time that the 
    1589      * <code>CronExpression</code> will match.
    1590      */
    1591     public Date getFinalFireTime() {
    1592         // FUTURE_TODO: implement QUARTZ-423
    1593         return null;
    1594     }
    1595     
    1596     protected boolean isLeapYear(int year) {
    1597         return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
    1598     }
    1599 
    1600     protected int getLastDayOfMonth(int monthNum, int year) {
    1601 
    1602         switch (monthNum) {
    1603             case 1:
    1604                 return 31;
    1605             case 2:
    1606                 return (isLeapYear(year)) ? 29 : 28;
    1607             case 3:
    1608                 return 31;
    1609             case 4:
    1610                 return 30;
    1611             case 5:
    1612                 return 31;
    1613             case 6:
    1614                 return 30;
    1615             case 7:
    1616                 return 31;
    1617             case 8:
    1618                 return 31;
    1619             case 9:
    1620                 return 30;
    1621             case 10:
    1622                 return 31;
    1623             case 11:
    1624                 return 30;
    1625             case 12:
    1626                 return 31;
    1627             default:
    1628                 throw new IllegalArgumentException("Illegal month number: "
    1629                         + monthNum);
    1630         }
    1631     }
    1632     
    1633 
    1634     private void readObject(java.io.ObjectInputStream stream)
    1635         throws java.io.IOException, ClassNotFoundException {
    1636         
    1637         stream.defaultReadObject();
    1638         try {
    1639             buildExpression(cronExpression);
    1640         } catch (Exception ignore) {
    1641         } // never happens
    1642     }    
    1643     
    1644     @Override
    1645     @Deprecated
    1646     public Object clone() {
    1647         return new CronExpression(this);
    1648     }
    1649 }
    1650 
    1651 class ValueSet {
    1652     public int value;
    1653 
    1654     public int pos;
    1655 }
    View Code
  • 相关阅读:
    快速幂模板
    洛谷题解 P1596 [USACO10OCT]Lake Counting S
    洛谷题解 P1460 [USACO2.1]健康的荷斯坦奶牛 Healthy Holsteins
    洛谷题解 P1305 新二叉树
    洛谷题解 P1075 质因数分解
    洛谷题解 P1331 海战
    洛谷题解 P1506 拯救oibh总部
    【洛谷P4831】Scarlet loves WenHuaKe
    【洛谷P6499】Burza
    【CF757F】Team Rocket Rises Again
  • 原文地址:https://www.cnblogs.com/jedjia/p/CronExpression.html
Copyright © 2020-2023  润新知