0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.lang.time;
0018:
0019: import java.text.ParseException;
0020: import java.text.ParsePosition;
0021: import java.text.SimpleDateFormat;
0022: import java.util.Calendar;
0023: import java.util.Date;
0024: import java.util.Iterator;
0025: import java.util.NoSuchElementException;
0026: import java.util.TimeZone;
0027:
0028: /**
0029: * <p>A suite of utilities surrounding the use of the
0030: * {@link java.util.Calendar} and {@link java.util.Date} object.</p>
0031: *
0032: * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
0033: * @author Stephen Colebourne
0034: * @author Janek Bogucki
0035: * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
0036: * @author Phil Steitz
0037: * @since 2.0
0038: * @version $Id: DateUtils.java 437554 2006-08-28 06:21:41Z bayard $
0039: */
0040: public class DateUtils {
0041:
0042: /**
0043: * The UTC time zone (often referred to as GMT).
0044: */
0045: public static final TimeZone UTC_TIME_ZONE = TimeZone
0046: .getTimeZone("GMT");
0047: /**
0048: * Number of milliseconds in a standard second.
0049: * @since 2.1
0050: */
0051: public static final long MILLIS_PER_SECOND = 1000;
0052: /**
0053: * Number of milliseconds in a standard minute.
0054: * @since 2.1
0055: */
0056: public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
0057: /**
0058: * Number of milliseconds in a standard hour.
0059: * @since 2.1
0060: */
0061: public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
0062: /**
0063: * Number of milliseconds in a standard day.
0064: * @since 2.1
0065: */
0066: public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
0067:
0068: /**
0069: * This is half a month, so this represents whether a date is in the top
0070: * or bottom half of the month.
0071: */
0072: public final static int SEMI_MONTH = 1001;
0073:
0074: private static final int[][] fields = { { Calendar.MILLISECOND },
0075: { Calendar.SECOND }, { Calendar.MINUTE },
0076: { Calendar.HOUR_OF_DAY, Calendar.HOUR },
0077: { Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM
0078: /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */
0079: }, { Calendar.MONTH, DateUtils.SEMI_MONTH },
0080: { Calendar.YEAR }, { Calendar.ERA } };
0081:
0082: /**
0083: * A week range, starting on Sunday.
0084: */
0085: public final static int RANGE_WEEK_SUNDAY = 1;
0086:
0087: /**
0088: * A week range, starting on Monday.
0089: */
0090: public final static int RANGE_WEEK_MONDAY = 2;
0091:
0092: /**
0093: * A week range, starting on the day focused.
0094: */
0095: public final static int RANGE_WEEK_RELATIVE = 3;
0096:
0097: /**
0098: * A week range, centered around the day focused.
0099: */
0100: public final static int RANGE_WEEK_CENTER = 4;
0101:
0102: /**
0103: * A month range, the week starting on Sunday.
0104: */
0105: public final static int RANGE_MONTH_SUNDAY = 5;
0106:
0107: /**
0108: * A month range, the week starting on Monday.
0109: */
0110: public final static int RANGE_MONTH_MONDAY = 6;
0111:
0112: /**
0113: * <p><code>DateUtils</code> instances should NOT be constructed in
0114: * standard programming. Instead, the class should be used as
0115: * <code>DateUtils.parse(str);</code>.</p>
0116: *
0117: * <p>This constructor is public to permit tools that require a JavaBean
0118: * instance to operate.</p>
0119: */
0120: public DateUtils() {
0121: super ();
0122: }
0123:
0124: //-----------------------------------------------------------------------
0125: /**
0126: * <p>Checks if two date objects are on the same day ignoring time.</p>
0127: *
0128: * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
0129: * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
0130: * </p>
0131: *
0132: * @param date1 the first date, not altered, not null
0133: * @param date2 the second date, not altered, not null
0134: * @return true if they represent the same day
0135: * @throws IllegalArgumentException if either date is <code>null</code>
0136: * @since 2.1
0137: */
0138: public static boolean isSameDay(Date date1, Date date2) {
0139: if (date1 == null || date2 == null) {
0140: throw new IllegalArgumentException(
0141: "The date must not be null");
0142: }
0143: Calendar cal1 = Calendar.getInstance();
0144: cal1.setTime(date1);
0145: Calendar cal2 = Calendar.getInstance();
0146: cal2.setTime(date2);
0147: return isSameDay(cal1, cal2);
0148: }
0149:
0150: /**
0151: * <p>Checks if two calendar objects are on the same day ignoring time.</p>
0152: *
0153: * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
0154: * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
0155: * </p>
0156: *
0157: * @param cal1 the first calendar, not altered, not null
0158: * @param cal2 the second calendar, not altered, not null
0159: * @return true if they represent the same day
0160: * @throws IllegalArgumentException if either calendar is <code>null</code>
0161: * @since 2.1
0162: */
0163: public static boolean isSameDay(Calendar cal1, Calendar cal2) {
0164: if (cal1 == null || cal2 == null) {
0165: throw new IllegalArgumentException(
0166: "The date must not be null");
0167: }
0168: return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
0169: && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1
0170: .get(Calendar.DAY_OF_YEAR) == cal2
0171: .get(Calendar.DAY_OF_YEAR));
0172: }
0173:
0174: //-----------------------------------------------------------------------
0175: /**
0176: * <p>Checks if two date objects represent the same instant in time.</p>
0177: *
0178: * <p>This method compares the long millisecond time of the two objects.</p>
0179: *
0180: * @param date1 the first date, not altered, not null
0181: * @param date2 the second date, not altered, not null
0182: * @return true if they represent the same millisecond instant
0183: * @throws IllegalArgumentException if either date is <code>null</code>
0184: * @since 2.1
0185: */
0186: public static boolean isSameInstant(Date date1, Date date2) {
0187: if (date1 == null || date2 == null) {
0188: throw new IllegalArgumentException(
0189: "The date must not be null");
0190: }
0191: return date1.getTime() == date2.getTime();
0192: }
0193:
0194: /**
0195: * <p>Checks if two calendar objects represent the same instant in time.</p>
0196: *
0197: * <p>This method compares the long millisecond time of the two objects.</p>
0198: *
0199: * @param cal1 the first calendar, not altered, not null
0200: * @param cal2 the second calendar, not altered, not null
0201: * @return true if they represent the same millisecond instant
0202: * @throws IllegalArgumentException if either date is <code>null</code>
0203: * @since 2.1
0204: */
0205: public static boolean isSameInstant(Calendar cal1, Calendar cal2) {
0206: if (cal1 == null || cal2 == null) {
0207: throw new IllegalArgumentException(
0208: "The date must not be null");
0209: }
0210: return cal1.getTime().getTime() == cal2.getTime().getTime();
0211: }
0212:
0213: //-----------------------------------------------------------------------
0214: /**
0215: * <p>Checks if two calendar objects represent the same local time.</p>
0216: *
0217: * <p>This method compares the values of the fields of the two objects.
0218: * In addition, both calendars must be the same of the same type.</p>
0219: *
0220: * @param cal1 the first calendar, not altered, not null
0221: * @param cal2 the second calendar, not altered, not null
0222: * @return true if they represent the same millisecond instant
0223: * @throws IllegalArgumentException if either date is <code>null</code>
0224: * @since 2.1
0225: */
0226: public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) {
0227: if (cal1 == null || cal2 == null) {
0228: throw new IllegalArgumentException(
0229: "The date must not be null");
0230: }
0231: return (cal1.get(Calendar.MILLISECOND) == cal2
0232: .get(Calendar.MILLISECOND)
0233: && cal1.get(Calendar.SECOND) == cal2
0234: .get(Calendar.SECOND)
0235: && cal1.get(Calendar.MINUTE) == cal2
0236: .get(Calendar.MINUTE)
0237: && cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR)
0238: && cal1.get(Calendar.DAY_OF_YEAR) == cal2
0239: .get(Calendar.DAY_OF_YEAR)
0240: && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
0241: && cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && cal1
0242: .getClass() == cal2.getClass());
0243: }
0244:
0245: //-----------------------------------------------------------------------
0246: /**
0247: * <p>Parses a string representing a date by trying a variety of different parsers.</p>
0248: *
0249: * <p>The parse will try each parse pattern in turn.
0250: * A parse is only deemed sucessful if it parses the whole of the input string.
0251: * If no parse patterns match, a ParseException is thrown.</p>
0252: *
0253: * @param str the date to parse, not null
0254: * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
0255: * @return the parsed date
0256: * @throws IllegalArgumentException if the date string or pattern array is null
0257: * @throws ParseException if none of the date patterns were suitable
0258: */
0259: public static Date parseDate(String str, String[] parsePatterns)
0260: throws ParseException {
0261: if (str == null || parsePatterns == null) {
0262: throw new IllegalArgumentException(
0263: "Date and Patterns must not be null");
0264: }
0265:
0266: SimpleDateFormat parser = null;
0267: ParsePosition pos = new ParsePosition(0);
0268: for (int i = 0; i < parsePatterns.length; i++) {
0269: if (i == 0) {
0270: parser = new SimpleDateFormat(parsePatterns[0]);
0271: } else {
0272: parser.applyPattern(parsePatterns[i]);
0273: }
0274: pos.setIndex(0);
0275: Date date = parser.parse(str, pos);
0276: if (date != null && pos.getIndex() == str.length()) {
0277: return date;
0278: }
0279: }
0280: throw new ParseException("Unable to parse the date: " + str, -1);
0281: }
0282:
0283: //-----------------------------------------------------------------------
0284: /**
0285: * Adds a number of years to a date returning a new object.
0286: * The original date object is unchanged.
0287: *
0288: * @param date the date, not null
0289: * @param amount the amount to add, may be negative
0290: * @return the new date object with the amount added
0291: * @throws IllegalArgumentException if the date is null
0292: */
0293: public static Date addYears(Date date, int amount) {
0294: return add(date, Calendar.YEAR, amount);
0295: }
0296:
0297: //-----------------------------------------------------------------------
0298: /**
0299: * Adds a number of months to a date returning a new object.
0300: * The original date object is unchanged.
0301: *
0302: * @param date the date, not null
0303: * @param amount the amount to add, may be negative
0304: * @return the new date object with the amount added
0305: * @throws IllegalArgumentException if the date is null
0306: */
0307: public static Date addMonths(Date date, int amount) {
0308: return add(date, Calendar.MONTH, amount);
0309: }
0310:
0311: //-----------------------------------------------------------------------
0312: /**
0313: * Adds a number of weeks to a date returning a new object.
0314: * The original date object is unchanged.
0315: *
0316: * @param date the date, not null
0317: * @param amount the amount to add, may be negative
0318: * @return the new date object with the amount added
0319: * @throws IllegalArgumentException if the date is null
0320: */
0321: public static Date addWeeks(Date date, int amount) {
0322: return add(date, Calendar.WEEK_OF_YEAR, amount);
0323: }
0324:
0325: //-----------------------------------------------------------------------
0326: /**
0327: * Adds a number of days to a date returning a new object.
0328: * The original date object is unchanged.
0329: *
0330: * @param date the date, not null
0331: * @param amount the amount to add, may be negative
0332: * @return the new date object with the amount added
0333: * @throws IllegalArgumentException if the date is null
0334: */
0335: public static Date addDays(Date date, int amount) {
0336: return add(date, Calendar.DAY_OF_MONTH, amount);
0337: }
0338:
0339: //-----------------------------------------------------------------------
0340: /**
0341: * Adds a number of hours to a date returning a new object.
0342: * The original date object is unchanged.
0343: *
0344: * @param date the date, not null
0345: * @param amount the amount to add, may be negative
0346: * @return the new date object with the amount added
0347: * @throws IllegalArgumentException if the date is null
0348: */
0349: public static Date addHours(Date date, int amount) {
0350: return add(date, Calendar.HOUR_OF_DAY, amount);
0351: }
0352:
0353: //-----------------------------------------------------------------------
0354: /**
0355: * Adds a number of minutes to a date returning a new object.
0356: * The original date object is unchanged.
0357: *
0358: * @param date the date, not null
0359: * @param amount the amount to add, may be negative
0360: * @return the new date object with the amount added
0361: * @throws IllegalArgumentException if the date is null
0362: */
0363: public static Date addMinutes(Date date, int amount) {
0364: return add(date, Calendar.MINUTE, amount);
0365: }
0366:
0367: //-----------------------------------------------------------------------
0368: /**
0369: * Adds a number of seconds to a date returning a new object.
0370: * The original date object is unchanged.
0371: *
0372: * @param date the date, not null
0373: * @param amount the amount to add, may be negative
0374: * @return the new date object with the amount added
0375: * @throws IllegalArgumentException if the date is null
0376: */
0377: public static Date addSeconds(Date date, int amount) {
0378: return add(date, Calendar.SECOND, amount);
0379: }
0380:
0381: //-----------------------------------------------------------------------
0382: /**
0383: * Adds a number of milliseconds to a date returning a new object.
0384: * The original date object is unchanged.
0385: *
0386: * @param date the date, not null
0387: * @param amount the amount to add, may be negative
0388: * @return the new date object with the amount added
0389: * @throws IllegalArgumentException if the date is null
0390: */
0391: public static Date addMilliseconds(Date date, int amount) {
0392: return add(date, Calendar.MILLISECOND, amount);
0393: }
0394:
0395: //-----------------------------------------------------------------------
0396: /**
0397: * Adds to a date returning a new object.
0398: * The original date object is unchanged.
0399: *
0400: * @param date the date, not null
0401: * @param calendarField the calendar field to add to
0402: * @param amount the amount to add, may be negative
0403: * @return the new date object with the amount added
0404: * @throws IllegalArgumentException if the date is null
0405: */
0406: public static Date add(Date date, int calendarField, int amount) {
0407: if (date == null) {
0408: throw new IllegalArgumentException(
0409: "The date must not be null");
0410: }
0411: Calendar c = Calendar.getInstance();
0412: c.setTime(date);
0413: c.add(calendarField, amount);
0414: return c.getTime();
0415: }
0416:
0417: //-----------------------------------------------------------------------
0418: /**
0419: * <p>Round this date, leaving the field specified as the most
0420: * significant field.</p>
0421: *
0422: * <p>For example, if you had the datetime of 28 Mar 2002
0423: * 13:45:01.231, if this was passed with HOUR, it would return
0424: * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
0425: * would return 1 April 2002 0:00:00.000.</p>
0426: *
0427: * <p>For a date in a timezone that handles the change to daylight
0428: * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
0429: * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
0430: * date that crosses this time would produce the following values:
0431: * <ul>
0432: * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
0433: * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
0434: * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
0435: * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
0436: * </ul>
0437: * </p>
0438: *
0439: * @param date the date to work with
0440: * @param field the field from <code>Calendar</code>
0441: * or <code>SEMI_MONTH</code>
0442: * @return the rounded date
0443: * @throws IllegalArgumentException if the date is <code>null</code>
0444: * @throws ArithmeticException if the year is over 280 million
0445: */
0446: public static Date round(Date date, int field) {
0447: if (date == null) {
0448: throw new IllegalArgumentException(
0449: "The date must not be null");
0450: }
0451: Calendar gval = Calendar.getInstance();
0452: gval.setTime(date);
0453: modify(gval, field, true);
0454: return gval.getTime();
0455: }
0456:
0457: /**
0458: * <p>Round this date, leaving the field specified as the most
0459: * significant field.</p>
0460: *
0461: * <p>For example, if you had the datetime of 28 Mar 2002
0462: * 13:45:01.231, if this was passed with HOUR, it would return
0463: * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
0464: * would return 1 April 2002 0:00:00.000.</p>
0465: *
0466: * <p>For a date in a timezone that handles the change to daylight
0467: * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
0468: * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
0469: * date that crosses this time would produce the following values:
0470: * <ul>
0471: * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
0472: * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
0473: * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
0474: * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
0475: * </ul>
0476: * </p>
0477: *
0478: * @param date the date to work with
0479: * @param field the field from <code>Calendar</code>
0480: * or <code>SEMI_MONTH</code>
0481: * @return the rounded date (a different object)
0482: * @throws IllegalArgumentException if the date is <code>null</code>
0483: * @throws ArithmeticException if the year is over 280 million
0484: */
0485: public static Calendar round(Calendar date, int field) {
0486: if (date == null) {
0487: throw new IllegalArgumentException(
0488: "The date must not be null");
0489: }
0490: Calendar rounded = (Calendar) date.clone();
0491: modify(rounded, field, true);
0492: return rounded;
0493: }
0494:
0495: /**
0496: * <p>Round this date, leaving the field specified as the most
0497: * significant field.</p>
0498: *
0499: * <p>For example, if you had the datetime of 28 Mar 2002
0500: * 13:45:01.231, if this was passed with HOUR, it would return
0501: * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
0502: * would return 1 April 2002 0:00:00.000.</p>
0503: *
0504: * <p>For a date in a timezone that handles the change to daylight
0505: * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
0506: * Suppose daylight saving time begins at 02:00 on March 30. Rounding a
0507: * date that crosses this time would produce the following values:
0508: * <ul>
0509: * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
0510: * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
0511: * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
0512: * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
0513: * </ul>
0514: * </p>
0515: *
0516: * @param date the date to work with, either Date or Calendar
0517: * @param field the field from <code>Calendar</code>
0518: * or <code>SEMI_MONTH</code>
0519: * @return the rounded date
0520: * @throws IllegalArgumentException if the date is <code>null</code>
0521: * @throws ClassCastException if the object type is not a <code>Date</code>
0522: * or <code>Calendar</code>
0523: * @throws ArithmeticException if the year is over 280 million
0524: */
0525: public static Date round(Object date, int field) {
0526: if (date == null) {
0527: throw new IllegalArgumentException(
0528: "The date must not be null");
0529: }
0530: if (date instanceof Date) {
0531: return round((Date) date, field);
0532: } else if (date instanceof Calendar) {
0533: return round((Calendar) date, field).getTime();
0534: } else {
0535: throw new ClassCastException("Could not round " + date);
0536: }
0537: }
0538:
0539: //-----------------------------------------------------------------------
0540: /**
0541: * <p>Truncate this date, leaving the field specified as the most
0542: * significant field.</p>
0543: *
0544: * <p>For example, if you had the datetime of 28 Mar 2002
0545: * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
0546: * 2002 13:00:00.000. If this was passed with MONTH, it would
0547: * return 1 Mar 2002 0:00:00.000.</p>
0548: *
0549: * @param date the date to work with
0550: * @param field the field from <code>Calendar</code>
0551: * or <code>SEMI_MONTH</code>
0552: * @return the rounded date
0553: * @throws IllegalArgumentException if the date is <code>null</code>
0554: * @throws ArithmeticException if the year is over 280 million
0555: */
0556: public static Date truncate(Date date, int field) {
0557: if (date == null) {
0558: throw new IllegalArgumentException(
0559: "The date must not be null");
0560: }
0561: Calendar gval = Calendar.getInstance();
0562: gval.setTime(date);
0563: modify(gval, field, false);
0564: return gval.getTime();
0565: }
0566:
0567: /**
0568: * <p>Truncate this date, leaving the field specified as the most
0569: * significant field.</p>
0570: *
0571: * <p>For example, if you had the datetime of 28 Mar 2002
0572: * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
0573: * 2002 13:00:00.000. If this was passed with MONTH, it would
0574: * return 1 Mar 2002 0:00:00.000.</p>
0575: *
0576: * @param date the date to work with
0577: * @param field the field from <code>Calendar</code>
0578: * or <code>SEMI_MONTH</code>
0579: * @return the rounded date (a different object)
0580: * @throws IllegalArgumentException if the date is <code>null</code>
0581: * @throws ArithmeticException if the year is over 280 million
0582: */
0583: public static Calendar truncate(Calendar date, int field) {
0584: if (date == null) {
0585: throw new IllegalArgumentException(
0586: "The date must not be null");
0587: }
0588: Calendar truncated = (Calendar) date.clone();
0589: modify(truncated, field, false);
0590: return truncated;
0591: }
0592:
0593: /**
0594: * <p>Truncate this date, leaving the field specified as the most
0595: * significant field.</p>
0596: *
0597: * <p>For example, if you had the datetime of 28 Mar 2002
0598: * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
0599: * 2002 13:00:00.000. If this was passed with MONTH, it would
0600: * return 1 Mar 2002 0:00:00.000.</p>
0601: *
0602: * @param date the date to work with, either <code>Date</code>
0603: * or <code>Calendar</code>
0604: * @param field the field from <code>Calendar</code>
0605: * or <code>SEMI_MONTH</code>
0606: * @return the rounded date
0607: * @throws IllegalArgumentException if the date
0608: * is <code>null</code>
0609: * @throws ClassCastException if the object type is not a
0610: * <code>Date</code> or <code>Calendar</code>
0611: * @throws ArithmeticException if the year is over 280 million
0612: */
0613: public static Date truncate(Object date, int field) {
0614: if (date == null) {
0615: throw new IllegalArgumentException(
0616: "The date must not be null");
0617: }
0618: if (date instanceof Date) {
0619: return truncate((Date) date, field);
0620: } else if (date instanceof Calendar) {
0621: return truncate((Calendar) date, field).getTime();
0622: } else {
0623: throw new ClassCastException("Could not truncate " + date);
0624: }
0625: }
0626:
0627: //-----------------------------------------------------------------------
0628: /**
0629: * <p>Internal calculation method.</p>
0630: *
0631: * @param val the calendar
0632: * @param field the field constant
0633: * @param round true to round, false to truncate
0634: * @throws ArithmeticException if the year is over 280 million
0635: */
0636: private static void modify(Calendar val, int field, boolean round) {
0637: if (val.get(Calendar.YEAR) > 280000000) {
0638: throw new ArithmeticException(
0639: "Calendar value too large for accurate calculations");
0640: }
0641:
0642: if (field == Calendar.MILLISECOND) {
0643: return;
0644: }
0645:
0646: // ----------------- Fix for LANG-59 ---------------------- START ---------------
0647: // see http://issues.apache.org/jira/browse/LANG-59
0648: //
0649: // Manually truncate milliseconds, seconds and minutes, rather than using
0650: // Calendar methods.
0651:
0652: Date date = val.getTime();
0653: long time = date.getTime();
0654: boolean done = false;
0655:
0656: // truncate milliseconds
0657: int millisecs = val.get(Calendar.MILLISECOND);
0658: if (!round || millisecs < 500) {
0659: time = time - millisecs;
0660: if (field == Calendar.SECOND) {
0661: done = true;
0662: }
0663: }
0664:
0665: // truncate seconds
0666: int seconds = val.get(Calendar.SECOND);
0667: if (!done && (!round || seconds < 30)) {
0668: time = time - (seconds * 1000L);
0669: if (field == Calendar.MINUTE) {
0670: done = true;
0671: }
0672: }
0673:
0674: // truncate minutes
0675: int minutes = val.get(Calendar.MINUTE);
0676: if (!done && (!round || minutes < 30)) {
0677: time = time - (minutes * 60000L);
0678: }
0679:
0680: // reset time
0681: if (date.getTime() != time) {
0682: date.setTime(time);
0683: val.setTime(date);
0684: }
0685: // ----------------- Fix for LANG-59 ----------------------- END ----------------
0686:
0687: boolean roundUp = false;
0688: for (int i = 0; i < fields.length; i++) {
0689: for (int j = 0; j < fields[i].length; j++) {
0690: if (fields[i][j] == field) {
0691: //This is our field... we stop looping
0692: if (round && roundUp) {
0693: if (field == DateUtils.SEMI_MONTH) {
0694: //This is a special case that's hard to generalize
0695: //If the date is 1, we round up to 16, otherwise
0696: // we subtract 15 days and add 1 month
0697: if (val.get(Calendar.DATE) == 1) {
0698: val.add(Calendar.DATE, 15);
0699: } else {
0700: val.add(Calendar.DATE, -15);
0701: val.add(Calendar.MONTH, 1);
0702: }
0703: } else {
0704: //We need at add one to this field since the
0705: // last number causes us to round up
0706: val.add(fields[i][0], 1);
0707: }
0708: }
0709: return;
0710: }
0711: }
0712: //We have various fields that are not easy roundings
0713: int offset = 0;
0714: boolean offsetSet = false;
0715: //These are special types of fields that require different rounding rules
0716: switch (field) {
0717: case DateUtils.SEMI_MONTH:
0718: if (fields[i][0] == Calendar.DATE) {
0719: //If we're going to drop the DATE field's value,
0720: // we want to do this our own way.
0721: //We need to subtrace 1 since the date has a minimum of 1
0722: offset = val.get(Calendar.DATE) - 1;
0723: //If we're above 15 days adjustment, that means we're in the
0724: // bottom half of the month and should stay accordingly.
0725: if (offset >= 15) {
0726: offset -= 15;
0727: }
0728: //Record whether we're in the top or bottom half of that range
0729: roundUp = offset > 7;
0730: offsetSet = true;
0731: }
0732: break;
0733: case Calendar.AM_PM:
0734: if (fields[i][0] == Calendar.HOUR_OF_DAY) {
0735: //If we're going to drop the HOUR field's value,
0736: // we want to do this our own way.
0737: offset = val.get(Calendar.HOUR_OF_DAY);
0738: if (offset >= 12) {
0739: offset -= 12;
0740: }
0741: roundUp = offset > 6;
0742: offsetSet = true;
0743: }
0744: break;
0745: }
0746: if (!offsetSet) {
0747: int min = val.getActualMinimum(fields[i][0]);
0748: int max = val.getActualMaximum(fields[i][0]);
0749: //Calculate the offset from the minimum allowed value
0750: offset = val.get(fields[i][0]) - min;
0751: //Set roundUp if this is more than half way between the minimum and maximum
0752: roundUp = offset > ((max - min) / 2);
0753: }
0754: //We need to remove this field
0755: if (offset != 0) {
0756: val.set(fields[i][0], val.get(fields[i][0]) - offset);
0757: }
0758: }
0759: throw new IllegalArgumentException("The field " + field
0760: + " is not supported");
0761:
0762: }
0763:
0764: //-----------------------------------------------------------------------
0765: /**
0766: * <p>This constructs an <code>Iterator</code> over each day in a date
0767: * range defined by a focus date and range style.</p>
0768: *
0769: * <p>For instance, passing Thursday, July 4, 2002 and a
0770: * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
0771: * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
0772: * 2002, returning a Calendar instance for each intermediate day.</p>
0773: *
0774: * <p>This method provides an iterator that returns Calendar objects.
0775: * The days are progressed using {@link Calendar#add(int, int)}.</p>
0776: *
0777: * @param focus the date to work with, not null
0778: * @param rangeStyle the style constant to use. Must be one of
0779: * {@link DateUtils#RANGE_MONTH_SUNDAY},
0780: * {@link DateUtils#RANGE_MONTH_MONDAY},
0781: * {@link DateUtils#RANGE_WEEK_SUNDAY},
0782: * {@link DateUtils#RANGE_WEEK_MONDAY},
0783: * {@link DateUtils#RANGE_WEEK_RELATIVE},
0784: * {@link DateUtils#RANGE_WEEK_CENTER}
0785: * @return the date iterator, which always returns Calendar instances
0786: * @throws IllegalArgumentException if the date is <code>null</code>
0787: * @throws IllegalArgumentException if the rangeStyle is invalid
0788: */
0789: public static Iterator iterator(Date focus, int rangeStyle) {
0790: if (focus == null) {
0791: throw new IllegalArgumentException(
0792: "The date must not be null");
0793: }
0794: Calendar gval = Calendar.getInstance();
0795: gval.setTime(focus);
0796: return iterator(gval, rangeStyle);
0797: }
0798:
0799: /**
0800: * <p>This constructs an <code>Iterator</code> over each day in a date
0801: * range defined by a focus date and range style.</p>
0802: *
0803: * <p>For instance, passing Thursday, July 4, 2002 and a
0804: * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
0805: * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
0806: * 2002, returning a Calendar instance for each intermediate day.</p>
0807: *
0808: * <p>This method provides an iterator that returns Calendar objects.
0809: * The days are progressed using {@link Calendar#add(int, int)}.</p>
0810: *
0811: * @param focus the date to work with
0812: * @param rangeStyle the style constant to use. Must be one of
0813: * {@link DateUtils#RANGE_MONTH_SUNDAY},
0814: * {@link DateUtils#RANGE_MONTH_MONDAY},
0815: * {@link DateUtils#RANGE_WEEK_SUNDAY},
0816: * {@link DateUtils#RANGE_WEEK_MONDAY},
0817: * {@link DateUtils#RANGE_WEEK_RELATIVE},
0818: * {@link DateUtils#RANGE_WEEK_CENTER}
0819: * @return the date iterator
0820: * @throws IllegalArgumentException if the date is <code>null</code>
0821: * @throws IllegalArgumentException if the rangeStyle is invalid
0822: */
0823: public static Iterator iterator(Calendar focus, int rangeStyle) {
0824: if (focus == null) {
0825: throw new IllegalArgumentException(
0826: "The date must not be null");
0827: }
0828: Calendar start = null;
0829: Calendar end = null;
0830: int startCutoff = Calendar.SUNDAY;
0831: int endCutoff = Calendar.SATURDAY;
0832: switch (rangeStyle) {
0833: case RANGE_MONTH_SUNDAY:
0834: case RANGE_MONTH_MONDAY:
0835: //Set start to the first of the month
0836: start = truncate(focus, Calendar.MONTH);
0837: //Set end to the last of the month
0838: end = (Calendar) start.clone();
0839: end.add(Calendar.MONTH, 1);
0840: end.add(Calendar.DATE, -1);
0841: //Loop start back to the previous sunday or monday
0842: if (rangeStyle == RANGE_MONTH_MONDAY) {
0843: startCutoff = Calendar.MONDAY;
0844: endCutoff = Calendar.SUNDAY;
0845: }
0846: break;
0847: case RANGE_WEEK_SUNDAY:
0848: case RANGE_WEEK_MONDAY:
0849: case RANGE_WEEK_RELATIVE:
0850: case RANGE_WEEK_CENTER:
0851: //Set start and end to the current date
0852: start = truncate(focus, Calendar.DATE);
0853: end = truncate(focus, Calendar.DATE);
0854: switch (rangeStyle) {
0855: case RANGE_WEEK_SUNDAY:
0856: //already set by default
0857: break;
0858: case RANGE_WEEK_MONDAY:
0859: startCutoff = Calendar.MONDAY;
0860: endCutoff = Calendar.SUNDAY;
0861: break;
0862: case RANGE_WEEK_RELATIVE:
0863: startCutoff = focus.get(Calendar.DAY_OF_WEEK);
0864: endCutoff = startCutoff - 1;
0865: break;
0866: case RANGE_WEEK_CENTER:
0867: startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
0868: endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
0869: break;
0870: }
0871: break;
0872: default:
0873: throw new IllegalArgumentException("The range style "
0874: + rangeStyle + " is not valid.");
0875: }
0876: if (startCutoff < Calendar.SUNDAY) {
0877: startCutoff += 7;
0878: }
0879: if (startCutoff > Calendar.SATURDAY) {
0880: startCutoff -= 7;
0881: }
0882: if (endCutoff < Calendar.SUNDAY) {
0883: endCutoff += 7;
0884: }
0885: if (endCutoff > Calendar.SATURDAY) {
0886: endCutoff -= 7;
0887: }
0888: while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
0889: start.add(Calendar.DATE, -1);
0890: }
0891: while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
0892: end.add(Calendar.DATE, 1);
0893: }
0894: return new DateIterator(start, end);
0895: }
0896:
0897: /**
0898: * <p>This constructs an <code>Iterator</code> over each day in a date
0899: * range defined by a focus date and range style.</p>
0900: *
0901: * <p>For instance, passing Thursday, July 4, 2002 and a
0902: * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
0903: * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
0904: * 2002, returning a Calendar instance for each intermediate day.</p>
0905: *
0906: * @param focus the date to work with, either
0907: * <code>Date</code> or <code>Calendar</code>
0908: * @param rangeStyle the style constant to use. Must be one of the range
0909: * styles listed for the {@link #iterator(Calendar, int)} method.
0910: * @return the date iterator
0911: * @throws IllegalArgumentException if the date
0912: * is <code>null</code>
0913: * @throws ClassCastException if the object type is
0914: * not a <code>Date</code> or <code>Calendar</code>
0915: */
0916: public static Iterator iterator(Object focus, int rangeStyle) {
0917: if (focus == null) {
0918: throw new IllegalArgumentException(
0919: "The date must not be null");
0920: }
0921: if (focus instanceof Date) {
0922: return iterator((Date) focus, rangeStyle);
0923: } else if (focus instanceof Calendar) {
0924: return iterator((Calendar) focus, rangeStyle);
0925: } else {
0926: throw new ClassCastException("Could not iterate based on "
0927: + focus);
0928: }
0929: }
0930:
0931: /**
0932: * <p>Date iterator.</p>
0933: */
0934: static class DateIterator implements Iterator {
0935: private final Calendar endFinal;
0936: private final Calendar spot;
0937:
0938: /**
0939: * Constructs a DateIterator that ranges from one date to another.
0940: *
0941: * @param startFinal start date (inclusive)
0942: * @param endFinal end date (not inclusive)
0943: */
0944: DateIterator(Calendar startFinal, Calendar endFinal) {
0945: super ();
0946: this .endFinal = endFinal;
0947: spot = startFinal;
0948: spot.add(Calendar.DATE, -1);
0949: }
0950:
0951: /**
0952: * Has the iterator not reached the end date yet?
0953: *
0954: * @return <code>true</code> if the iterator has yet to reach the end date
0955: */
0956: public boolean hasNext() {
0957: return spot.before(endFinal);
0958: }
0959:
0960: /**
0961: * Return the next calendar in the iteration
0962: *
0963: * @return Object calendar for the next date
0964: */
0965: public Object next() {
0966: if (spot.equals(endFinal)) {
0967: throw new NoSuchElementException();
0968: }
0969: spot.add(Calendar.DATE, 1);
0970: return spot.clone();
0971: }
0972:
0973: /**
0974: * Always throws UnsupportedOperationException.
0975: *
0976: * @throws UnsupportedOperationException
0977: * @see java.util.Iterator#remove()
0978: */
0979: public void remove() {
0980: throw new UnsupportedOperationException();
0981: }
0982: }
0983:
0984: //-------------------------------------------------------------------------
0985: // Deprecated int constants
0986: // TODO: Remove in 3.0
0987:
0988: /**
0989: * Number of milliseconds in a standard second.
0990: *
0991: * @deprecated Use MILLIS_PER_SECOND. This will be removed in Commons Lang 3.0.
0992: */
0993: public static final int MILLIS_IN_SECOND = 1000;
0994: /**
0995: * Number of milliseconds in a standard minute.
0996: *
0997: * @deprecated Use MILLIS_PER_MINUTE. This will be removed in Commons Lang 3.0.
0998: */
0999: public static final int MILLIS_IN_MINUTE = 60 * 1000;
1000: /**
1001: * Number of milliseconds in a standard hour.
1002: *
1003: * @deprecated Use MILLIS_PER_HOUR. This will be removed in Commons Lang 3.0.
1004: */
1005: public static final int MILLIS_IN_HOUR = 60 * 60 * 1000;
1006: /**
1007: * Number of milliseconds in a standard day.
1008: *
1009: * @deprecated Use MILLIS_PER_DAY. This will be removed in Commons Lang 3.0.
1010: */
1011: public static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
1012:
1013: }
|