PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/src/play-java/src/main/java/play/libs/Time.java

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