定时器CronExpression配置说明详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了定时器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, " \t",
 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) != ‘\t‘)) {
 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) == ‘\t‘)) { 
 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("\n");
 854         buf.append("minutes: ");
 855         buf.append(getExpressionSetSummary(minutes));
 856         buf.append("\n");
 857         buf.append("hours: ");
 858         buf.append(getExpressionSetSummary(hours));
 859         buf.append("\n");
 860         buf.append("daysOfMonth: ");
 861         buf.append(getExpressionSetSummary(daysOfMonth));
 862         buf.append("\n");
 863         buf.append("months: ");
 864         buf.append(getExpressionSetSummary(months));
 865         buf.append("\n");
 866         buf.append("daysOfWeek: ");
 867         buf.append(getExpressionSetSummary(daysOfWeek));
 868         buf.append("\n");
 869         buf.append("lastdayOfWeek: ");
 870         buf.append(lastdayOfWeek);
 871         buf.append("\n");
 872         buf.append("nearestWeekday: ");
 873         buf.append(nearestWeekday);
 874         buf.append("\n");
 875         buf.append("NthDayOfWeek: ");
 876         buf.append(nthdayOfWeek);
 877         buf.append("\n");
 878         buf.append("lastdayOfMonth: ");
 879         buf.append(lastdayOfMonth);
 880         buf.append("\n");
 881         buf.append("years: ");
 882         buf.append(getExpressionSetSummary(years));
 883         buf.append("\n");
 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) == ‘\t‘); 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) != ‘\t‘); 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

 

以上是关于定时器CronExpression配置说明详解的主要内容,如果未能解决你的问题,请参考以下文章

Quartz定时调度CronExpression配置格式说明与实例

Quartz定时调度CronExpression配置格式说明与实例

Spring--quartz中cronExpression配置说明

怎样动态的配置job和cronExpression?应用quartz实现定时任务

Spring中任务调度cronExpression配置说明

CronExpression 表达式详解