PageRenderTime 57ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/src/play/libs/CronExpression.java

http://github.com/playframework/play
Java | 1483 lines | 1039 code | 151 blank | 293 comment | 443 complexity | 94d5b856a2808c46563190bf5f9c812b MD5 | raw file
Possible License(s): Apache-2.0

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

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

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