View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang.time;
18  
19  import java.text.ParseException;
20  import java.text.ParsePosition;
21  import java.text.SimpleDateFormat;
22  import java.util.Calendar;
23  import java.util.Date;
24  import java.util.Iterator;
25  import java.util.NoSuchElementException;
26  import java.util.TimeZone;
27  
28  /**
29   * <p>A suite of utilities surrounding the use of the
30   * {@link java.util.Calendar} and {@link java.util.Date} object.</p>
31   * 
32   * <p>DateUtils contains a lot of common methods considering manipulations
33   * of Dates or Calendars. Some methods require some extra explanation.
34   * The truncate and round methods could be considered the Math.floor(),
35   * Math.ceil() or Math.round versions for dates
36   * This way date-fields will be ignored in bottom-up order.
37   * As a complement to these methods we've introduced some fragment-methods.
38   * With these methods the Date-fields will be ignored in top-down order.
39   * Since a date without a year is not a valid date, you have to decide in what
40   * kind of date-field you want your result, for instance milliseconds or days.
41   * </p>
42   *   
43   *   
44   *
45   * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
46   * @author Stephen Colebourne
47   * @author Janek Bogucki
48   * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
49   * @author Phil Steitz
50   * @author Robert Scholte
51   * @since 2.0
52   * @version $Id: DateUtils.java 634096 2008-03-06 00:58:11Z niallp $
53   */
54  public class DateUtils {
55      
56      /**
57       * The UTC time zone  (often referred to as GMT).
58       */
59      public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT");
60      /**
61       * Number of milliseconds in a standard second.
62       * @since 2.1
63       */
64      public static final long MILLIS_PER_SECOND = 1000;
65      /**
66       * Number of milliseconds in a standard minute.
67       * @since 2.1
68       */
69      public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
70      /**
71       * Number of milliseconds in a standard hour.
72       * @since 2.1
73       */
74      public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
75      /**
76       * Number of milliseconds in a standard day.
77       * @since 2.1
78       */
79      public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
80  
81      /**
82       * This is half a month, so this represents whether a date is in the top
83       * or bottom half of the month.
84       */
85      public final static int SEMI_MONTH = 1001;
86  
87      private static final int[][] fields = {
88              {Calendar.MILLISECOND},
89              {Calendar.SECOND},
90              {Calendar.MINUTE},
91              {Calendar.HOUR_OF_DAY, Calendar.HOUR},
92              {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM 
93                  /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */
94              },
95              {Calendar.MONTH, DateUtils.SEMI_MONTH},
96              {Calendar.YEAR},
97              {Calendar.ERA}};
98  
99      /**
100      * A week range, starting on Sunday.
101      */
102     public final static int RANGE_WEEK_SUNDAY = 1;
103 
104     /**
105      * A week range, starting on Monday.
106      */
107     public final static int RANGE_WEEK_MONDAY = 2;
108 
109     /**
110      * A week range, starting on the day focused.
111      */
112     public final static int RANGE_WEEK_RELATIVE = 3;
113 
114     /**
115      * A week range, centered around the day focused.
116      */
117     public final static int RANGE_WEEK_CENTER = 4;
118 
119     /**
120      * A month range, the week starting on Sunday.
121      */
122     public final static int RANGE_MONTH_SUNDAY = 5;
123 
124     /**
125      * A month range, the week starting on Monday.
126      */
127     public final static int RANGE_MONTH_MONDAY = 6;
128 
129     /**
130      * <p><code>DateUtils</code> instances should NOT be constructed in
131      * standard programming. Instead, the class should be used as
132      * <code>DateUtils.parse(str);</code>.</p>
133      *
134      * <p>This constructor is public to permit tools that require a JavaBean
135      * instance to operate.</p>
136      */
137     public DateUtils() {
138         super();
139     }
140 
141     //-----------------------------------------------------------------------
142     /**
143      * <p>Checks if two date objects are on the same day ignoring time.</p>
144      *
145      * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
146      * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
147      * </p>
148      * 
149      * @param date1  the first date, not altered, not null
150      * @param date2  the second date, not altered, not null
151      * @return true if they represent the same day
152      * @throws IllegalArgumentException if either date is <code>null</code>
153      * @since 2.1
154      */
155     public static boolean isSameDay(Date date1, Date date2) {
156         if (date1 == null || date2 == null) {
157             throw new IllegalArgumentException("The date must not be null");
158         }
159         Calendar cal1 = Calendar.getInstance();
160         cal1.setTime(date1);
161         Calendar cal2 = Calendar.getInstance();
162         cal2.setTime(date2);
163         return isSameDay(cal1, cal2);
164     }
165 
166     /**
167      * <p>Checks if two calendar objects are on the same day ignoring time.</p>
168      *
169      * <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
170      * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
171      * </p>
172      * 
173      * @param cal1  the first calendar, not altered, not null
174      * @param cal2  the second calendar, not altered, not null
175      * @return true if they represent the same day
176      * @throws IllegalArgumentException if either calendar is <code>null</code>
177      * @since 2.1
178      */
179     public static boolean isSameDay(Calendar cal1, Calendar cal2) {
180         if (cal1 == null || cal2 == null) {
181             throw new IllegalArgumentException("The date must not be null");
182         }
183         return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
184                 cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
185                 cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
186     }
187 
188     //-----------------------------------------------------------------------
189     /**
190      * <p>Checks if two date objects represent the same instant in time.</p>
191      *
192      * <p>This method compares the long millisecond time of the two objects.</p>
193      * 
194      * @param date1  the first date, not altered, not null
195      * @param date2  the second date, not altered, not null
196      * @return true if they represent the same millisecond instant
197      * @throws IllegalArgumentException if either date is <code>null</code>
198      * @since 2.1
199      */
200     public static boolean isSameInstant(Date date1, Date date2) {
201         if (date1 == null || date2 == null) {
202             throw new IllegalArgumentException("The date must not be null");
203         }
204         return date1.getTime() == date2.getTime();
205     }
206 
207     /**
208      * <p>Checks if two calendar objects represent the same instant in time.</p>
209      *
210      * <p>This method compares the long millisecond time of the two objects.</p>
211      * 
212      * @param cal1  the first calendar, not altered, not null
213      * @param cal2  the second calendar, not altered, not null
214      * @return true if they represent the same millisecond instant
215      * @throws IllegalArgumentException if either date is <code>null</code>
216      * @since 2.1
217      */
218     public static boolean isSameInstant(Calendar cal1, Calendar cal2) {
219         if (cal1 == null || cal2 == null) {
220             throw new IllegalArgumentException("The date must not be null");
221         }
222         return cal1.getTime().getTime() == cal2.getTime().getTime();
223     }
224 
225     //-----------------------------------------------------------------------
226     /**
227      * <p>Checks if two calendar objects represent the same local time.</p>
228      *
229      * <p>This method compares the values of the fields of the two objects.
230      * In addition, both calendars must be the same of the same type.</p>
231      * 
232      * @param cal1  the first calendar, not altered, not null
233      * @param cal2  the second calendar, not altered, not null
234      * @return true if they represent the same millisecond instant
235      * @throws IllegalArgumentException if either date is <code>null</code>
236      * @since 2.1
237      */
238     public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) {
239         if (cal1 == null || cal2 == null) {
240             throw new IllegalArgumentException("The date must not be null");
241         }
242         return (cal1.get(Calendar.MILLISECOND) == cal2.get(Calendar.MILLISECOND) &&
243                 cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) &&
244                 cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
245                 cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
246                 cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
247                 cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
248                 cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
249                 cal1.getClass() == cal2.getClass());
250     }
251 
252     //-----------------------------------------------------------------------
253     /**
254      * <p>Parses a string representing a date by trying a variety of different parsers.</p>
255      * 
256      * <p>The parse will try each parse pattern in turn.
257      * A parse is only deemed sucessful if it parses the whole of the input string.
258      * If no parse patterns match, a ParseException is thrown.</p>
259      * 
260      * @param str  the date to parse, not null
261      * @param parsePatterns  the date format patterns to use, see SimpleDateFormat, not null
262      * @return the parsed date
263      * @throws IllegalArgumentException if the date string or pattern array is null
264      * @throws ParseException if none of the date patterns were suitable
265      */
266     public static Date parseDate(String str, String[] parsePatterns) throws ParseException {
267         if (str == null || parsePatterns == null) {
268             throw new IllegalArgumentException("Date and Patterns must not be null");
269         }
270         
271         SimpleDateFormat parser = null;
272         ParsePosition pos = new ParsePosition(0);
273         for (int i = 0; i < parsePatterns.length; i++) {
274             if (i == 0) {
275                 parser = new SimpleDateFormat(parsePatterns[0]);
276             } else {
277                 parser.applyPattern(parsePatterns[i]);
278             }
279             pos.setIndex(0);
280             Date date = parser.parse(str, pos);
281             if (date != null && pos.getIndex() == str.length()) {
282                 return date;
283             }
284         }
285         throw new ParseException("Unable to parse the date: " + str, -1);
286     }
287 
288     //-----------------------------------------------------------------------
289     /**
290      * Adds a number of years to a date returning a new object.
291      * The original date object is unchanged.
292      *
293      * @param date  the date, not null
294      * @param amount  the amount to add, may be negative
295      * @return the new date object with the amount added
296      * @throws IllegalArgumentException if the date is null
297      */
298     public static Date addYears(Date date, int amount) {
299         return add(date, Calendar.YEAR, amount);
300     }
301 
302     //-----------------------------------------------------------------------
303     /**
304      * Adds a number of months to a date returning a new object.
305      * The original date object is unchanged.
306      *
307      * @param date  the date, not null
308      * @param amount  the amount to add, may be negative
309      * @return the new date object with the amount added
310      * @throws IllegalArgumentException if the date is null
311      */
312     public static Date addMonths(Date date, int amount) {
313         return add(date, Calendar.MONTH, amount);
314     }
315 
316     //-----------------------------------------------------------------------
317     /**
318      * Adds a number of weeks to a date returning a new object.
319      * The original date object is unchanged.
320      *
321      * @param date  the date, not null
322      * @param amount  the amount to add, may be negative
323      * @return the new date object with the amount added
324      * @throws IllegalArgumentException if the date is null
325      */
326     public static Date addWeeks(Date date, int amount) {
327         return add(date, Calendar.WEEK_OF_YEAR, amount);
328     }
329 
330     //-----------------------------------------------------------------------
331     /**
332      * Adds a number of days to a date returning a new object.
333      * The original date object is unchanged.
334      *
335      * @param date  the date, not null
336      * @param amount  the amount to add, may be negative
337      * @return the new date object with the amount added
338      * @throws IllegalArgumentException if the date is null
339      */
340     public static Date addDays(Date date, int amount) {
341         return add(date, Calendar.DAY_OF_MONTH, amount);
342     }
343 
344     //-----------------------------------------------------------------------
345     /**
346      * Adds a number of hours to a date returning a new object.
347      * The original date object is unchanged.
348      *
349      * @param date  the date, not null
350      * @param amount  the amount to add, may be negative
351      * @return the new date object with the amount added
352      * @throws IllegalArgumentException if the date is null
353      */
354     public static Date addHours(Date date, int amount) {
355         return add(date, Calendar.HOUR_OF_DAY, amount);
356     }
357 
358     //-----------------------------------------------------------------------
359     /**
360      * Adds a number of minutes to a date returning a new object.
361      * The original date object is unchanged.
362      *
363      * @param date  the date, not null
364      * @param amount  the amount to add, may be negative
365      * @return the new date object with the amount added
366      * @throws IllegalArgumentException if the date is null
367      */
368     public static Date addMinutes(Date date, int amount) {
369         return add(date, Calendar.MINUTE, amount);
370     }
371 
372     //-----------------------------------------------------------------------
373     /**
374      * Adds a number of seconds to a date returning a new object.
375      * The original date object is unchanged.
376      *
377      * @param date  the date, not null
378      * @param amount  the amount to add, may be negative
379      * @return the new date object with the amount added
380      * @throws IllegalArgumentException if the date is null
381      */
382     public static Date addSeconds(Date date, int amount) {
383         return add(date, Calendar.SECOND, amount);
384     }
385 
386     //-----------------------------------------------------------------------
387     /**
388      * Adds a number of milliseconds to a date returning a new object.
389      * The original date object is unchanged.
390      *
391      * @param date  the date, not null
392      * @param amount  the amount to add, may be negative
393      * @return the new date object with the amount added
394      * @throws IllegalArgumentException if the date is null
395      */
396     public static Date addMilliseconds(Date date, int amount) {
397         return add(date, Calendar.MILLISECOND, amount);
398     }
399 
400     //-----------------------------------------------------------------------
401     /**
402      * Adds to a date returning a new object.
403      * The original date object is unchanged.
404      *
405      * @param date  the date, not null
406      * @param calendarField  the calendar field to add to
407      * @param amount  the amount to add, may be negative
408      * @return the new date object with the amount added
409      * @throws IllegalArgumentException if the date is null
410      * @deprecated Will become privately scoped in 3.0
411      */
412     public static Date add(Date date, int calendarField, int amount) {
413         if (date == null) {
414             throw new IllegalArgumentException("The date must not be null");
415         }
416         Calendar c = Calendar.getInstance();
417         c.setTime(date);
418         c.add(calendarField, amount);
419         return c.getTime();
420     }
421     
422     //-----------------------------------------------------------------------
423     /**
424      * Sets the years field to a date returning a new object.
425      * The original date object is unchanged.
426      *
427      * @param date  the date, not null
428      * @param amount the amount to set
429      * @return a new Date object set with the specified value
430      * @throws IllegalArgumentException if the date is null
431      * @since 2.4
432      */
433     public static Date setYears(Date date, int amount) {
434         return set(date, Calendar.YEAR, amount);
435     }
436 
437     //-----------------------------------------------------------------------
438     /**
439      * Sets the months field to a date returning a new object.
440      * The original date object is unchanged.
441      *
442      * @param date  the date, not null
443      * @param amount the amount to set
444      * @return a new Date object set with the specified value
445      * @throws IllegalArgumentException if the date is null
446      * @since 2.4
447      */
448     public static Date setMonths(Date date, int amount) {
449         return set(date, Calendar.MONTH, amount);
450     }
451 
452     //-----------------------------------------------------------------------
453     /**
454      * Sets the day of month field to a date returning a new object.
455      * The original date object is unchanged.
456      *
457      * @param date  the date, not null
458      * @param amount the amount to set
459      * @return a new Date object set with the specified value
460      * @throws IllegalArgumentException if the date is null
461      * @since 2.4
462      */
463     public static Date setDays(Date date, int amount) {
464         return set(date, Calendar.DAY_OF_MONTH, amount);
465     }
466 
467     //-----------------------------------------------------------------------
468     /**
469      * Sets the hours field to a date returning a new object.  Hours range 
470      * from  0-23.
471      * The original date object is unchanged.
472      *
473      * @param date  the date, not null
474      * @param amount the amount to set
475      * @return a new Date object set with the specified value
476      * @throws IllegalArgumentException if the date is null
477      * @since 2.4
478      */
479     public static Date setHours(Date date, int amount) {
480         return set(date, Calendar.HOUR_OF_DAY, amount);
481     }
482 
483     //-----------------------------------------------------------------------
484     /**
485      * Sets the minute field to a date returning a new object.
486      * The original date object is unchanged.
487      *
488      * @param date  the date, not null
489      * @param amount the amount to set
490      * @return a new Date object set with the specified value
491      * @throws IllegalArgumentException if the date is null
492      * @since 2.4
493      */
494     public static Date setMinutes(Date date, int amount) {
495         return set(date, Calendar.MINUTE, amount);
496     }
497     
498     //-----------------------------------------------------------------------
499     /**
500      * Sets the seconds field to a date returning a new object.
501      * The original date object is unchanged.
502      *
503      * @param date  the date, not null
504      * @param amount the amount to set
505      * @return a new Date object set with the specified value
506      * @throws IllegalArgumentException if the date is null
507      * @since 2.4
508      */
509     public static Date setSeconds(Date date, int amount) {
510         return set(date, Calendar.SECOND, amount);
511     }
512 
513     //-----------------------------------------------------------------------
514     /**
515      * Sets the miliseconds field to a date returning a new object.
516      * The original date object is unchanged.
517      *
518      * @param date  the date, not null
519      * @param amount the amount to set
520      * @return a new Date object set with the specified value
521      * @throws IllegalArgumentException if the date is null
522      * @since 2.4
523      */
524     public static Date setMilliseconds(Date date, int amount) {
525         return set(date, Calendar.MILLISECOND, amount);
526     } 
527     
528     //-----------------------------------------------------------------------
529     /**
530      * Sets the specified field to a date returning a new object.  
531      * This does not use a lenient calendar.
532      * The original date object is unchanged.
533      *
534      * @param date  the date, not null
535      * @param calendarField  the calendar field to set the amount to
536      * @param amount the amount to set
537      * @return a new Date object set with the specified value
538      * @throws IllegalArgumentException if the date is null
539      * @since 2.4
540      */
541     private static Date set(Date date, int calendarField, int amount) {
542         if (date == null) {
543             throw new IllegalArgumentException("The date must not be null");
544         }
545         // getInstance() returns a new object, so this method is thread safe.
546         Calendar c = Calendar.getInstance();
547         c.setLenient(false);
548         c.setTime(date);
549         c.set(calendarField, amount);
550         return c.getTime();
551     }   
552     
553     //-----------------------------------------------------------------------
554     /**
555      * <p>Round this date, leaving the field specified as the most
556      * significant field.</p>
557      *
558      * <p>For example, if you had the datetime of 28 Mar 2002
559      * 13:45:01.231, if this was passed with HOUR, it would return
560      * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
561      * would return 1 April 2002 0:00:00.000.</p>
562      * 
563      * <p>For a date in a timezone that handles the change to daylight
564      * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
565      * Suppose daylight saving time begins at 02:00 on March 30. Rounding a 
566      * date that crosses this time would produce the following values:
567      * <ul>
568      * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
569      * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
570      * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
571      * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
572      * </ul>
573      * </p>
574      * 
575      * @param date  the date to work with
576      * @param field  the field from <code>Calendar</code>
577      *  or <code>SEMI_MONTH</code>
578      * @return the rounded date
579      * @throws IllegalArgumentException if the date is <code>null</code>
580      * @throws ArithmeticException if the year is over 280 million
581      */
582     public static Date round(Date date, int field) {
583         if (date == null) {
584             throw new IllegalArgumentException("The date must not be null");
585         }
586         Calendar gval = Calendar.getInstance();
587         gval.setTime(date);
588         modify(gval, field, true);
589         return gval.getTime();
590     }
591 
592     /**
593      * <p>Round this date, leaving the field specified as the most
594      * significant field.</p>
595      *
596      * <p>For example, if you had the datetime of 28 Mar 2002
597      * 13:45:01.231, if this was passed with HOUR, it would return
598      * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
599      * would return 1 April 2002 0:00:00.000.</p>
600      * 
601      * <p>For a date in a timezone that handles the change to daylight
602      * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
603      * Suppose daylight saving time begins at 02:00 on March 30. Rounding a 
604      * date that crosses this time would produce the following values:
605      * <ul>
606      * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
607      * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
608      * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
609      * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
610      * </ul>
611      * </p>
612      * 
613      * @param date  the date to work with
614      * @param field  the field from <code>Calendar</code>
615      *  or <code>SEMI_MONTH</code>
616      * @return the rounded date (a different object)
617      * @throws IllegalArgumentException if the date is <code>null</code>
618      * @throws ArithmeticException if the year is over 280 million
619      */
620     public static Calendar round(Calendar date, int field) {
621         if (date == null) {
622             throw new IllegalArgumentException("The date must not be null");
623         }
624         Calendar rounded = (Calendar) date.clone();
625         modify(rounded, field, true);
626         return rounded;
627     }
628 
629     /**
630      * <p>Round this date, leaving the field specified as the most
631      * significant field.</p>
632      *
633      * <p>For example, if you had the datetime of 28 Mar 2002
634      * 13:45:01.231, if this was passed with HOUR, it would return
635      * 28 Mar 2002 14:00:00.000. If this was passed with MONTH, it
636      * would return 1 April 2002 0:00:00.000.</p>
637      * 
638      * <p>For a date in a timezone that handles the change to daylight
639      * saving time, rounding to Calendar.HOUR_OF_DAY will behave as follows.
640      * Suppose daylight saving time begins at 02:00 on March 30. Rounding a 
641      * date that crosses this time would produce the following values:
642      * <ul>
643      * <li>March 30, 2003 01:10 rounds to March 30, 2003 01:00</li>
644      * <li>March 30, 2003 01:40 rounds to March 30, 2003 03:00</li>
645      * <li>March 30, 2003 02:10 rounds to March 30, 2003 03:00</li>
646      * <li>March 30, 2003 02:40 rounds to March 30, 2003 04:00</li>
647      * </ul>
648      * </p>
649      * 
650      * @param date  the date to work with, either Date or Calendar
651      * @param field  the field from <code>Calendar</code>
652      *  or <code>SEMI_MONTH</code>
653      * @return the rounded date
654      * @throws IllegalArgumentException if the date is <code>null</code>
655      * @throws ClassCastException if the object type is not a <code>Date</code>
656      *  or <code>Calendar</code>
657      * @throws ArithmeticException if the year is over 280 million
658      */
659     public static Date round(Object date, int field) {
660         if (date == null) {
661             throw new IllegalArgumentException("The date must not be null");
662         }
663         if (date instanceof Date) {
664             return round((Date) date, field);
665         } else if (date instanceof Calendar) {
666             return round((Calendar) date, field).getTime();
667         } else {
668             throw new ClassCastException("Could not round " + date);
669         }
670     }
671 
672     //-----------------------------------------------------------------------
673     /**
674      * <p>Truncate this date, leaving the field specified as the most
675      * significant field.</p>
676      *
677      * <p>For example, if you had the datetime of 28 Mar 2002
678      * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
679      * 2002 13:00:00.000.  If this was passed with MONTH, it would
680      * return 1 Mar 2002 0:00:00.000.</p>
681      * 
682      * @param date  the date to work with
683      * @param field  the field from <code>Calendar</code>
684      *  or <code>SEMI_MONTH</code>
685      * @return the rounded date
686      * @throws IllegalArgumentException if the date is <code>null</code>
687      * @throws ArithmeticException if the year is over 280 million
688      */
689     public static Date truncate(Date date, int field) {
690         if (date == null) {
691             throw new IllegalArgumentException("The date must not be null");
692         }
693         Calendar gval = Calendar.getInstance();
694         gval.setTime(date);
695         modify(gval, field, false);
696         return gval.getTime();
697     }
698 
699     /**
700      * <p>Truncate this date, leaving the field specified as the most
701      * significant field.</p>
702      *
703      * <p>For example, if you had the datetime of 28 Mar 2002
704      * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
705      * 2002 13:00:00.000.  If this was passed with MONTH, it would
706      * return 1 Mar 2002 0:00:00.000.</p>
707      * 
708      * @param date  the date to work with
709      * @param field  the field from <code>Calendar</code>
710      *  or <code>SEMI_MONTH</code>
711      * @return the rounded date (a different object)
712      * @throws IllegalArgumentException if the date is <code>null</code>
713      * @throws ArithmeticException if the year is over 280 million
714      */
715     public static Calendar truncate(Calendar date, int field) {
716         if (date == null) {
717             throw new IllegalArgumentException("The date must not be null");
718         }
719         Calendar truncated = (Calendar) date.clone();
720         modify(truncated, field, false);
721         return truncated;
722     }
723 
724     /**
725      * <p>Truncate this date, leaving the field specified as the most
726      * significant field.</p>
727      *
728      * <p>For example, if you had the datetime of 28 Mar 2002
729      * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
730      * 2002 13:00:00.000.  If this was passed with MONTH, it would
731      * return 1 Mar 2002 0:00:00.000.</p>
732      * 
733      * @param date  the date to work with, either <code>Date</code>
734      *  or <code>Calendar</code>
735      * @param field  the field from <code>Calendar</code>
736      *  or <code>SEMI_MONTH</code>
737      * @return the rounded date
738      * @throws IllegalArgumentException if the date
739      *  is <code>null</code>
740      * @throws ClassCastException if the object type is not a
741      *  <code>Date</code> or <code>Calendar</code>
742      * @throws ArithmeticException if the year is over 280 million
743      */
744     public static Date truncate(Object date, int field) {
745         if (date == null) {
746             throw new IllegalArgumentException("The date must not be null");
747         }
748         if (date instanceof Date) {
749             return truncate((Date) date, field);
750         } else if (date instanceof Calendar) {
751             return truncate((Calendar) date, field).getTime();
752         } else {
753             throw new ClassCastException("Could not truncate " + date);
754         }
755     }
756 
757     //-----------------------------------------------------------------------
758     /**
759      * <p>Internal calculation method.</p>
760      * 
761      * @param val  the calendar
762      * @param field  the field constant
763      * @param round  true to round, false to truncate
764      * @throws ArithmeticException if the year is over 280 million
765      */
766     private static void modify(Calendar val, int field, boolean round) {
767         if (val.get(Calendar.YEAR) > 280000000) {
768             throw new ArithmeticException("Calendar value too large for accurate calculations");
769         }
770         
771         if (field == Calendar.MILLISECOND) {
772             return;
773         }
774 
775         // ----------------- Fix for LANG-59 ---------------------- START ---------------
776         // see http://issues.apache.org/jira/browse/LANG-59
777         //
778         // Manually truncate milliseconds, seconds and minutes, rather than using
779         // Calendar methods.
780 
781         Date date = val.getTime();
782         long time = date.getTime();
783         boolean done = false;
784 
785         // truncate milliseconds
786         int millisecs = val.get(Calendar.MILLISECOND);
787         if (!round || millisecs < 500) {
788             time = time - millisecs;
789         }
790         if (field == Calendar.SECOND) {
791             done = true;
792         }
793 
794         // truncate seconds
795         int seconds = val.get(Calendar.SECOND);
796         if (!done && (!round || seconds < 30)) {
797             time = time - (seconds * 1000L);
798         }
799         if (field == Calendar.MINUTE) {
800             done = true;
801         }
802 
803         // truncate minutes
804         int minutes = val.get(Calendar.MINUTE);
805         if (!done && (!round || minutes < 30)) {
806             time = time - (minutes * 60000L);
807         }
808 
809         // reset time
810         if (date.getTime() != time) {
811             date.setTime(time);
812             val.setTime(date);
813         }
814         // ----------------- Fix for LANG-59 ----------------------- END ----------------
815 
816         boolean roundUp = false;
817         for (int i = 0; i < fields.length; i++) {
818             for (int j = 0; j < fields[i].length; j++) {
819                 if (fields[i][j] == field) {
820                     //This is our field... we stop looping
821                     if (round && roundUp) {
822                         if (field == DateUtils.SEMI_MONTH) {
823                             //This is a special case that's hard to generalize
824                             //If the date is 1, we round up to 16, otherwise
825                             //  we subtract 15 days and add 1 month
826                             if (val.get(Calendar.DATE) == 1) {
827                                 val.add(Calendar.DATE, 15);
828                             } else {
829                                 val.add(Calendar.DATE, -15);
830                                 val.add(Calendar.MONTH, 1);
831                             }
832                         } else {
833                             //We need at add one to this field since the
834                             //  last number causes us to round up
835                             val.add(fields[i][0], 1);
836                         }
837                     }
838                     return;
839                 }
840             }
841             //We have various fields that are not easy roundings
842             int offset = 0;
843             boolean offsetSet = false;
844             //These are special types of fields that require different rounding rules
845             switch (field) {
846                 case DateUtils.SEMI_MONTH:
847                     if (fields[i][0] == Calendar.DATE) {
848                         //If we're going to drop the DATE field's value,
849                         //  we want to do this our own way.
850                         //We need to subtrace 1 since the date has a minimum of 1
851                         offset = val.get(Calendar.DATE) - 1;
852                         //If we're above 15 days adjustment, that means we're in the
853                         //  bottom half of the month and should stay accordingly.
854                         if (offset >= 15) {
855                             offset -= 15;
856                         }
857                         //Record whether we're in the top or bottom half of that range
858                         roundUp = offset > 7;
859                         offsetSet = true;
860                     }
861                     break;
862                 case Calendar.AM_PM:
863                     if (fields[i][0] == Calendar.HOUR_OF_DAY) {
864                         //If we're going to drop the HOUR field's value,
865                         //  we want to do this our own way.
866                         offset = val.get(Calendar.HOUR_OF_DAY);
867                         if (offset >= 12) {
868                             offset -= 12;
869                         }
870                         roundUp = offset > 6;
871                         offsetSet = true;
872                     }
873                     break;
874             }
875             if (!offsetSet) {
876                 int min = val.getActualMinimum(fields[i][0]);
877                 int max = val.getActualMaximum(fields[i][0]);
878                 //Calculate the offset from the minimum allowed value
879                 offset = val.get(fields[i][0]) - min;
880                 //Set roundUp if this is more than half way between the minimum and maximum
881                 roundUp = offset > ((max - min) / 2);
882             }
883             //We need to remove this field
884             if (offset != 0) {
885                 val.set(fields[i][0], val.get(fields[i][0]) - offset);
886             }
887         }
888         throw new IllegalArgumentException("The field " + field + " is not supported");
889 
890     }
891 
892     //-----------------------------------------------------------------------
893     /**
894      * <p>This constructs an <code>Iterator</code> over each day in a date
895      * range defined by a focus date and range style.</p>
896      *
897      * <p>For instance, passing Thursday, July 4, 2002 and a
898      * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
899      * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
900      * 2002, returning a Calendar instance for each intermediate day.</p>
901      *
902      * <p>This method provides an iterator that returns Calendar objects.
903      * The days are progressed using {@link Calendar#add(int, int)}.</p>
904      *
905      * @param focus  the date to work with, not null
906      * @param rangeStyle  the style constant to use. Must be one of
907      * {@link DateUtils#RANGE_MONTH_SUNDAY}, 
908      * {@link DateUtils#RANGE_MONTH_MONDAY},
909      * {@link DateUtils#RANGE_WEEK_SUNDAY},
910      * {@link DateUtils#RANGE_WEEK_MONDAY},
911      * {@link DateUtils#RANGE_WEEK_RELATIVE},
912      * {@link DateUtils#RANGE_WEEK_CENTER}
913      * @return the date iterator, which always returns Calendar instances
914      * @throws IllegalArgumentException if the date is <code>null</code>
915      * @throws IllegalArgumentException if the rangeStyle is invalid
916      */
917     public static Iterator iterator(Date focus, int rangeStyle) {
918         if (focus == null) {
919             throw new IllegalArgumentException("The date must not be null");
920         }
921         Calendar gval = Calendar.getInstance();
922         gval.setTime(focus);
923         return iterator(gval, rangeStyle);
924     }
925 
926     /**
927      * <p>This constructs an <code>Iterator</code> over each day in a date
928      * range defined by a focus date and range style.</p>
929      *
930      * <p>For instance, passing Thursday, July 4, 2002 and a
931      * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
932      * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
933      * 2002, returning a Calendar instance for each intermediate day.</p>
934      *
935      * <p>This method provides an iterator that returns Calendar objects.
936      * The days are progressed using {@link Calendar#add(int, int)}.</p>
937      *
938      * @param focus  the date to work with
939      * @param rangeStyle  the style constant to use. Must be one of
940      * {@link DateUtils#RANGE_MONTH_SUNDAY}, 
941      * {@link DateUtils#RANGE_MONTH_MONDAY},
942      * {@link DateUtils#RANGE_WEEK_SUNDAY},
943      * {@link DateUtils#RANGE_WEEK_MONDAY},
944      * {@link DateUtils#RANGE_WEEK_RELATIVE},
945      * {@link DateUtils#RANGE_WEEK_CENTER}
946      * @return the date iterator
947      * @throws IllegalArgumentException if the date is <code>null</code>
948      * @throws IllegalArgumentException if the rangeStyle is invalid
949      */
950     public static Iterator iterator(Calendar focus, int rangeStyle) {
951         if (focus == null) {
952             throw new IllegalArgumentException("The date must not be null");
953         }
954         Calendar start = null;
955         Calendar end = null;
956         int startCutoff = Calendar.SUNDAY;
957         int endCutoff = Calendar.SATURDAY;
958         switch (rangeStyle) {
959             case RANGE_MONTH_SUNDAY:
960             case RANGE_MONTH_MONDAY:
961                 //Set start to the first of the month
962                 start = truncate(focus, Calendar.MONTH);
963                 //Set end to the last of the month
964                 end = (Calendar) start.clone();
965                 end.add(Calendar.MONTH, 1);
966                 end.add(Calendar.DATE, -1);
967                 //Loop start back to the previous sunday or monday
968                 if (rangeStyle == RANGE_MONTH_MONDAY) {
969                     startCutoff = Calendar.MONDAY;
970                     endCutoff = Calendar.SUNDAY;
971                 }
972                 break;
973             case RANGE_WEEK_SUNDAY:
974             case RANGE_WEEK_MONDAY:
975             case RANGE_WEEK_RELATIVE:
976             case RANGE_WEEK_CENTER:
977                 //Set start and end to the current date
978                 start = truncate(focus, Calendar.DATE);
979                 end = truncate(focus, Calendar.DATE);
980                 switch (rangeStyle) {
981                     case RANGE_WEEK_SUNDAY:
982                         //already set by default
983                         break;
984                     case RANGE_WEEK_MONDAY:
985                         startCutoff = Calendar.MONDAY;
986                         endCutoff = Calendar.SUNDAY;
987                         break;
988                     case RANGE_WEEK_RELATIVE:
989                         startCutoff = focus.get(Calendar.DAY_OF_WEEK);
990                         endCutoff = startCutoff - 1;
991                         break;
992                     case RANGE_WEEK_CENTER:
993                         startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
994                         endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
995                         break;
996                 }
997                 break;
998             default:
999                 throw new IllegalArgumentException("The range style " + rangeStyle + " is not valid.");
1000         }
1001         if (startCutoff < Calendar.SUNDAY) {
1002             startCutoff += 7;
1003         }
1004         if (startCutoff > Calendar.SATURDAY) {
1005             startCutoff -= 7;
1006         }
1007         if (endCutoff < Calendar.SUNDAY) {
1008             endCutoff += 7;
1009         }
1010         if (endCutoff > Calendar.SATURDAY) {
1011             endCutoff -= 7;
1012         }
1013         while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
1014             start.add(Calendar.DATE, -1);
1015         }
1016         while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
1017             end.add(Calendar.DATE, 1);
1018         }
1019         return new DateIterator(start, end);
1020     }
1021 
1022     /**
1023      * <p>This constructs an <code>Iterator</code> over each day in a date
1024      * range defined by a focus date and range style.</p>
1025      *
1026      * <p>For instance, passing Thursday, July 4, 2002 and a
1027      * <code>RANGE_MONTH_SUNDAY</code> will return an <code>Iterator</code>
1028      * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
1029      * 2002, returning a Calendar instance for each intermediate day.</p>
1030      *
1031      * @param focus  the date to work with, either
1032      *  <code>Date</code> or <code>Calendar</code>
1033      * @param rangeStyle  the style constant to use. Must be one of the range
1034      * styles listed for the {@link #iterator(Calendar, int)} method.
1035      * @return the date iterator
1036      * @throws IllegalArgumentException if the date
1037      *  is <code>null</code>
1038      * @throws ClassCastException if the object type is
1039      *  not a <code>Date</code> or <code>Calendar</code>
1040      */
1041     public static Iterator iterator(Object focus, int rangeStyle) {
1042         if (focus == null) {
1043             throw new IllegalArgumentException("The date must not be null");
1044         }
1045         if (focus instanceof Date) {
1046             return iterator((Date) focus, rangeStyle);
1047         } else if (focus instanceof Calendar) {
1048             return iterator((Calendar) focus, rangeStyle);
1049         } else {
1050             throw new ClassCastException("Could not iterate based on " + focus);
1051         }
1052     }
1053     
1054     /**
1055      * <p>Returns the number of milliseconds within the 
1056      * fragment. All datefields greater than the fragment will be ignored.</p>
1057      * 
1058      * <p>Asking the milliseconds of any date will only return the number of milliseconds
1059      * of the current second (resulting in a number between 0 and 999). This 
1060      * method will retrieve the number of milliseconds for any fragment. 
1061      * For example, if you want to calculate the number of milliseconds past today, 
1062      * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
1063      * be all milliseconds of the past hour(s), minutes(s) and second(s).</p>
1064      * 
1065      * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both 
1066      * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY, 
1067      * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
1068      * A fragment less than or equal to a SECOND field will return 0.</p> 
1069      * 
1070      * <p>
1071      * <ul>
1072      *  <li>January 1, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
1073      *  <li>January 6, 2008 7:15:10.538 with Calendar.SECOND as fragment will return 538</li>
1074      *  <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10538 (10*1000 + 538)</li>
1075      *  <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
1076      *   (a millisecond cannot be split in milliseconds)</li>
1077      * </ul>
1078      * </p>
1079      * 
1080      * @param date the date to work with, not null
1081      * @param fragment the Calendar field part of date to calculate 
1082      * @return number of milliseconds within the fragment of date
1083      * @throws IllegalArgumentException if the date is <code>null</code> or
1084      * fragment is not supported
1085      * @since 2.4
1086      */
1087     public static long getFragmentInMilliseconds(Date date, int fragment) {
1088         return getFragment(date, fragment, Calendar.MILLISECOND);    
1089     }
1090     
1091     /**
1092      * <p>Returns the number of seconds within the 
1093      * fragment. All datefields greater than the fragment will be ignored.</p> 
1094      * 
1095      * <p>Asking the seconds of any date will only return the number of seconds
1096      * of the current minute (resulting in a number between 0 and 59). This 
1097      * method will retrieve the number of seconds for any fragment. 
1098      * For example, if you want to calculate the number of seconds past today, 
1099      * your fragment is Calendar.DATE or Calendar.DAY_OF_YEAR. The result will
1100      * be all seconds of the past hour(s) and minutes(s).</p> 
1101      * 
1102      * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both 
1103      * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY, 
1104      * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
1105      * A fragment less than or equal to a SECOND field will return 0.</p> 
1106      * 
1107      * <p>
1108      * <ul>
1109      *  <li>January 1, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
1110      *   (equivalent to deprecated date.getSeconds())</li>
1111      *  <li>January 6, 2008 7:15:10.538 with Calendar.MINUTE as fragment will return 10
1112      *   (equivalent to deprecated date.getSeconds())</li>
1113      *  <li>January 6, 2008 7:15:10.538 with Calendar.DAY_OF_YEAR as fragment will return 26110
1114      *   (7*3600 + 15*60 + 10)</li>
1115      *  <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
1116      *   (a millisecond cannot be split in seconds)</li>
1117      * </ul>
1118      * </p>
1119      * 
1120      * @param date the date to work with, not null
1121      * @param fragment the Calendar field part of date to calculate 
1122      * @return number of seconds within the fragment of date
1123      * @throws IllegalArgumentException if the date is <code>null</code> or
1124      * fragment is not supported
1125      * @since 2.4
1126      */
1127     public static long getFragmentInSeconds(Date date, int fragment) {
1128         return getFragment(date, fragment, Calendar.SECOND);
1129     }
1130     
1131     /**
1132      * <p>Returns the number of minutes within the 
1133      * fragment. All datefields greater than the fragment will be ignored.</p> 
1134      * 
1135      * <p>Asking the minutes of any date will only return the number of minutes
1136      * of the current hour (resulting in a number between 0 and 59). This 
1137      * method will retrieve the number of minutes for any fragment. 
1138      * For example, if you want to calculate the number of minutes past this month, 
1139      * your fragment is Calendar.MONTH. The result will be all minutes of the 
1140      * past day(s) and hour(s).</p> 
1141      * 
1142      * <p>Valid fragments are: Calendar.YEAR, Calendar.MONTH, both 
1143      * Calendar.DAY_OF_YEAR and Calendar.DATE, Calendar.HOUR_OF_DAY, 
1144      * Calendar.MINUTE, Calendar.SECOND and Calendar.MILLISECOND
1145      * A fragment less than or equal to a MINUTE field will return 0.</p> 
1146      * 
1147      * <p>
1148      * <ul>
1149      *  <li>January 1, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
1150      *   (equivalent to deprecated date.getMinutes())</li>
1151      *  <li>January 6, 2008 7:15:10.538 with Calendar.HOUR_OF_DAY as fragment will return 15
1152      *   (equivalent to deprecated date.getMinutes())</li>
1153      *  <li>January 1, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 15</li>
1154      *  <li>January 6, 2008 7:15:10.538 with Calendar.MONTH as fragment will return 435 (7*60 + 15)</li>
1155      *  <li>January 16, 2008 7:15:10.538 with Calendar.MILLISECOND as fragment will return 0
1156      *   (a millisecond cannot be split in minutes)</li>
1157      * </ul>
1158      * </p>
1159      * 
1160      * @param date the date to work with, not null
1161      * @param fragment the Calendar field part of date to calculate