PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/pvm/src/test/java/org/jbpm/pvm/internal/jobexecutor/cron/CronExpression.java

https://bitbucket.org/visionest/jbpm4
Java | 1514 lines | 1072 code | 157 blank | 285 comment | 444 complexity | 9110d55c10ce6d1f0c7a6161ff7201c6 MD5 | raw file
Possible License(s): LGPL-2.1

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

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

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