PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

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

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