Mega Code Archive

 
Categories / Java / Development Class
 

A time-less Date class for basic date arithmetics

/*  * (C) 2004 - Geotechnical Software Services  *   * This code is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public   * License as published by the Free Software Foundation; either   * version 2.1 of the License, or (at your option) any later version.  *  * This code is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the  * GNU Lesser General Public License for more details.  *  * You should have received a copy of the GNU Lesser General Public   * License along with this program; if not, write to the Free   * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,   * MA  02111-1307, USA.  */ //package no.geosoft.cc.util; import java.util.Date; import java.util.Calendar; import java.util.TimeZone; import java.io.Serializable; /**  * A time-less Date class for basic date arithmetics.  *  * Thanks to Paul Hill @ xmission.com for valuable contribution.  *  * @author  Jacob Dreyer (<a href="mailto:jacob.dreyer@geosoft.no">jacob.dreyer@geosoft.no</a>)  */    public class Day   implements Comparable, Cloneable, Serializable {   protected Calendar  calendar_;        /**    * Initialize the internal calendar instance.    *     * @param year        Year of new day.    * @param month       Month of new day.    * @param dayOfMonth  Day of month of new day.    */   private void initialize (int year, int month, int dayOfMonth)   {     calendar_ = Calendar.getInstance();     calendar_.setLenient (true);     calendar_.setFirstDayOfWeek (Calendar.MONDAY);     calendar_.setTimeZone (TimeZone.getTimeZone ("GMT"));     set (year, month, dayOfMonth);   }             /**    * Create a new day.    * The day is lenient meaning that illegal day parameters can be    * specified and results in a recomputed day with legal month/day    * values.    *    * @param year        Year of new day.    * @param month       Month of new day (0-11)    * @param dayOfMonth  Day of month of new day (1-31)    */   public Day (int year, int month, int dayOfMonth)   {     initialize (year, month, dayOfMonth);   }   /**    * Create a new day, specifying the year and the day of year.    * The day is lenient meaning that illegal day parameters can be    * specified and results in a recomputed day with legal month/day    * values.    *     * @param year       Year of new day.    * @param dayOfYear  1=January 1, etc.    */   public Day (int year, int dayOfYear)   {     initialize (year, Calendar.JANUARY, 1);     calendar_.set (Calendar.DAY_OF_YEAR, dayOfYear);   }             /**    * Create a new day representing the day of creation    * (according to the setting of the current machine).    */   public Day()   {     // Now (in the currenct locale of the client machine)     Calendar calendar = Calendar.getInstance();     // Prune time part     initialize (calendar.get (Calendar.YEAR),                 calendar.get (Calendar.MONTH),                 calendar.get (Calendar.DAY_OF_MONTH));   }        /**    * Create a new day based on a java.util.Calendar instance.    * NOTE: The time component from calendar will be pruned.    *     * @param calendar  Calendar instance to copy.    */   public Day (Calendar calendar)   {     this (calendar.get (Calendar.YEAR),           calendar.get (Calendar.MONTH),           calendar.get (Calendar.DAY_OF_MONTH));   }             /**    * Create a new day based on a java.util.Date instance.    * NOTE: The time component from date will be pruned.         *     * @param date  Date instance to copy.    */   public Day (Date date)   {     // Create a calendar based on given date     Calendar calendar = Calendar.getInstance();     calendar.setTime (date);     // Extract date values and use these only     initialize (calendar.get (Calendar.YEAR),                 calendar.get (Calendar.MONTH),                 calendar.get (Calendar.DAY_OF_MONTH));   }   /**    * Create a new day based on a time value.    * Time is milliseconds since "the Epoch" (1.1.1970).    * NOTE: The time component from time will be pruned.         *     * @param time  Milliseconds since "the Epoch".    */   public Day (long time)   {     this (new Date (time));   }        /**    * Create a new day as a copy of the specified day.    *     * @param day  Day to clone.    */   public Day (Day day)   {     this (day.getYear(), day.getMonth(), day.getDayOfMonth());   }        /**    * Create a clone of this day.    *     * @return  This day cloned.    */   public Object clone()   {     return new Day (this);   }   /**    * A more explicit front-end to the Day() constructor which return a day    * object representing the day of creation.    *     * @return  A day instance representing today.    */   public static Day today()   {     return new Day();   }            /**    * Return a Calendar instance representing the same day    * as this instance. For use by secondary methods requiring    * java.util.Calendar as input.    *     * @return  Calendar equivalent representing this day.    */   public Calendar getCalendar()   {     return (Calendar) calendar_.clone();   }        /**    * Return a Date instance representing the same date    * as this instance. For use by secondary methods requiring    * java.util.Date as input.    *     * @return  Date equivalent representing this day.    */   public Date getDate()   {     return getCalendar().getTime();   }             /**    * Compare this day to the specified day. If object is    * not of type Day a ClassCastException is thrown.    *     * @param object  Day object to compare to.    * @return        @see Comparable#compareTo(Object)    * @throws ClassCastException  If object is not of type Day.    */   public int compareTo (Object object)   {     Day day = (Day) object;     return calendar_.getTime().compareTo (day.calendar_.getTime());   }        /**    * Return true if this day is after the specified day.    *     * @param date  Day to compare to.    * @return      True if this is after day, false otherwise.    */   public boolean isAfter (Day day)   {     return calendar_.after (day.calendar_);   }        /**    * Return true if this day is before the specified day.    *     * @param date  Day to compare to.    * @return      True if this is before day, false otherwise.    */   public boolean isBefore (Day day)   {     return calendar_.before (day.calendar_);   }   /**    * Return true if this day equals (represent the same date)    * as the specified day.    *     * @param date  Day to compare to.    * @return      True if this equals day, false otherwise.    */   public boolean equals (Day day)   {     return calendar_.equals (day.calendar_);   }   /**    * Overload required as default definition of equals() has changed.    *     * @return  A hash code value for this object.    */   public int hashCode()   {     return calendar_.hashCode();   }         /**    * Set date of this day.    * The day is lenient meaning that illegal day parameters can be    * specified and results in a recomputed day with legal month/day    * values.    *     * @param year        Year of this day.    * @param month       Month of this day (0-11).    * @param dayOfMonth  Day of month of this day (1-31).    */   public void set (int year, int month, int dayOfMonth)   {     setYear (year);     setMonth (month);     setDayOfMonth (dayOfMonth);   }                  /**    * Return year of this day.    *     * @return  Year of this day.    */   public int getYear()   {     return calendar_.get (Calendar.YEAR);   }   /**    * Set year of this day.    *     * @param year  New year of this day.    */   public void setYear (int year)   {     calendar_.set (Calendar.YEAR, year);   }   /**    * Return month of this day. The result must be compared to Calendar.JANUARY,    * Calendar.FEBRUARY, etc.    *     * @return  Month of this day.    */   public int getMonth()   {     return calendar_.get (Calendar.MONTH);   }      /**    * Return the 1-based month number of the month of this day.    * 1 = January, 2 = February and so on.    *     * @return Month number of this month    */   public int getMonthNo()   {     // It is tempting to return getMonth() + 1 but this is conceptually     // wrong, as Calendar month is an enumeration and the values are tags     // only and can be anything.     switch (getMonth()) {       case Calendar.JANUARY   : return 1;       case Calendar.FEBRUARY  : return 2;       case Calendar.MARCH     : return 3;                       case Calendar.APRIL     : return 4;       case Calendar.MAY       : return 5;                       case Calendar.JUNE      : return 6;       case Calendar.JULY      : return 7;                       case Calendar.AUGUST    : return 8;       case Calendar.SEPTEMBER : return 9;                       case Calendar.OCTOBER   : return 10;       case Calendar.NOVEMBER  : return 11;                       case Calendar.DECEMBER  : return 12;     }     // This will never happen     return 0;   }           /**    * Set month of this day. January = 0, February = 1, etc.    * Illegal month values will result in a recomputation of    * year and a resetting of month to a valid value.    * I.e. setMonth(20), will add 1 year to day and set month    * to 8.    *     * @param month  New month of this day.    */   public void setMonth (int month)   {     calendar_.set (Calendar.MONTH, month);   }   /**    * Return day of month of this day.    * NOTE: First day of month is 1 (not 0).    *     * @return  Day of month of this day.    */   public int getDayOfMonth()   {     return calendar_.get (Calendar.DAY_OF_MONTH);   }        /**    * Set day of month of this date. 1=1st  2=2nd, etc.    * Illegal day values will result in a recomputation of    * month/year and a resetting of day to a valid value.    * I.e. setDayOfMonth(33), will add 1 month to date and    * set day to 5, 4, 3 or 2 depending on month/year.    *     * @param dayOfMonth  New day of month of this day.    */   public void setDayOfMonth (int dayOfMonth)   {     calendar_.set (Calendar.DAY_OF_MONTH, dayOfMonth);   }        /**    * Return the day number of year this day represents.    * January 1 = 1 and so on.    *     * @return day number of year.    */   public int getDayOfYear()   {     return calendar_.get (Calendar.DAY_OF_YEAR);   }   /**    * Return the day of week of this day.    * NOTE: Must be compared to Calendar.MONDAY, TUSEDAY etc.    *     * @return  Day of week of this day.    */   public int getDayOfWeek()   {     return calendar_.get (Calendar.DAY_OF_WEEK);   }             /**    * Return the day number of week of this day, where    * Monday=1, Tuesday=2, ... Sunday=7.    *     * @return  Day number of week of this day.    */   public int getDayNumberOfWeek()   {     return getDayOfWeek() == Calendar.SUNDAY ?                            7 : getDayOfWeek() - Calendar.SUNDAY;   }             /**    * Return the week number of year, this day    * belongs to. 1st=1 and so on.    *     * @return  Week number of year of this day.    */   public int getWeekOfYear()   {     return calendar_.get (Calendar.WEEK_OF_YEAR);   }             /**    * Add a number of days to this day. Subtracting a number of    * days can be done by a negative argument to addDays() or calling    * subtractDays() explicitly.    *     * @param nDays  Number of days to add.    */   public void addDays (int nDays)   {     calendar_.add (Calendar.DAY_OF_MONTH, nDays);   }      /**    * Subtract a number of days from this day    *     * @param nDays  Number of days to subtract.    */   public void subtractDays (int nDays)   {     addDays (-nDays);   }           /**    * Add a number of months to this day. The actual number of days added    * depends on the staring day. Subtracting a number of months can be done    * by a negative argument to addMonths() or calling subtactMonths()    * explicitly.    * NOTE: addMonth(n) m times will in general give a different result    * than addMonth(m*n). Add 1 month to January 31, 2005 will give    * February 28, 2005.    *     * @param nMonths  Number of months to add.    */   public void addMonths (int nMonths)   {     calendar_.add (Calendar.MONTH, nMonths);   }             /**    * Subtract a number of months from this day    * @see #addMonths(int).    *     * @param nDays  Number of days to subtract.    */   public void subtractMonths (int nMonths)   {     addMonths (-nMonths);   }           /**    * Add a number of years to this day. The actual     * number of days added depends on the starting day.    * Subtracting a number of years can be done by a negative argument to    * addYears() or calling subtractYears explicitly.    *     * @param nYears  Number of years to add.    */   public void addYears (int nYears)   {     calendar_.add (Calendar.YEAR, nYears);   }        /**    * Subtract a number of years from this day    * @see #addYears(int).    *     * @param nYears  Number of years to subtract.    */   public void subtractYears (int nYears)   {     addYears (-nYears);   }           /**    * Return the number of days in the year of this day.    *     * @return  Number of days in this year.    */   public int getDaysInYear()   {     return calendar_.getActualMaximum (Calendar.DAY_OF_YEAR);   }   /**    * Return true if the year of this day is a leap year.    *     * @return  True if this year is a leap year, false otherwise.    */   public boolean isLeapYear()   {     return getDaysInYear() == calendar_.getMaximum (Calendar.DAY_OF_YEAR);   }   /**    * Return true if the specified year is a leap year.    *     * @param year  Year to check.    * @return      True if specified year is leap year, false otherwise.    */   public static boolean isLeapYear (int year)   {     return (new Day (year, Calendar.JANUARY, 1)).isLeapYear();   }         /**    * Return the number of days in the month of this day.    *     * @return  Number of days in this month.    */   public int getDaysInMonth()   {     return calendar_.getActualMaximum (Calendar.DAY_OF_MONTH);   }      /**    * Get default locale name of this day ("Monday", "Tuesday", etc.    *     * @return  Name of day.    */   public String getDayName()   {     switch (getDayOfWeek()) {       case Calendar.MONDAY    : return "Monday";       case Calendar.TUESDAY   : return "Tuesday";               case Calendar.WEDNESDAY : return "Wednesday";       case Calendar.THURSDAY  : return "Thursday";               case Calendar.FRIDAY    : return "Friday";       case Calendar.SATURDAY  : return "Saturday";               case Calendar.SUNDAY    : return "Sunday";     }     // This will never happen     return null;   }              /**    * Return number of days between two days.    * The method always returns a positive number of days.    *    * @param date  The day to compare to.    * @return      Number of days between this and day.    */   public int daysBetween (Day day)   {     long millisBetween = Math.abs (calendar_.getTime().getTime() -                                    day.calendar_.getTime().getTime());     return (int) Math.round (millisBetween / (1000 * 60 * 60 * 24));   }   /**    * Find the n'th xxxxday of s specified month (for instance find 1st sunday    * of May 2006; findNthOfMonth (1, Calendar.SUNDAY, Calendar.MAY, 2006);    * Return null if the specified day doesn't exists.    *     * @param n          Nth day to look for.    * @param dayOfWeek  Day to look for (Calendar.XXXDAY).    * @param month      Month to check (Calendar.XXX).    * @param year       Year to check.    * @return           Required Day (or null if non-existent)    * @throws           ArrayIndexOutOfBoundsException if dyaOfWeek parameter    *                   doesn't represent a valid day.    */   public static Day getNthOfMonth (int n, int dayOfWeek, int month, int year)     throws ArrayIndexOutOfBoundsException   {     // Validate the dayOfWeek argument     if (dayOfWeek < 0 || dayOfWeek > 6)       throw new ArrayIndexOutOfBoundsException (dayOfWeek);          Day first = new Day (year, month, 1);          int offset = dayOfWeek - first.getDayOfWeek();     if (offset < 0) offset = 7 + offset;          int dayNo = (n - 1) * 7 + offset + 1;          return dayNo > first.getDaysInMonth() ? null : new Day (year, month, dayNo);   }      /**    * Find the first of a specific day in a given month, for instance    * first Tuesday of May:    * getFirstOfMonth (Calendar.TUESDAY, Calendar.MAY, 2005);    *     * @param dayOfWeek  Weekday to get.    * @param month      Month of day to get.    * @param year       Year of day to get.    * @return           The requested day.    */   public static Day getFirstOfMonth (int dayOfWeek, int month, int year)   {     return Day.getNthOfMonth (1, dayOfWeek, month, year);   }         /**    * Find the last of a specific day in a given month, for instance    * last Tuesday of May:    * getLastOfMonth (Calendar.TUESDAY, Calendar.MAY, 2005);    *     * @param dayOfWeek  Weekday to get.    * @param month      Month of day to get.    * @param year       Year of day to get.    * @return           The requested day.    */   public static Day getLastOfMonth (int dayOfWeek, int month, int year)   {     Day day = Day.getNthOfMonth (5, dayOfWeek, month, year);     return day != null ? day : Day.getNthOfMonth (4, dayOfWeek, month, year);   }      /**    * Return a scratch string representation of this day.    * Used for debugging only. The format of the    * day is dd/mm-yyyy    *     * @return  A string representation of this day.    */   public String toString()   {     StringBuffer string = new StringBuffer();             if (getDayOfMonth() < 10) string.append ('0');     string.append (getDayOfMonth());     string.append ('/');     if (getMonth() < 9) string.append ('0');     string.append (getMonth()+1);     string.append ('-');     string.append (getYear());     string.append (" ");     string.append (getDayName());             return string.toString();   }      /**    * Testing this class.    *     * @param args  Not used.    */   public static void main (String[] args)   {     // This proves that there are 912 days between the two major     // terrorist attacks, not 911 as is common knowledge.     Day september11 = new Day (2001, Calendar.SEPTEMBER, 11);     Day march11     = new Day (2004, Calendar.MARCH,     11);     System.out.println (september11.daysBetween (march11));     // This proves that Kennedy was president for 1037 days,     // not 1000 as is the popular belief nor 1036 which is the     // bluffers reply. Nerds knows when to add one...     Day precidency   = new Day (1961, Calendar.JANUARY,  20);     Day assasination = new Day (1963, Calendar.NOVEMBER, 22);     System.out.println (precidency.daysBetween (assasination) + 1);     // Niel Armstrong walked the moon on a Sunday     Day nielOnMoon = new Day (1969, Calendar.JULY, 20);     System.out.println (nielOnMoon.getDayNumberOfWeek());     // Find last tuesdays for 2005     for (int i = 0; i < 12; i++) {       Day tuesday = Day.getLastOfMonth (Calendar.TUESDAY, i, 2005);       System.out.println (tuesday);     }   } }