Collections Data Structure Java

import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
/**
 * 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.
 */
/**
 * 

A suite of utilities surrounding the use of the
 * {@link java.util.Calendar} and {@link java.util.Date} object.


 * 
 * 

DateUtils contains a lot of common methods considering manipulations
 * of Dates or Calendars. Some methods require some extra explanation.
 * The truncate and round methods could be considered the Math.floor(),
 * Math.ceil() or Math.round versions for dates
 * This way date-fields will be ignored in bottom-up order.
 * As a complement to these methods we've introduced some fragment-methods.
 * With these methods the Date-fields will be ignored in top-down order.
 * Since a date without a year is not a valid date, you have to decide in what
 * kind of date-field you want your result, for instance milliseconds or days.
 * 


 *   
 *   
 *
 * @author Serge Knystautas
 * @author Stephen Colebourne
 * @author Janek Bogucki
 * @author Gary Gregory
 * @author Phil Steitz
 * @author Robert Scholte
 * @since 2.0
 * @version $Id: DateUtils.java 634096 2008-03-06 00:58:11Z niallp $
 */
public class Main {
  
  /**
   * 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;
  //-----------------------------------------------------------------------
  /**
   * 

This constructs an Iterator over each day in a date
   * range defined by a focus date and range style.


   *
   * 

For instance, passing Thursday, July 4, 2002 and a
   * RANGE_MONTH_SUNDAY will return an Iterator
   * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
   * 2002, returning a Calendar instance for each intermediate day.


   *
   * 

This method provides an iterator that returns Calendar objects.
   * The days are progressed using {@link Calendar#add(int, int)}.


   *
   * @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 null
   * @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);
  }
  /**
   * 

This constructs an Iterator over each day in a date
   * range defined by a focus date and range style.


   *
   * 

For instance, passing Thursday, July 4, 2002 and a
   * RANGE_MONTH_SUNDAY will return an Iterator
   * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
   * 2002, returning a Calendar instance for each intermediate day.


   *
   * 

This method provides an iterator that returns Calendar objects.
   * The days are progressed using {@link Calendar#add(int, int)}.


   *
   * @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 null
   * @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);
  }
  /**
   * 

This constructs an Iterator over each day in a date
   * range defined by a focus date and range style.


   *
   * 

For instance, passing Thursday, July 4, 2002 and a
   * RANGE_MONTH_SUNDAY will return an Iterator
   * that starts with Sunday, June 30, 2002 and ends with Saturday, August 3,
   * 2002, returning a Calendar instance for each intermediate day.


   *
   * @param focus  the date to work with, either
   *  Date or Calendar
   * @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 null
   * @throws ClassCastException if the object type is
   *  not a Date or Calendar
   */
  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);
      }
  }
  /**
   * 

Truncate this date, leaving the field specified as the most
   * significant field.


   *
   * 

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.


   * 
   * @param date  the date to work with, either Date
   *  or Calendar
   * @param field  the field from Calendar
   *  or SEMI_MONTH
   * @return the rounded date
   * @throws IllegalArgumentException if the date
   *  is null
   * @throws ClassCastException if the object type is not a
   *  Date or Calendar
   * @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);
      }
  }
  /**
   * 

Truncate this date, leaving the field specified as the most
   * significant field.


   *
   * 

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.


   * 
   * @param date  the date to work with
   * @param field  the field from Calendar
   *  or SEMI_MONTH
   * @return the rounded date (a different object)
   * @throws IllegalArgumentException if the date is null
   * @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;
  }
  //-----------------------------------------------------------------------
  /**
   * 

Internal calculation method.


   * 
   * @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");
  }
  /**
   * 

Date iterator.


   */
  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 true 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 RuntimeException();
          }
          spot.add(Calendar.DATE, 1);
          return spot.clone();
      }
      /**
       * Always throws UnsupportedOperationException.
       * 
       * @throws UnsupportedOperationException
       * @see java.util.Iterator#remove()
       */
      public void remove() {
          throw new UnsupportedOperationException();
      }
  }
}