| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.lang2.time; |
| |
| import java.text.ParseException; |
| import java.text.ParsePosition; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| import java.util.TimeZone; |
| |
| /** |
| * <p>A suite of utilities surrounding the use of the |
| * {@link java.util.Calendar} and {@link java.util.Date} object.</p> |
| * |
| * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a> |
| * @author Stephen Colebourne |
| * @author Janek Bogucki |
| * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a> |
| * @author Phil Steitz |
| * @since 2.0 |
| * @version $Id$ |
| */ |
| public class DateUtils { |
| |
| /** |
| * The UTC time zone (often referred to as GMT). |
| */ |
| public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT"); |
| /** |
| * Number of milliseconds in a standard second. |
| * @since 2.1 |
| */ |
| public static final long MILLIS_PER_SECOND = 1000; |
| /** |
| * Number of milliseconds in a standard minute. |
| * @since 2.1 |
| */ |
| public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND; |
| /** |
| * Number of milliseconds in a standard hour. |
| * @since 2.1 |
| */ |
| public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE; |
| /** |
| * Number of milliseconds in a standard day. |
| * @since 2.1 |
| */ |
| public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR; |
| |
| /** |
| * This is half a month, so this represents whether a date is in the top |
| * or bottom half of the month. |
| */ |
| public final static int SEMI_MONTH = 1001; |
| |
| private static final int[][] fields = { |
| {Calendar.MILLISECOND}, |
| {Calendar.SECOND}, |
| {Calendar.MINUTE}, |
| {Calendar.HOUR_OF_DAY, Calendar.HOUR}, |
| {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM |
| /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */ |
| }, |
| {Calendar.MONTH, DateUtils.SEMI_MONTH}, |
| {Calendar.YEAR}, |
| {Calendar.ERA}}; |
| |
| /** |
| * A week range, starting on Sunday. |
| */ |
| public final static int RANGE_WEEK_SUNDAY = 1; |
| |
| /** |
| * A week range, starting on Monday. |
| */ |
| public final static int RANGE_WEEK_MONDAY = 2; |
| |
| /** |
| * A week range, starting on the day focused. |
| */ |
| public final static int RANGE_WEEK_RELATIVE = 3; |
| |
| /** |
| * A week range, centered around the day focused. |
| */ |
| public final static int RANGE_WEEK_CENTER = 4; |
| |
| /** |
| * A month range, the week starting on Sunday. |
| */ |
| public final static int RANGE_MONTH_SUNDAY = 5; |
| |
| /** |
| * A month range, the week starting on Monday. |
| */ |
| public final static int RANGE_MONTH_MONDAY = 6; |
| |
| /** |
| * <p><code>DateUtils</code> instances should NOT be constructed in |
| * standard programming. Instead, the class should be used as |
| * <code>DateUtils.parse(str);</code>.</p> |
| * |
| * <p>This constructor is public to permit tools that require a JavaBean |
| * instance to operate.</p> |
| */ |
| public DateUtils() { |
| super(); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Checks if two date objects are on the same day ignoring time.</p> |
| * |
| * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true. |
| * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false. |
| * </p> |
| * |
| * @param date1 the first date, not altered, not null |
| * @param date2 the second date, not altered, not null |
| * @return true if they represent the same day |
| * @throws IllegalArgumentException if either date is <code>null</code> |
| * @since 2.1 |
| */ |
| public static boolean isSameDay(Date date1, Date date2) { |
| if (date1 == null || date2 == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar cal1 = Calendar.getInstance(); |
| cal1.setTime(date1); |
| Calendar cal2 = Calendar.getInstance(); |
| cal2.setTime(date2); |
| return isSameDay(cal1, cal2); |
| } |
| |
| /** |
| * <p>Checks if two calendar objects are on the same day ignoring time.</p> |
| * |
| * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true. |
| * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false. |
| * </p> |
| * |
| * @param cal1 the first calendar, not altered, not null |
| * @param cal2 the second calendar, not altered, not null |
| * @return true if they represent the same day |
| * @throws IllegalArgumentException if either calendar is <code>null</code> |
| * @since 2.1 |
| */ |
| public static boolean isSameDay(Calendar cal1, Calendar cal2) { |
| if (cal1 == null || cal2 == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && |
| cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && |
| cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Checks if two date objects represent the same instant in time.</p> |
| * |
| * <p>This method compares the long millisecond time of the two objects.</p> |
| * |
| * @param date1 the first date, not altered, not null |
| * @param date2 the second date, not altered, not null |
| * @return true if they represent the same millisecond instant |
| * @throws IllegalArgumentException if either date is <code>null</code> |
| * @since 2.1 |
| */ |
| public static boolean isSameInstant(Date date1, Date date2) { |
| if (date1 == null || date2 == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| return date1.getTime() == date2.getTime(); |
| } |
| |
| /** |
| * <p>Checks if two calendar objects represent the same instant in time.</p> |
| * |
| * <p>This method compares the long millisecond time of the two objects.</p> |
| * |
| * @param cal1 the first calendar, not altered, not null |
| * @param cal2 the second calendar, not altered, not null |
| * @return true if they represent the same millisecond instant |
| * @throws IllegalArgumentException if either date is <code>null</code> |
| * @since 2.1 |
| */ |
| public static boolean isSameInstant(Calendar cal1, Calendar cal2) { |
| if (cal1 == null || cal2 == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| return cal1.getTime().getTime() == cal2.getTime().getTime(); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Checks if two calendar objects represent the same local time.</p> |
| * |
| * <p>This method compares the values of the fields of the two objects. |
| * In addition, both calendars must be the same of the same type.</p> |
| * |
| * @param cal1 the first calendar, not altered, not null |
| * @param cal2 the second calendar, not altered, not null |
| * @return true if they represent the same millisecond instant |
| * @throws IllegalArgumentException if either date is <code>null</code> |
| * @since 2.1 |
| */ |
| public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) { |
| if (cal1 == null || cal2 == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| return (cal1.get(Calendar.MILLISECOND) == cal2.get(Calendar.MILLISECOND) && |
| cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) && |
| cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) && |
| cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) && |
| cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && |
| cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && |
| cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && |
| cal1.getClass() == cal2.getClass()); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Parses a string representing a date by trying a variety of different parsers.</p> |
| * |
| * <p>The parse will try each parse pattern in turn. |
| * A parse is only deemed sucessful if it parses the whole of the input string. |
| * If no parse patterns match, a ParseException is thrown.</p> |
| * |
| * @param str the date to parse, not null |
| * @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null |
| * @return the parsed date |
| * @throws IllegalArgumentException if the date string or pattern array is null |
| * @throws ParseException if none of the date patterns were suitable |
| */ |
| public static Date parseDate(String str, String[] parsePatterns) throws ParseException { |
| if (str == null || parsePatterns == null) { |
| throw new IllegalArgumentException("Date and Patterns must not be null"); |
| } |
| |
| SimpleDateFormat parser = null; |
| ParsePosition pos = new ParsePosition(0); |
| for (int i = 0; i < parsePatterns.length; i++) { |
| if (i == 0) { |
| parser = new SimpleDateFormat(parsePatterns[0]); |
| } else { |
| parser.applyPattern(parsePatterns[i]); |
| } |
| pos.setIndex(0); |
| Date date = parser.parse(str, pos); |
| if (date != null && pos.getIndex() == str.length()) { |
| return date; |
| } |
| } |
| throw new ParseException("Unable to parse the date: " + str, -1); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of years to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addYears(Date date, int amount) { |
| return add(date, Calendar.YEAR, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of months to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addMonths(Date date, int amount) { |
| return add(date, Calendar.MONTH, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of weeks to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addWeeks(Date date, int amount) { |
| return add(date, Calendar.WEEK_OF_YEAR, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of days to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addDays(Date date, int amount) { |
| return add(date, Calendar.DAY_OF_MONTH, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of hours to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addHours(Date date, int amount) { |
| return add(date, Calendar.HOUR_OF_DAY, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of minutes to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addMinutes(Date date, int amount) { |
| return add(date, Calendar.MINUTE, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of seconds to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addSeconds(Date date, int amount) { |
| return add(date, Calendar.SECOND, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds a number of milliseconds to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date addMilliseconds(Date date, int amount) { |
| return add(date, Calendar.MILLISECOND, amount); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Adds to a date returning a new object. |
| * The original date object is unchanged. |
| * |
| * @param date the date, not null |
| * @param calendarField the calendar field to add to |
| * @param amount the amount to add, may be negative |
| * @return the new date object with the amount added |
| * @throws IllegalArgumentException if the date is null |
| */ |
| public static Date add(Date date, int calendarField, int amount) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar c = Calendar.getInstance(); |
| c.setTime(date); |
| c.add(calendarField, amount); |
| return c.getTime(); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Round this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if this was passed with HOUR, it would return |
| * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it |
| * would return 1 April 2002 0:00:00.000.</p> |
| * |
| * <p>For a date in a timezone that handles the change to daylight |
| * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows. |
| * Suppose daylight saving time begins at 02:00 on March 30. Rounding a |
| * date that crosses this time would produce the following values: |
| * <ul> |
| * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li> |
| * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li> |
| * </ul> |
| * </p> |
| * |
| * @param date the date to work with |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Date round(Date date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar gval = Calendar.getInstance(); |
| gval.setTime(date); |
| modify(gval, field, true); |
| return gval.getTime(); |
| } |
| |
| /** |
| * <p>Round this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if this was passed with HOUR, it would return |
| * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it |
| * would return 1 April 2002 0:00:00.000.</p> |
| * |
| * <p>For a date in a timezone that handles the change to daylight |
| * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows. |
| * Suppose daylight saving time begins at 02:00 on March 30. Rounding a |
| * date that crosses this time would produce the following values: |
| * <ul> |
| * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li> |
| * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li> |
| * </ul> |
| * </p> |
| * |
| * @param date the date to work with |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date (a different object) |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Calendar round(Calendar date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar rounded = (Calendar) date.clone(); |
| modify(rounded, field, true); |
| return rounded; |
| } |
| |
| /** |
| * <p>Round this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if this was passed with HOUR, it would return |
| * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it |
| * would return 1 April 2002 0:00:00.000.</p> |
| * |
| * <p>For a date in a timezone that handles the change to daylight |
| * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows. |
| * Suppose daylight saving time begins at 02:00 on March 30. Rounding a |
| * date that crosses this time would produce the following values: |
| * <ul> |
| * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li> |
| * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li> |
| * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li> |
| * </ul> |
| * </p> |
| * |
| * @param date the date to work with, either Date or Calendar |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws ClassCastException if the object type is not a <code>Date</code> |
| * or <code>Calendar</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Date round(Object date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| if (date instanceof Date) { |
| return round((Date) date, field); |
| } else if (date instanceof Calendar) { |
| return round((Calendar) date, field).getTime(); |
| } else { |
| throw new ClassCastException("Could not round " + date); |
| } |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Truncate this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if you passed with HOUR, it would return 28 Mar |
| * 2002 13:00:00.000. If this was passed with MONTH, it would |
| * return 1 Mar 2002 0:00:00.000.</p> |
| * |
| * @param date the date to work with |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Date truncate(Date date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar gval = Calendar.getInstance(); |
| gval.setTime(date); |
| modify(gval, field, false); |
| return gval.getTime(); |
| } |
| |
| /** |
| * <p>Truncate this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if you passed with HOUR, it would return 28 Mar |
| * 2002 13:00:00.000. If this was passed with MONTH, it would |
| * return 1 Mar 2002 0:00:00.000.</p> |
| * |
| * @param date the date to work with |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date (a different object) |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Calendar truncate(Calendar date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar truncated = (Calendar) date.clone(); |
| modify(truncated, field, false); |
| return truncated; |
| } |
| |
| /** |
| * <p>Truncate this date, leaving the field specified as the most |
| * significant field.</p> |
| * |
| * <p>For example, if you had the datetime of 28 Mar 2002 |
| * 13:45:01.231, if you passed with HOUR, it would return 28 Mar |
| * 2002 13:00:00.000. If this was passed with MONTH, it would |
| * return 1 Mar 2002 0:00:00.000.</p> |
| * |
| * @param date the date to work with, either <code>Date</code> |
| * or <code>Calendar</code> |
| * @param field the field from <code>Calendar</code> |
| * or <code>SEMI_MONTH</code> |
| * @return the rounded date |
| * @throws IllegalArgumentException if the date |
| * is <code>null</code> |
| * @throws ClassCastException if the object type is not a |
| * <code>Date</code> or <code>Calendar</code> |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| public static Date truncate(Object date, int field) { |
| if (date == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| if (date instanceof Date) { |
| return truncate((Date) date, field); |
| } else if (date instanceof Calendar) { |
| return truncate((Calendar) date, field).getTime(); |
| } else { |
| throw new ClassCastException("Could not truncate " + date); |
| } |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>Internal calculation method.</p> |
| * |
| * @param val the calendar |
| * @param field the field constant |
| * @param round true to round, false to truncate |
| * @throws ArithmeticException if the year is over 280 million |
| */ |
| private static void modify(Calendar val, int field, boolean round) { |
| if (val.get(Calendar.YEAR) > 280000000) { |
| throw new ArithmeticException("Calendar value too large for accurate calculations"); |
| } |
| |
| if (field == Calendar.MILLISECOND) { |
| return; |
| } |
| |
| // ----------------- Fix for LANG-59 ---------------------- START --------------- |
| // see http://issues.apache.org/jira/browse/LANG-59 |
| // |
| // Manually truncate milliseconds, seconds and minutes, rather than using |
| // Calendar methods. |
| |
| Date date = val.getTime(); |
| long time = date.getTime(); |
| boolean done = false; |
| |
| // truncate milliseconds |
| int millisecs = val.get(Calendar.MILLISECOND); |
| if (!round || millisecs < 500) { |
| time = time - millisecs; |
| if (field == Calendar.SECOND) { |
| done = true; |
| } |
| } |
| |
| // truncate seconds |
| int seconds = val.get(Calendar.SECOND); |
| if (!done && (!round || seconds < 30)) { |
| time = time - (seconds * 1000L); |
| if (field == Calendar.MINUTE) { |
| done = true; |
| } |
| } |
| |
| // truncate minutes |
| int minutes = val.get(Calendar.MINUTE); |
| if (!done && (!round || minutes < 30)) { |
| time = time - (minutes * 60000L); |
| } |
| |
| // reset time |
| if (date.getTime() != time) { |
| date.setTime(time); |
| val.setTime(date); |
| } |
| // ----------------- Fix for LANG-59 ----------------------- END ---------------- |
| |
| boolean roundUp = false; |
| for (int i = 0; i < fields.length; i++) { |
| for (int j = 0; j < fields[i].length; j++) { |
| if (fields[i][j] == field) { |
| //This is our field... we stop looping |
| if (round && roundUp) { |
| if (field == DateUtils.SEMI_MONTH) { |
| //This is a special case that's hard to generalize |
| //If the date is 1, we round up to 16, otherwise |
| // we subtract 15 days and add 1 month |
| if (val.get(Calendar.DATE) == 1) { |
| val.add(Calendar.DATE, 15); |
| } else { |
| val.add(Calendar.DATE, -15); |
| val.add(Calendar.MONTH, 1); |
| } |
| } else { |
| //We need at add one to this field since the |
| // last number causes us to round up |
| val.add(fields[i][0], 1); |
| } |
| } |
| return; |
| } |
| } |
| //We have various fields that are not easy roundings |
| int offset = 0; |
| boolean offsetSet = false; |
| //These are special types of fields that require different rounding rules |
| switch (field) { |
| case DateUtils.SEMI_MONTH: |
| if (fields[i][0] == Calendar.DATE) { |
| //If we're going to drop the DATE field's value, |
| // we want to do this our own way. |
| //We need to subtrace 1 since the date has a minimum of 1 |
| offset = val.get(Calendar.DATE) - 1; |
| //If we're above 15 days adjustment, that means we're in the |
| // bottom half of the month and should stay accordingly. |
| if (offset >= 15) { |
| offset -= 15; |
| } |
| //Record whether we're in the top or bottom half of that range |
| roundUp = offset > 7; |
| offsetSet = true; |
| } |
| break; |
| case Calendar.AM_PM: |
| if (fields[i][0] == Calendar.HOUR_OF_DAY) { |
| //If we're going to drop the HOUR field's value, |
| // we want to do this our own way. |
| offset = val.get(Calendar.HOUR_OF_DAY); |
| if (offset >= 12) { |
| offset -= 12; |
| } |
| roundUp = offset > 6; |
| offsetSet = true; |
| } |
| break; |
| } |
| if (!offsetSet) { |
| int min = val.getActualMinimum(fields[i][0]); |
| int max = val.getActualMaximum(fields[i][0]); |
| //Calculate the offset from the minimum allowed value |
| offset = val.get(fields[i][0]) - min; |
| //Set roundUp if this is more than half way between the minimum and maximum |
| roundUp = offset > ((max - min) / 2); |
| } |
| //We need to remove this field |
| if (offset != 0) { |
| val.set(fields[i][0], val.get(fields[i][0]) - offset); |
| } |
| } |
| throw new IllegalArgumentException("The field " + field + " is not supported"); |
| |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * <p>This constructs an <code>Iterator</code> over each day in a date |
| * range defined by a focus date and range style.</p> |
| * |
| * <p>For instance, passing Thursday, July 4, 2002 and a |
| * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code> |
| * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, |
| * 2002, returning a Calendar instance for each intermediate day.</p> |
| * |
| * <p>This method provides an iterator that returns Calendar objects. |
| * The days are progressed using {@link Calendar#add(int, int)}.</p> |
| * |
| * @param focus the date to work with, not null |
| * @param rangeStyle the style constant to use. Must be one of |
| * {@link DateUtils#RANGE_MONTH_SUNDAY}, |
| * {@link DateUtils#RANGE_MONTH_MONDAY}, |
| * {@link DateUtils#RANGE_WEEK_SUNDAY}, |
| * {@link DateUtils#RANGE_WEEK_MONDAY}, |
| * {@link DateUtils#RANGE_WEEK_RELATIVE}, |
| * {@link DateUtils#RANGE_WEEK_CENTER} |
| * @return the date iterator, which always returns Calendar instances |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws IllegalArgumentException if the rangeStyle is invalid |
| */ |
| public static Iterator iterator(Date focus, int rangeStyle) { |
| if (focus == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar gval = Calendar.getInstance(); |
| gval.setTime(focus); |
| return iterator(gval, rangeStyle); |
| } |
| |
| /** |
| * <p>This constructs an <code>Iterator</code> over each day in a date |
| * range defined by a focus date and range style.</p> |
| * |
| * <p>For instance, passing Thursday, July 4, 2002 and a |
| * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code> |
| * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, |
| * 2002, returning a Calendar instance for each intermediate day.</p> |
| * |
| * <p>This method provides an iterator that returns Calendar objects. |
| * The days are progressed using {@link Calendar#add(int, int)}.</p> |
| * |
| * @param focus the date to work with |
| * @param rangeStyle the style constant to use. Must be one of |
| * {@link DateUtils#RANGE_MONTH_SUNDAY}, |
| * {@link DateUtils#RANGE_MONTH_MONDAY}, |
| * {@link DateUtils#RANGE_WEEK_SUNDAY}, |
| * {@link DateUtils#RANGE_WEEK_MONDAY}, |
| * {@link DateUtils#RANGE_WEEK_RELATIVE}, |
| * {@link DateUtils#RANGE_WEEK_CENTER} |
| * @return the date iterator |
| * @throws IllegalArgumentException if the date is <code>null</code> |
| * @throws IllegalArgumentException if the rangeStyle is invalid |
| */ |
| public static Iterator iterator(Calendar focus, int rangeStyle) { |
| if (focus == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| Calendar start = null; |
| Calendar end = null; |
| int startCutoff = Calendar.SUNDAY; |
| int endCutoff = Calendar.SATURDAY; |
| switch (rangeStyle) { |
| case RANGE_MONTH_SUNDAY: |
| case RANGE_MONTH_MONDAY: |
| //Set start to the first of the month |
| start = truncate(focus, Calendar.MONTH); |
| //Set end to the last of the month |
| end = (Calendar) start.clone(); |
| end.add(Calendar.MONTH, 1); |
| end.add(Calendar.DATE, -1); |
| //Loop start back to the previous sunday or monday |
| if (rangeStyle == RANGE_MONTH_MONDAY) { |
| startCutoff = Calendar.MONDAY; |
| endCutoff = Calendar.SUNDAY; |
| } |
| break; |
| case RANGE_WEEK_SUNDAY: |
| case RANGE_WEEK_MONDAY: |
| case RANGE_WEEK_RELATIVE: |
| case RANGE_WEEK_CENTER: |
| //Set start and end to the current date |
| start = truncate(focus, Calendar.DATE); |
| end = truncate(focus, Calendar.DATE); |
| switch (rangeStyle) { |
| case RANGE_WEEK_SUNDAY: |
| //already set by default |
| break; |
| case RANGE_WEEK_MONDAY: |
| startCutoff = Calendar.MONDAY; |
| endCutoff = Calendar.SUNDAY; |
| break; |
| case RANGE_WEEK_RELATIVE: |
| startCutoff = focus.get(Calendar.DAY_OF_WEEK); |
| endCutoff = startCutoff - 1; |
| break; |
| case RANGE_WEEK_CENTER: |
| startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3; |
| endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3; |
| break; |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("The range style " + rangeStyle + " is not valid."); |
| } |
| if (startCutoff < Calendar.SUNDAY) { |
| startCutoff += 7; |
| } |
| if (startCutoff > Calendar.SATURDAY) { |
| startCutoff -= 7; |
| } |
| if (endCutoff < Calendar.SUNDAY) { |
| endCutoff += 7; |
| } |
| if (endCutoff > Calendar.SATURDAY) { |
| endCutoff -= 7; |
| } |
| while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) { |
| start.add(Calendar.DATE, -1); |
| } |
| while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) { |
| end.add(Calendar.DATE, 1); |
| } |
| return new DateIterator(start, end); |
| } |
| |
| /** |
| * <p>This constructs an <code>Iterator</code> over each day in a date |
| * range defined by a focus date and range style.</p> |
| * |
| * <p>For instance, passing Thursday, July 4, 2002 and a |
| * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code> |
| * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3, |
| * 2002, returning a Calendar instance for each intermediate day.</p> |
| * |
| * @param focus the date to work with, either |
| * <code>Date</code> or <code>Calendar</code> |
| * @param rangeStyle the style constant to use. Must be one of the range |
| * styles listed for the {@link #iterator(Calendar, int)} method. |
| * @return the date iterator |
| * @throws IllegalArgumentException if the date |
| * is <code>null</code> |
| * @throws ClassCastException if the object type is |
| * not a <code>Date</code> or <code>Calendar</code> |
| */ |
| public static Iterator iterator(Object focus, int rangeStyle) { |
| if (focus == null) { |
| throw new IllegalArgumentException("The date must not be null"); |
| } |
| if (focus instanceof Date) { |
| return iterator((Date) focus, rangeStyle); |
| } else if (focus instanceof Calendar) { |
| return iterator((Calendar) focus, rangeStyle); |
| } else { |
| throw new ClassCastException("Could not iterate based on " + focus); |
| } |
| } |
| |
| /** |
| * <p>Date iterator.</p> |
| */ |
| static class DateIterator implements Iterator { |
| private final Calendar endFinal; |
| private final Calendar spot; |
| |
| /** |
| * Constructs a DateIterator that ranges from one date to another. |
| * |
| * @param startFinal start date (inclusive) |
| * @param endFinal end date (not inclusive) |
| */ |
| DateIterator(Calendar startFinal, Calendar endFinal) { |
| super(); |
| this.endFinal = endFinal; |
| spot = startFinal; |
| spot.add(Calendar.DATE, -1); |
| } |
| |
| /** |
| * Has the iterator not reached the end date yet? |
| * |
| * @return <code>true</code> if the iterator has yet to reach the end date |
| */ |
| public boolean hasNext() { |
| return spot.before(endFinal); |
| } |
| |
| /** |
| * Return the next calendar in the iteration |
| * |
| * @return Object calendar for the next date |
| */ |
| public Object next() { |
| if (spot.equals(endFinal)) { |
| throw new NoSuchElementException(); |
| } |
| spot.add(Calendar.DATE, 1); |
| return spot.clone(); |
| } |
| |
| /** |
| * Always throws UnsupportedOperationException. |
| * |
| * @throws UnsupportedOperationException |
| * @see java.util.Iterator#remove() |
| */ |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| } |