1. /* ====================================================================
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowledgement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowledgement may appear in the software itself,
  24. * if and wherever such third-party acknowledgements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Software Foundation.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.commons.lang.time;
  55. import java.util.Calendar;
  56. import java.util.Date;
  57. import java.util.GregorianCalendar;
  58. import java.util.Iterator;
  59. import java.util.NoSuchElementException;
  60. import java.util.TimeZone;
  61. /**
  62. * <p>A suite of utilities surrounding the use of the
  63. * {@link java.util.Calendar} and {@link java.util.Date} object.</p>
  64. *
  65. * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
  66. * @author Stephen Colebourne
  67. * @author Janek Bogucki
  68. * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
  69. * @since 2.0
  70. * @version $Id: DateUtils.java,v 1.16 2003/08/18 21:52:39 ggregory Exp $
  71. */
  72. public class DateUtils {
  73. /**
  74. * The UTC time zone (often referred to as GMT).
  75. */
  76. public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT");
  77. /**
  78. * Number of milliseconds in a standard second.
  79. */
  80. public static final int MILLIS_IN_SECOND = 1000;
  81. /**
  82. * Number of milliseconds in a standard minute.
  83. */
  84. public static final int MILLIS_IN_MINUTE = 60 * 1000;
  85. /**
  86. * Number of milliseconds in a standard hour.
  87. */
  88. public static final int MILLIS_IN_HOUR = 60 * 60 * 1000;
  89. /**
  90. * Number of milliseconds in a standard day.
  91. */
  92. public static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
  93. /**
  94. * This is half a month, so this represents whether a date is in the top
  95. * or bottom half of the month.
  96. */
  97. public final static int SEMI_MONTH = 1001;
  98. private static final int[][] fields = {
  99. {Calendar.MILLISECOND},
  100. {Calendar.SECOND},
  101. {Calendar.MINUTE},
  102. {Calendar.HOUR_OF_DAY, Calendar.HOUR},
  103. {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */},
  104. {Calendar.MONTH, DateUtils.SEMI_MONTH},
  105. {Calendar.YEAR},
  106. {Calendar.ERA}};
  107. /**
  108. * A week range, starting on Sunday.
  109. */
  110. public final static int RANGE_WEEK_SUNDAY = 1;
  111. /**
  112. * A week range, starting on Monday.
  113. */
  114. public final static int RANGE_WEEK_MONDAY = 2;
  115. /**
  116. * A week range, starting on the day focused.
  117. */
  118. public final static int RANGE_WEEK_RELATIVE = 3;
  119. /**
  120. * A week range, centered around the day focused.
  121. */
  122. public final static int RANGE_WEEK_CENTER = 4;
  123. /**
  124. * A month range, the week starting on Sunday.
  125. */
  126. public final static int RANGE_MONTH_SUNDAY = 5;
  127. /**
  128. * A month range, the week starting on Monday.
  129. */
  130. public final static int RANGE_MONTH_MONDAY = 6;
  131. /**
  132. * <p><code>DateUtils</code> instances should NOT be constructed in
  133. * standard programming. Instead, the class should be used as
  134. * <code>DateUtils.parse(str);</code>.</p>
  135. *
  136. * <p>This constructor is public to permit tools that require a JavaBean
  137. * instance to operate.</p>
  138. */
  139. public DateUtils() {
  140. }
  141. //-----------------------------------------------------------------------
  142. /**
  143. * <p>Round this date, leaving the field specified as the most
  144. * significant field.</p>
  145. *
  146. * <p>For example, if you had the datetime of 28 Mar 2002
  147. * 13:45:01.231, if this was passed with HOUR, it would return
  148. * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
  149. * would return 1 April 2002 0:00:00.000.</p>
  150. *
  151. * @param date the date to work with
  152. * @param field the field from <code>Calendar</code>
  153. * or <code>SEMI_MONTH</code>
  154. * @return the rounded date
  155. * @throws IllegalArgumentException if the date is <code>null</code>
  156. */
  157. public static Date round(Date date, int field) {
  158. if (date == null) {
  159. throw new IllegalArgumentException("The date must not be null");
  160. }
  161. GregorianCalendar gval = new GregorianCalendar();
  162. gval.setTime(date);
  163. modify(gval, field, true);
  164. return gval.getTime();
  165. }
  166. /**
  167. * <p>Round this date, leaving the field specified as the most
  168. * significant field.</p>
  169. *
  170. * <p>For example, if you had the datetime of 28 Mar 2002
  171. * 13:45:01.231, if this was passed with HOUR, it would return
  172. * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
  173. * would return 1 April 2002 0:00:00.000.</p>
  174. *
  175. * @param date the date to work with
  176. * @param field the field from <code>Calendar</code>
  177. * or <code>SEMI_MONTH</code>
  178. * @return the rounded date (a different object)
  179. * @throws IllegalArgumentException if the date is <code>null</code>
  180. */
  181. public static Calendar round(Calendar date, int field) {
  182. if (date == null) {
  183. throw new IllegalArgumentException("The date must not be null");
  184. }
  185. Calendar rounded = (Calendar) date.clone();
  186. modify(rounded, field, true);
  187. return rounded;
  188. }
  189. /**
  190. * <p>Round this date, leaving the field specified as the most
  191. * significant field.</p>
  192. *
  193. * <p>For example, if you had the datetime of 28 Mar 2002
  194. * 13:45:01.231, if this was passed with HOUR, it would return
  195. * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
  196. * would return 1 April 2002 0:00:00.000.</p>
  197. *
  198. * @param date the date to work with, either Date or Calendar
  199. * @param field the field from <code>Calendar</code>
  200. * or <code>SEMI_MONTH</code>
  201. * @return the rounded date
  202. * @throws IllegalArgumentException if the date is <code>null</code>
  203. * @throws ClassCastException if the object type is not a <code>Date</code>
  204. * or <code>Calendar</code>
  205. */
  206. public static Date round(Object date, int field) {
  207. if (date == null) {
  208. throw new IllegalArgumentException("The date must not be null");
  209. }
  210. if (date instanceof Date) {
  211. return round((Date) date, field);
  212. } else if (date instanceof Calendar) {
  213. return round((Calendar) date, field).getTime();
  214. } else {
  215. throw new ClassCastException("Could not round " + date);
  216. }
  217. }
  218. //-----------------------------------------------------------------------
  219. /**
  220. * <p>Truncate this date, leaving the field specified as the most
  221. * significant field.</p>
  222. *
  223. * <p>For example, if you had the datetime of 28 Mar 2002
  224. * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
  225. * 2002 13:00:00.000. If this was passed with MONTH, it would
  226. * return 1 Mar 2002 0:00:00.000.</p>
  227. *
  228. * @param date the date to work with
  229. * @param field the field from <code>Calendar</code>
  230. * or <code>SEMI_MONTH</code>
  231. * @return the rounded date
  232. * @throws IllegalArgumentException if the date is <code>null</code>
  233. */
  234. public static Date truncate(Date date, int field) {
  235. if (date == null) {
  236. throw new IllegalArgumentException("The date must not be null");
  237. }
  238. GregorianCalendar gval = new GregorianCalendar();
  239. gval.setTime(date);
  240. modify(gval, field, false);
  241. return gval.getTime();
  242. }
  243. /**
  244. * <p>Truncate this date, leaving the field specified as the most
  245. * significant field.</p>
  246. *
  247. * <p>For example, if you had the datetime of 28 Mar 2002
  248. * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
  249. * 2002 13:00:00.000. If this was passed with MONTH, it would
  250. * return 1 Mar 2002 0:00:00.000.</p>
  251. *
  252. * @param date the date to work with
  253. * @param field the field from <code>Calendar</code>
  254. * or <code>SEMI_MONTH</code>
  255. * @return the rounded date (a different object)
  256. * @throws IllegalArgumentException if the date is <code>null</code>
  257. */
  258. public static Calendar truncate(Calendar date, int field) {
  259. if (date == null) {
  260. throw new IllegalArgumentException("The date must not be null");
  261. }
  262. Calendar truncated = (Calendar) date.clone();
  263. modify(truncated, field, false);
  264. return truncated;
  265. }
  266. /**
  267. * <p>Truncate this date, leaving the field specified as the most
  268. * significant field.</p>
  269. *
  270. * <p>For example, if you had the datetime of 28 Mar 2002
  271. * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
  272. * 2002 13:00:00.000. If this was passed with MONTH, it would
  273. * return 1 Mar 2002 0:00:00.000.</p>
  274. *
  275. * @param date the date to work with, either <code>Date</code>
  276. * or <code>Calendar</code>
  277. * @param field the field from <code>Calendar</code>
  278. * or <code>SEMI_MONTH</code>
  279. * @return the rounded date
  280. * @throws IllegalArgumentException if the date
  281. * is <code>null</code>
  282. * @throws ClassCastException if the object type is not a
  283. * <code>Date</code> or <code>Calendar</code>
  284. */
  285. public static Date truncate(Object date, int field) {
  286. if (date == null) {
  287. throw new IllegalArgumentException("The date must not be null");
  288. }
  289. if (date instanceof Date) {
  290. return truncate((Date) date, field);
  291. } else if (date instanceof Calendar) {
  292. return truncate((Calendar) date, field).getTime();
  293. } else {
  294. throw new ClassCastException("Could not truncate " + date);
  295. }
  296. }
  297. //-----------------------------------------------------------------------
  298. /**
  299. * <p>Internal calculation method.</p>
  300. *
  301. * @param val the calendar
  302. * @param field the field constant
  303. * @param round true to round, false to truncate
  304. */
  305. private static void modify(Calendar val, int field, boolean round) {
  306. boolean roundUp = false;
  307. for (int i = 0; i < fields.length; i++) {
  308. for (int j = 0; j < fields[i].length; j++) {
  309. if (fields[i][j] == field) {
  310. //This is our field... we stop looping
  311. if (round && roundUp) {
  312. if (field == DateUtils.SEMI_MONTH) {
  313. //This is a special case that's hard to generalize
  314. //If the date is 1, we round up to 16, otherwise
  315. // we subtract 15 days and add 1 month
  316. if (val.get(Calendar.DATE) == 1) {
  317. val.add(Calendar.DATE, 15);
  318. } else {
  319. val.add(Calendar.DATE, -15);
  320. val.add(Calendar.MONTH, 1);
  321. }
  322. } else {
  323. //We need at add one to this field since the
  324. // last number causes us to round up
  325. val.add(fields[i][0], 1);
  326. }
  327. }
  328. return;
  329. }
  330. }
  331. //We have various fields that are not easy roundings
  332. int offset = 0;
  333. boolean offsetSet = false;
  334. //These are special types of fields that require different rounding rules
  335. switch (field) {
  336. case DateUtils.SEMI_MONTH:
  337. if (fields[i][0] == Calendar.DATE) {
  338. //If we're going to drop the DATE field's value,
  339. // we want to do this our own way.
  340. //We need to subtrace 1 since the date has a minimum of 1
  341. offset = val.get(Calendar.DATE) - 1;
  342. //If we're above 15 days adjustment, that means we're in the
  343. // bottom half of the month and should stay accordingly.
  344. if (offset >= 15) {
  345. offset -= 15;
  346. }
  347. //Record whether we're in the top or bottom half of that range
  348. roundUp = offset > 7;
  349. offsetSet = true;
  350. }
  351. break;
  352. case Calendar.AM_PM:
  353. if (fields[i][0] == Calendar.HOUR) {
  354. //If we're going to drop the HOUR field's value,
  355. // we want to do this our own way.
  356. offset = val.get(Calendar.HOUR);
  357. if (offset >= 12) {
  358. offset -= 12;
  359. }
  360. roundUp = offset > 6;
  361. offsetSet = true;
  362. }
  363. break;
  364. }
  365. if (!offsetSet) {
  366. int min = val.getActualMinimum(fields[i][0]);
  367. int max = val.getActualMaximum(fields[i][0]);
  368. //Calculate the offset from the minimum allowed value
  369. offset = val.get(fields[i][0]) - min;
  370. //Set roundUp if this is more than half way between the minimum and maximum
  371. roundUp = offset > ((max - min) / 2);
  372. }
  373. //We need to remove this field
  374. val.add(fields[i][0], -offset);
  375. }
  376. throw new IllegalArgumentException("The field " + field + " is not supported");
  377. }
  378. // TODO: Decide whether this code is removed or goes into 2.1
  379. //-----------------------------------------------------------------------
  380. /*
  381. * <p>Parses a date string formatted in CVS format.</p>
  382. *
  383. * @param dateStr the date to parse
  384. * @return the parsed date
  385. * @throws IllegalArgumentException if the date cannot be parsed
  386. public static Calendar parseCVS(String dateStr) {
  387. if (dateStr == null) {
  388. throw new IllegalArgumentException("The date must not be null");
  389. }
  390. //Get the symbol names
  391. DateFormatSymbols symbols = new DateFormatSymbols(Locale.ENGLISH);
  392. //Prep the string to parse
  393. String value = dateStr.toLowerCase().trim();
  394. //Get the current date/time
  395. Calendar now = Calendar.getInstance();
  396. if (value.endsWith(" ago")) {
  397. //If this was a date that was "ago" the current time...
  398. //Strip out the ' ago' part
  399. value = value.substring(0, value.length() - 4);
  400. //Split the value and unit
  401. int start = value.indexOf(" ");
  402. if (start < 0) {
  403. throw new IllegalArgumentException("Could not find space in between value and unit");
  404. }
  405. String unit = value.substring(start + 1);
  406. value = value.substring(0, start);
  407. //We support "a week", so we need to parse the value as "a"
  408. int val = 0;
  409. if (value.equals("a") || value.equals("an")) {
  410. val = 1;
  411. } else {
  412. val = Integer.parseInt(value);
  413. }
  414. //Determine the unit
  415. if (unit.equals("milliseconds") || unit.equals("millisecond")) {
  416. now.add(Calendar.MILLISECOND, -val);
  417. } else if (unit.equals("seconds") || unit.equals("second")) {
  418. now.add(Calendar.SECOND, -val);
  419. } else if (unit.equals("minutes") || unit.equals("minute")) {
  420. now.add(Calendar.MINUTE, -val);
  421. } else if (unit.equals("hours") || unit.equals("hour")) {
  422. now.add(Calendar.HOUR, -val);
  423. } else if (unit.equals("days") || unit.equals("day")) {
  424. now.add(Calendar.DATE, -val);
  425. } else if (unit.equals("weeks") || unit.equals("week")) {
  426. now.add(Calendar.DATE, -val * 7);
  427. } else if (unit.equals("fortnights") || unit.equals("fortnight")) {
  428. now.add(Calendar.DATE, -val * 14);
  429. } else if (unit.equals("months") || unit.equals("month")) {
  430. now.add(Calendar.MONTH, -val);
  431. } else if (unit.equals("years") || unit.equals("year")) {
  432. now.add(Calendar.YEAR, -val);
  433. } else {
  434. throw new IllegalArgumentException("We do not understand that many units ago");
  435. }
  436. return now;
  437. } else if (value.startsWith("last ")) {
  438. //If this was the last time a certain field was met
  439. //Strip out the 'last ' part
  440. value = value.substring(5);
  441. //Get the current date/time
  442. String[] strings = symbols.getWeekdays();
  443. for (int i = 0; i < strings.length; i++) {
  444. if (value.equalsIgnoreCase(strings[i])) {
  445. //How many days after Sunday
  446. int daysAgo = now.get(Calendar.DAY_OF_WEEK) - i;
  447. if (daysAgo <= 0) {
  448. daysAgo += 7;
  449. }
  450. now.add(Calendar.DATE, -daysAgo);
  451. return now;
  452. }
  453. }
  454. strings = symbols.getMonths();
  455. for (int i = 0; i < strings.length; i++) {
  456. if (value.equalsIgnoreCase(strings[i])) {
  457. //How many days after January
  458. int monthsAgo = now.get(Calendar.MONTH) - i;
  459. if (monthsAgo <= 0) {
  460. monthsAgo += 12;
  461. }
  462. now.add(Calendar.MONTH, -monthsAgo);
  463. return now;
  464. }
  465. }
  466. if (value.equals("week")) {
  467. now.add(Calendar.DATE, -7);
  468. return now;
  469. }
  470. throw new IllegalArgumentException("We do not understand that last units");
  471. } else if (value.equals("yesterday")) {
  472. now.add(Calendar.DATE, -1);
  473. return now;
  474. } else if (value.equals("tomorrow")) {
  475. now.add(Calendar.DATE, 1);
  476. return now;
  477. }
  478. //Try to parse the date a number of different ways
  479. for (int i = 0; i < dateFormats.length; i++) {
  480. try {
  481. Date datetime = dateFormats[i].parse(dateStr);
  482. Calendar cal = Calendar.getInstance();
  483. cal.setTime(datetime);
  484. return cal;
  485. } catch (ParseException pe) {
  486. //we ignore this and just keep trying
  487. }
  488. }
  489. throw new IllegalArgumentException("Unable to parse '" + dateStr + "'.");
  490. }
  491. */
  492. //-----------------------------------------------------------------------
  493. /**
  494. * <p>This constructs an <code>Iterator</code> that will
  495. * start and stop over a date range based on the focused
  496. * date and the range style.</p>
  497. *
  498. * <p>For instance, passing Thursday, July 4, 2002 and a
  499. * <code>RANGE_MONTH_SUNDAY</code> will return an
  500. * <code>Iterator</code> that starts with Sunday, June 30,
  501. * 2002 and ends with Saturday, August 3, 2002.
  502. *
  503. * @param focus the date to work with
  504. * @param rangeStyle the style constant to use. Must be one of the range
  505. * styles listed for the {@link #iterator(Calendar, int)} method.
  506. *
  507. * @return the date iterator
  508. * @throws IllegalArgumentException if the date is <code>null</code> or if
  509. * the rangeStyle is not
  510. */
  511. public static Iterator iterator(Date focus, int rangeStyle) {
  512. if (focus == null) {
  513. throw new IllegalArgumentException("The date must not be null");
  514. }
  515. GregorianCalendar gval = new GregorianCalendar();
  516. gval.setTime(focus);
  517. return iterator(gval, rangeStyle);
  518. }
  519. /**
  520. * <p>This constructs an <code>Iterator</code> that will
  521. * start and stop over a date range based on the focused
  522. * date and the range style.</p>
  523. *
  524. * <p>For instance, passing Thursday, July 4, 2002 and a
  525. * <code>RANGE_MONTH_SUNDAY</code> will return an
  526. * <code>Iterator</code> that starts with Sunday, June 30,
  527. * 2002 and ends with Saturday, August 3, 2002.
  528. *
  529. * @param focus the date to work with
  530. * @param rangeStyle the style constant to use. Must be one of
  531. * {@link DateUtils#RANGE_MONTH_SUNDAY},
  532. * {@link DateUtils#RANGE_MONTH_MONDAY},
  533. * {@link DateUtils#RANGE_WEEK_SUNDAY},
  534. * {@link DateUtils#RANGE_WEEK_MONDAY},
  535. * {@link DateUtils#RANGE_WEEK_RELATIVE},
  536. * {@link DateUtils#RANGE_WEEK_CENTER}
  537. * @return the date iterator
  538. * @throws IllegalArgumentException if the date is <code>null</code>
  539. */
  540. public static Iterator iterator(Calendar focus, int rangeStyle) {
  541. if (focus == null) {
  542. throw new IllegalArgumentException("The date must not be null");
  543. }
  544. Calendar start = null;
  545. Calendar end = null;
  546. int startCutoff = Calendar.SUNDAY;
  547. int endCutoff = Calendar.SATURDAY;
  548. switch (rangeStyle) {
  549. case RANGE_MONTH_SUNDAY:
  550. case RANGE_MONTH_MONDAY:
  551. //Set start to the first of the month
  552. start = truncate(focus, Calendar.MONTH);
  553. //Set end to the last of the month
  554. end = (Calendar) start.clone();
  555. end.add(Calendar.MONTH, 1);
  556. end.add(Calendar.DATE, -1);
  557. //Loop start back to the previous sunday or monday
  558. if (rangeStyle == RANGE_MONTH_MONDAY) {
  559. startCutoff = Calendar.MONDAY;
  560. endCutoff = Calendar.SUNDAY;
  561. }
  562. break;
  563. case RANGE_WEEK_SUNDAY:
  564. case RANGE_WEEK_MONDAY:
  565. case RANGE_WEEK_RELATIVE:
  566. case RANGE_WEEK_CENTER:
  567. //Set start and end to the current date
  568. start = truncate(focus, Calendar.DATE);
  569. end = truncate(focus, Calendar.DATE);
  570. switch (rangeStyle) {
  571. case RANGE_WEEK_SUNDAY:
  572. //already set by default
  573. break;
  574. case RANGE_WEEK_MONDAY:
  575. startCutoff = Calendar.MONDAY;
  576. endCutoff = Calendar.SUNDAY;
  577. break;
  578. case RANGE_WEEK_RELATIVE:
  579. startCutoff = focus.get(Calendar.DAY_OF_WEEK);
  580. endCutoff = startCutoff - 1;
  581. break;
  582. case RANGE_WEEK_CENTER:
  583. startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
  584. endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
  585. break;
  586. }
  587. break;
  588. default:
  589. throw new IllegalArgumentException("The range style " + rangeStyle + " is not valid.");
  590. }
  591. if (startCutoff < Calendar.SUNDAY) {
  592. startCutoff += 7;
  593. }
  594. if (startCutoff > Calendar.SATURDAY) {
  595. startCutoff -= 7;
  596. }
  597. if (endCutoff < Calendar.SUNDAY) {
  598. endCutoff += 7;
  599. }
  600. if (endCutoff > Calendar.SATURDAY) {
  601. endCutoff -= 7;
  602. }
  603. while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
  604. start.add(Calendar.DATE, -1);
  605. }
  606. while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
  607. end.add(Calendar.DATE, 1);
  608. }
  609. return new DateIterator(start, end);
  610. }
  611. /**
  612. * <p>This constructs an <code>Iterator</code> that will
  613. * start and stop over a date range based on the focused
  614. * date and the range style.</p>
  615. *
  616. * <p>For instance, passing Thursday, July 4, 2002 and a
  617. * <code>RANGE_MONTH_SUNDAY</code> will return an
  618. * <code>Iterator</code> that starts with Sunday, June 30,
  619. * 2002 and ends with Saturday, August 3, 2002.</p>
  620. *
  621. * @param focus the date to work with, either
  622. * <code>Date</code> or <code>Calendar</code>
  623. * @param rangeStyle the style constant to use. Must be one of the range
  624. * styles listed for the {@link #iterator(Calendar, int)} method.
  625. * @return the date iterator
  626. * @throws IllegalArgumentException if the date
  627. * is <code>null</code>
  628. * @throws ClassCastException if the object type is
  629. * not a <code>Date</code> or <code>Calendar</code>
  630. */
  631. public static Iterator iterator(Object focus, int rangeStyle) {
  632. if (focus == null) {
  633. throw new IllegalArgumentException("The date must not be null");
  634. }
  635. if (focus instanceof Date) {
  636. return iterator((Date) focus, rangeStyle);
  637. } else if (focus instanceof Calendar) {
  638. return iterator((Calendar) focus, rangeStyle);
  639. } else {
  640. throw new ClassCastException("Could not iterate based on " + focus);
  641. }
  642. }
  643. /**
  644. * <p>Date iterator.</p>
  645. */
  646. static class DateIterator implements Iterator {
  647. private final Calendar endFinal;
  648. private final Calendar spot;
  649. DateIterator(Calendar startFinal, Calendar endFinal) {
  650. super();
  651. this.endFinal = endFinal;
  652. spot = startFinal;
  653. spot.add(Calendar.DATE, -1);
  654. }
  655. public boolean hasNext() {
  656. return spot.before(endFinal);
  657. }
  658. public Object next() {
  659. if (spot.equals(endFinal)) {
  660. throw new NoSuchElementException();
  661. }
  662. spot.add(Calendar.DATE, 1);
  663. return spot.clone();
  664. }
  665. public void remove() {
  666. throw new UnsupportedOperationException();
  667. }
  668. }
  669. }