Mega Code Archive

 
Categories / Android / Date Type
 

Represents a date using an integer, in a similar fashion to the implementation in Microsoft Excel

package app.test; /*   * AFreeChart : a free chart library for Android(tm) platform.  *              (based on JFreeChart and JCommon)  *   *  * (C) Copyright 2010, by Icom Systech Co., Ltd.  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.  *   * Project Info:  *    AFreeChart: http://code.google.com/p/afreechart/  *    JFreeChart: http://www.jfree.org/jfreechart/index.html  *    JCommon   : http://www.jfree.org/jcommon/index.html  *  * This program is free software: you can redistribute it and/or modify  * it under the terms of the GNU General Public License as published by  * the Free Software Foundation, either version 3 of the License, or  * (at your option) any later version.  *   * This program 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 General Public License for more details.  *   * You should have received a copy of the GNU General Public License  * along with this program.  If not, see <http://www.gnu.org/licenses/>.  *  * [Android is a trademark of Google Inc.]  *  * ---------------  * SerialDate.java  * ---------------  *   * (C) Copyright 2010, by Icom Systech Co., Ltd.  *  * Original Author:  shiraki  (for Icom Systech Co., Ltd);  * Contributor(s):   Sato Yoshiaki ;  *                   Niwano Masayoshi;  *  * Changes (from 19-Nov-2010)  * --------------------------  * 19-Nov-2010 : port JCommon 1.0.16 to Android as "AFreeChart"  *   * ------------- JFreeChart ---------------------------------------------  * (C) Copyright 2001-2006, by Object Refinery Limited.  *  * Original Author:  David Gilbert (for Object Refinery Limited);  * Contributor(s):   -;  *  *  * Changes (from 11-Oct-2001)  * --------------------------  * 11-Oct-2001 : Re-organised the class and moved it to new package   *               com.jrefinery.date (DG);  * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate   *               class (DG);  * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate   *               class is gone (DG);  Changed getPreviousDayOfWeek(),   *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct   *               bugs (DG);  * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);  * 29-May-2002 : Moved the month constants into a separate interface   *               (MonthConstants) (DG);  * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);  * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);  * 13-Mar-2003 : Implemented Serializable (DG);  * 29-May-2003 : Fixed bug in addMonths method (DG);  * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);  * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);  *   */ import java.io.Serializable; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /**  * Represents a date using an integer, in a similar fashion to the  * implementation in Microsoft Excel. The range of dates supported is 1-Jan-1900  * to 31-Dec-9999.  * <P>  * Be aware that there is a deliberate bug in Excel that recognises the year  * 1900 as a leap year when in fact it is not a leap year. You can find more  * information on the Microsoft website in article Q181370:  * <P>  * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp  * <P>  * Excel uses the convention that 1-Jan-1900 = 1. This class uses the convention  * 1-Jan-1900 = 2. The result is that the day number in this class will be  * different to the Excel figure for January and February 1900...but then Excel  * adds in an extra day (29-Feb-1900 which does not actually exist!) and from  * that point forward the day numbers will match.  *   * @author David Gilbert  */ public class SpreadsheetDate extends SerialDate {   /** For serialization. */   private static final long serialVersionUID = -2039586705374454461L;   /**    * The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 =    * 2958465).    */   private final int serial;   /** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */   private final int day;   /** The month of the year (1 to 12). */   private final int month;   /** The year (1900 to 9999). */   private final int year;   /**    * Creates a new date instance.    *     * @param day    *            the day (in the range 1 to 28/29/30/31).    * @param month    *            the month (in the range 1 to 12).    * @param year    *            the year (in the range 1900 to 9999).    */   public SpreadsheetDate(final int day, final int month, final int year) {     if ((year >= 1900) && (year <= 9999)) {       this.year = year;     } else {       throw new IllegalArgumentException(           "The 'year' argument must be in range 1900 to 9999.");     }     if ((month >= MonthConstants.JANUARY)         && (month <= MonthConstants.DECEMBER)) {       this.month = month;     } else {       throw new IllegalArgumentException(           "The 'month' argument must be in the range 1 to 12.");     }     if ((day >= 1) && (day <= SerialDate.lastDayOfMonth(month, year))) {       this.day = day;     } else {       throw new IllegalArgumentException("Invalid 'day' argument.");     }     // the serial number needs to be synchronised with the day-month-year...     this.serial = calcSerial(day, month, year);   }   /**    * Standard constructor - creates a new date object representing the    * specified day number (which should be in the range 2 to 2958465.    *     * @param serial    *            the serial number for the day (range: 2 to 2958465).    */   public SpreadsheetDate(final int serial) {     if ((serial >= SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) {       this.serial = serial;     } else {       throw new IllegalArgumentException(           "SpreadsheetDate: Serial must be in range 2 to 2958465.");     }     // the day-month-year needs to be synchronised with the serial number...     // get the year from the serial date     final int days = this.serial - SERIAL_LOWER_BOUND;     // overestimated because we ignored leap days     final int overestimatedYYYY = 1900 + (days / 365);     final int leaps = SerialDate.leapYearCount(overestimatedYYYY);     final int nonleapdays = days - leaps;     // underestimated because we overestimated years     int underestimatedYYYY = 1900 + (nonleapdays / 365);     if (underestimatedYYYY == overestimatedYYYY) {       this.year = underestimatedYYYY;     } else {       int ss1 = calcSerial(1, 1, underestimatedYYYY);       while (ss1 <= this.serial) {         underestimatedYYYY = underestimatedYYYY + 1;         ss1 = calcSerial(1, 1, underestimatedYYYY);       }       this.year = underestimatedYYYY - 1;     }     final int ss2 = calcSerial(1, 1, this.year);     int[] daysToEndOfPrecedingMonth = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;     if (isLeapYear(this.year)) {       daysToEndOfPrecedingMonth = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;     }     // get the month from the serial date     int mm = 1;     int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;     while (sss < this.serial) {       mm = mm + 1;       sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;     }     this.month = mm - 1;     // what's left is d(+1);     this.day = this.serial - ss2 - daysToEndOfPrecedingMonth[this.month]         + 1;   }   /**    * Returns the serial number for the date, where 1 January 1900 = 2 (this    * corresponds, almost, to the numbering system used in Microsoft Excel for    * Windows and Lotus 1-2-3).    *     * @return The serial number of this date.    */   public int toSerial() {     return this.serial;   }   /**    * Returns a <code>java.util.Date</code> equivalent to this date.    *     * @return The date.    */   public Date toDate() {     final Calendar calendar = Calendar.getInstance();     calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0, 0);     return calendar.getTime();   }   /**    * Returns the year (assume a valid range of 1900 to 9999).    *     * @return The year.    */   public int getYYYY() {     return this.year;   }   /**    * Returns the month (January = 1, February = 2, March = 3).    *     * @return The month of the year.    */   public int getMonth() {     return this.month;   }   /**    * Returns the day of the month.    *     * @return The day of the month.    */   public int getDayOfMonth() {     return this.day;   }   /**    * Returns a code representing the day of the week.    * <P>    * The codes are defined in the {@link SerialDate} class as:    * <code>SUNDAY</code>, <code>MONDAY</code>, <code>TUESDAY</code>,    * <code>WEDNESDAY</code>, <code>THURSDAY</code>, <code>FRIDAY</code>, and    * <code>SATURDAY</code>.    *     * @return A code representing the day of the week.    */   public int getDayOfWeek() {     return (this.serial + 6) % 7 + 1;   }   /**    * Tests the equality of this date with an arbitrary object.    * <P>    * This method will return true ONLY if the object is an instance of the    * {@link SerialDate} base class, and it represents the same day as this    * {@link SpreadsheetDate}.    *     * @param object    *            the object to compare (<code>null</code> permitted).    *     * @return A boolean.    */   public boolean equals(final Object object) {     if (object instanceof SerialDate) {       final SerialDate s = (SerialDate) object;       return (s.toSerial() == this.toSerial());     } else {       return false;     }   }   /**    * Returns a hash code for this object instance.    *     * @return A hash code.    */   public int hashCode() {     return toSerial();   }   /**    * Returns the difference (in days) between this date and the specified    * 'other' date.    *     * @param other    *            the date being compared to.    *     * @return The difference (in days) between this date and the specified    *         'other' date.    */   public int compare(final SerialDate other) {     return this.serial - other.toSerial();   }   /**    * Implements the method required by the Comparable interface.    *     * @param other    *            the other object (usually another SerialDate).    *     * @return A negative integer, zero, or a positive integer as this object is    *         less than, equal to, or greater than the specified object.    */   public int compareTo(final Object other) {     return compare((SerialDate) other);   }   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public boolean isOn(final SerialDate other) {     return (this.serial == other.toSerial());   }   /**    * Returns true if this SerialDate represents an earlier date compared to    * the specified SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents an earlier date    *         compared to the specified SerialDate.    */   public boolean isBefore(final SerialDate other) {     return (this.serial < other.toSerial());   }   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public boolean isOnOrBefore(final SerialDate other) {     return (this.serial <= other.toSerial());   }   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public boolean isAfter(final SerialDate other) {     return (this.serial > other.toSerial());   }   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public boolean isOnOrAfter(final SerialDate other) {     return (this.serial >= other.toSerial());   }   /**    * Returns <code>true</code> if this {@link SerialDate} is within the    * specified range (INCLUSIVE). The date order of d1 and d2 is not    * important.    *     * @param d1    *            a boundary date for the range.    * @param d2    *            the other boundary date for the range.    *     * @return A boolean.    */   public boolean isInRange(final SerialDate d1, final SerialDate d2) {     return isInRange(d1, d2, SerialDate.INCLUDE_BOTH);   }   /**    * Returns true if this SerialDate is within the specified range (caller    * specifies whether or not the end-points are included). The order of d1    * and d2 is not important.    *     * @param d1    *            one boundary date for the range.    * @param d2    *            a second boundary date for the range.    * @param include    *            a code that controls whether or not the start and end dates    *            are included in the range.    *     * @return <code>true</code> if this SerialDate is within the specified    *         range.    */   public boolean isInRange(final SerialDate d1, final SerialDate d2,       final int include) {     final int s1 = d1.toSerial();     final int s2 = d2.toSerial();     final int start = Math.min(s1, s2);     final int end = Math.max(s1, s2);     final int s = toSerial();     if (include == SerialDate.INCLUDE_BOTH) {       return (s >= start && s <= end);     } else if (include == SerialDate.INCLUDE_FIRST) {       return (s >= start && s < end);     } else if (include == SerialDate.INCLUDE_SECOND) {       return (s > start && s <= end);     } else {       return (s > start && s < end);     }   }   /**    * Calculate the serial number from the day, month and year.    * <P>    * 1-Jan-1900 = 2.    *     * @param d    *            the day.    * @param m    *            the month.    * @param y    *            the year.    *     * @return the serial number from the day, month and year.    */   private int calcSerial(final int d, final int m, final int y) {     final int yy = ((y - 1900) * 365) + SerialDate.leapYearCount(y - 1);     int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m];     if (m > MonthConstants.FEBRUARY) {       if (SerialDate.isLeapYear(y)) {         mm = mm + 1;       }     }     final int dd = d;     return yy + mm + dd + 1;   } } /**  * An abstract class that defines our requirements for manipulating dates,  * without tying down a particular implementation.  * <P>  * Requirement 1 : match at least what Excel does for dates; Requirement 2 : the  * date represented by the class is immutable;  * <P>  * Why not just use java.util.Date? We will, when it makes sense. At times,  * java.util.Date can be *too* precise - it represents an instant in time,  * accurate to 1/1000th of a second (with the date itself depending on the  * time-zone). Sometimes we just want to represent a particular day (e.g. 21  * January 2015) without concerning ourselves about the time of day, or the  * time-zone, or anything else. That's what we've defined SerialDate for.  * <P>  * You can call getInstance() to get a concrete subclass of SerialDate, without  * worrying about the exact implementation.  *   * @author David Gilbert  */ abstract class SerialDate implements Comparable, Serializable, MonthConstants {   /** For serialization. */   private static final long serialVersionUID = -293716040467423637L;   /** Date format symbols. */   public static final DateFormatSymbols DATE_FORMAT_SYMBOLS = new SimpleDateFormat()       .getDateFormatSymbols();   /** The serial number for 1 January 1900. */   public static final int SERIAL_LOWER_BOUND = 2;   /** The serial number for 31 December 9999. */   public static final int SERIAL_UPPER_BOUND = 2958465;   /** The lowest year value supported by this date format. */   public static final int MINIMUM_YEAR_SUPPORTED = 1900;   /** The highest year value supported by this date format. */   public static final int MAXIMUM_YEAR_SUPPORTED = 9999;   /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */   public static final int MONDAY = Calendar.MONDAY;   /**    * Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY.    */   public static final int TUESDAY = Calendar.TUESDAY;   /**    * Useful constant for Wednesday. Equivalent to    * java.util.Calendar.WEDNESDAY.    */   public static final int WEDNESDAY = Calendar.WEDNESDAY;   /**    * Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY.    */   public static final int THURSDAY = Calendar.THURSDAY;   /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */   public static final int FRIDAY = Calendar.FRIDAY;   /**    * Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY.    */   public static final int SATURDAY = Calendar.SATURDAY;   /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */   public static final int SUNDAY = Calendar.SUNDAY;   /** The number of days in each month in non leap years. */   static final int[] LAST_DAY_OF_MONTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31,       30, 31, 30, 31 };   /** The number of days in a (non-leap) year up to the end of each month. */   static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH = { 0, 31, 59, 90, 120,       151, 181, 212, 243, 273, 304, 334, 365 };   /** The number of days in a year up to the end of the preceding month. */   static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = { 0, 0, 31,       59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };   /** The number of days in a leap year up to the end of each month. */   static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH = { 0, 31, 60,       91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };   /**    * The number of days in a leap year up to the end of the preceding month.    */   static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = {       0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };   /** A useful constant for referring to the first week in a month. */   public static final int FIRST_WEEK_IN_MONTH = 1;   /** A useful constant for referring to the second week in a month. */   public static final int SECOND_WEEK_IN_MONTH = 2;   /** A useful constant for referring to the third week in a month. */   public static final int THIRD_WEEK_IN_MONTH = 3;   /** A useful constant for referring to the fourth week in a month. */   public static final int FOURTH_WEEK_IN_MONTH = 4;   /** A useful constant for referring to the last week in a month. */   public static final int LAST_WEEK_IN_MONTH = 0;   /** Useful range constant. */   public static final int INCLUDE_NONE = 0;   /** Useful range constant. */   public static final int INCLUDE_FIRST = 1;   /** Useful range constant. */   public static final int INCLUDE_SECOND = 2;   /** Useful range constant. */   public static final int INCLUDE_BOTH = 3;   /**    * Useful constant for specifying a day of the week relative to a fixed    * date.    */   public static final int PRECEDING = -1;   /**    * Useful constant for specifying a day of the week relative to a fixed    * date.    */   public static final int NEAREST = 0;   /**    * Useful constant for specifying a day of the week relative to a fixed    * date.    */   public static final int FOLLOWING = 1;   /** A description for the date. */   private String description;   /**    * Default constructor.    */   protected SerialDate() {   }   /**    * Returns <code>true</code> if the supplied integer code represents a valid    * day-of-the-week, and <code>false</code> otherwise.    *     * @param code    *            the code being checked for validity.    *     * @return <code>true</code> if the supplied integer code represents a valid    *         day-of-the-week, and <code>false</code> otherwise.    */   public static boolean isValidWeekdayCode(final int code) {     switch (code) {     case SUNDAY:     case MONDAY:     case TUESDAY:     case WEDNESDAY:     case THURSDAY:     case FRIDAY:     case SATURDAY:       return true;     default:       return false;     }   }   /**    * Converts the supplied string to a day of the week.    *     * @param s    *            a string representing the day of the week.    *     * @return <code>-1</code> if the string is not convertable, the day of the    *         week otherwise.    */   public static int stringToWeekdayCode(String s) {     final String[] shortWeekdayNames = DATE_FORMAT_SYMBOLS         .getShortWeekdays();     final String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays();     int result = -1;     s = s.trim();     for (int i = 0; i < weekDayNames.length; i++) {       if (s.equals(shortWeekdayNames[i])) {         result = i;         break;       }       if (s.equals(weekDayNames[i])) {         result = i;         break;       }     }     return result;   }   /**    * Returns a string representing the supplied day-of-the-week.    * <P>    * Need to find a better approach.    *     * @param weekday    *            the day of the week.    *     * @return a string representing the supplied day-of-the-week.    */   public static String weekdayCodeToString(final int weekday) {     final String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays();     return weekdays[weekday];   }   /**    * Returns an array of month names.    *     * @return an array of month names.    */   public static String[] getMonths() {     return getMonths(false);   }   /**    * Returns an array of month names.    *     * @param shortened    *            a flag indicating that shortened month names should be    *            returned.    *     * @return an array of month names.    */   public static String[] getMonths(final boolean shortened) {     if (shortened) {       return DATE_FORMAT_SYMBOLS.getShortMonths();     } else {       return DATE_FORMAT_SYMBOLS.getMonths();     }   }   /**    * Returns true if the supplied integer code represents a valid month.    *     * @param code    *            the code being checked for validity.    *     * @return <code>true</code> if the supplied integer code represents a valid    *         month.    */   public static boolean isValidMonthCode(final int code) {     switch (code) {     case JANUARY:     case FEBRUARY:     case MARCH:     case APRIL:     case MAY:     case JUNE:     case JULY:     case AUGUST:     case SEPTEMBER:     case OCTOBER:     case NOVEMBER:     case DECEMBER:       return true;     default:       return false;     }   }   /**    * Returns the quarter for the specified month.    *     * @param code    *            the month code (1-12).    *     * @return the quarter that the month belongs to.    */   public static int monthCodeToQuarter(final int code) {     switch (code) {     case JANUARY:     case FEBRUARY:     case MARCH:       return 1;     case APRIL:     case MAY:     case JUNE:       return 2;     case JULY:     case AUGUST:     case SEPTEMBER:       return 3;     case OCTOBER:     case NOVEMBER:     case DECEMBER:       return 4;     default:       throw new IllegalArgumentException(           "SerialDate.monthCodeToQuarter: invalid month code.");     }   }   /**    * Returns a string representing the supplied month.    * <P>    * The string returned is the long form of the month name taken from the    * default locale.    *     * @param month    *            the month.    *     * @return a string representing the supplied month.    */   public static String monthCodeToString(final int month) {     return monthCodeToString(month, false);   }   /**    * Returns a string representing the supplied month.    * <P>    * The string returned is the long or short form of the month name taken    * from the default locale.    *     * @param month    *            the month.    * @param shortened    *            if <code>true</code> return the abbreviation of the month.    *     * @return a string representing the supplied month.    */   public static String monthCodeToString(final int month,       final boolean shortened) {     // check arguments...     if (!isValidMonthCode(month)) {       throw new IllegalArgumentException(           "SerialDate.monthCodeToString: month outside valid range.");     }     final String[] months;     if (shortened) {       months = DATE_FORMAT_SYMBOLS.getShortMonths();     } else {       months = DATE_FORMAT_SYMBOLS.getMonths();     }     return months[month - 1];   }   /**    * Converts a string to a month code.    * <P>    * This method will return one of the constants JANUARY, FEBRUARY, ...,    * DECEMBER that corresponds to the string. If the string is not recognised,    * this method returns -1.    *     * @param s    *            the string to parse.    *     * @return <code>-1</code> if the string is not parseable, the month of the    *         year otherwise.    */   public static int stringToMonthCode(String s) {     final String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths();     final String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths();     int result = -1;     s = s.trim();     // first try parsing the string as an integer (1-12)...     try {       result = Integer.parseInt(s);     } catch (NumberFormatException e) {       // suppress     }     // now search through the month names...     if ((result < 1) || (result > 12)) {       for (int i = 0; i < monthNames.length; i++) {         if (s.equals(shortMonthNames[i])) {           result = i + 1;           break;         }         if (s.equals(monthNames[i])) {           result = i + 1;           break;         }       }     }     return result;   }   /**    * Returns true if the supplied integer code represents a valid    * week-in-the-month, and false otherwise.    *     * @param code    *            the code being checked for validity.    * @return <code>true</code> if the supplied integer code represents a valid    *         week-in-the-month.    */   public static boolean isValidWeekInMonthCode(final int code) {     switch (code) {     case FIRST_WEEK_IN_MONTH:     case SECOND_WEEK_IN_MONTH:     case THIRD_WEEK_IN_MONTH:     case FOURTH_WEEK_IN_MONTH:     case LAST_WEEK_IN_MONTH:       return true;     default:       return false;     }   }   /**    * Determines whether or not the specified year is a leap year.    *     * @param yyyy    *            the year (in the range 1900 to 9999).    *     * @return <code>true</code> if the specified year is a leap year.    */   public static boolean isLeapYear(final int yyyy) {     if ((yyyy % 4) != 0) {       return false;     } else if ((yyyy % 400) == 0) {       return true;     } else if ((yyyy % 100) == 0) {       return false;     } else {       return true;     }   }   /**    * Returns the number of leap years from 1900 to the specified year    * INCLUSIVE.    * <P>    * Note that 1900 is not a leap year.    *     * @param yyyy    *            the year (in the range 1900 to 9999).    *     * @return the number of leap years from 1900 to the specified year.    */   public static int leapYearCount(final int yyyy) {     final int leap4 = (yyyy - 1896) / 4;     final int leap100 = (yyyy - 1800) / 100;     final int leap400 = (yyyy - 1600) / 400;     return leap4 - leap100 + leap400;   }   /**    * Returns the number of the last day of the month, taking into account leap    * years.    *     * @param month    *            the month.    * @param yyyy    *            the year (in the range 1900 to 9999).    *     * @return the number of the last day of the month.    */   public static int lastDayOfMonth(final int month, final int yyyy) {     final int result = LAST_DAY_OF_MONTH[month];     if (month != FEBRUARY) {       return result;     } else if (isLeapYear(yyyy)) {       return result + 1;     } else {       return result;     }   }   /**    * Creates a new date by adding the specified number of days to the base    * date.    *     * @param days    *            the number of days to add (can be negative).    * @param base    *            the base date.    *     * @return a new date.    */   public static SerialDate addDays(final int days, final SerialDate base) {     final int serialDayNumber = base.toSerial() + days;     return SerialDate.createInstance(serialDayNumber);   }   /**    * Creates a new date by adding the specified number of months to the base    * date.    * <P>    * If the base date is close to the end of the month, the day on the result    * may be adjusted slightly: 31 May + 1 month = 30 June.    *     * @param months    *            the number of months to add (can be negative).    * @param base    *            the base date.    *     * @return a new date.    */   public static SerialDate addMonths(final int months, final SerialDate base) {     final int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) / 12;     final int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1;     final int dd = Math.min(base.getDayOfMonth(),         SerialDate.lastDayOfMonth(mm, yy));     return SerialDate.createInstance(dd, mm, yy);   }   /**    * Creates a new date by adding the specified number of years to the base    * date.    *     * @param years    *            the number of years to add (can be negative).    * @param base    *            the base date.    *     * @return A new date.    */   public static SerialDate addYears(final int years, final SerialDate base) {     final int baseY = base.getYYYY();     final int baseM = base.getMonth();     final int baseD = base.getDayOfMonth();     final int targetY = baseY + years;     final int targetD = Math.min(baseD,         SerialDate.lastDayOfMonth(baseM, targetY));     return SerialDate.createInstance(targetD, baseM, targetY);   }   /**    * Returns the latest date that falls on the specified day-of-the-week and    * is BEFORE the base date.    *     * @param targetWeekday    *            a code for the target day-of-the-week.    * @param base    *            the base date.    *     * @return the latest date that falls on the specified day-of-the-week and    *         is BEFORE the base date.    */   public static SerialDate getPreviousDayOfWeek(final int targetWeekday,       final SerialDate base) {     // check arguments...     if (!SerialDate.isValidWeekdayCode(targetWeekday)) {       throw new IllegalArgumentException("Invalid day-of-the-week code.");     }     // find the date...     final int adjust;     final int baseDOW = base.getDayOfWeek();     if (baseDOW > targetWeekday) {       adjust = Math.min(0, targetWeekday - baseDOW);     } else {       adjust = -7 + Math.max(0, targetWeekday - baseDOW);     }     return SerialDate.addDays(adjust, base);   }   /**    * Returns the earliest date that falls on the specified day-of-the-week and    * is AFTER the base date.    *     * @param targetWeekday    *            a code for the target day-of-the-week.    * @param base    *            the base date.    *     * @return the earliest date that falls on the specified day-of-the-week and    *         is AFTER the base date.    */   public static SerialDate getFollowingDayOfWeek(final int targetWeekday,       final SerialDate base) {     // check arguments...     if (!SerialDate.isValidWeekdayCode(targetWeekday)) {       throw new IllegalArgumentException("Invalid day-of-the-week code.");     }     // find the date...     final int adjust;     final int baseDOW = base.getDayOfWeek();     if (baseDOW > targetWeekday) {       adjust = 7 + Math.min(0, targetWeekday - baseDOW);     } else {       adjust = Math.max(0, targetWeekday - baseDOW);     }     return SerialDate.addDays(adjust, base);   }   /**    * Returns the date that falls on the specified day-of-the-week and is    * CLOSEST to the base date.    *     * @param targetDOW    *            a code for the target day-of-the-week.    * @param base    *            the base date.    *     * @return the date that falls on the specified day-of-the-week and is    *         CLOSEST to the base date.    */   public static SerialDate getNearestDayOfWeek(final int targetDOW,       final SerialDate base) {     // check arguments...     if (!SerialDate.isValidWeekdayCode(targetDOW)) {       throw new IllegalArgumentException("Invalid day-of-the-week code.");     }     // find the date...     final int baseDOW = base.getDayOfWeek();     int adjust = -Math.abs(targetDOW - baseDOW);     if (adjust >= 4) {       adjust = 7 - adjust;     }     if (adjust <= -4) {       adjust = 7 + adjust;     }     return SerialDate.addDays(adjust, base);   }   /**    * Rolls the date forward to the last day of the month.    *     * @param base    *            the base date.    *     * @return a new serial date.    */   public SerialDate getEndOfCurrentMonth(final SerialDate base) {     final int last = SerialDate.lastDayOfMonth(base.getMonth(),         base.getYYYY());     return SerialDate.createInstance(last, base.getMonth(), base.getYYYY());   }   /**    * Returns a string corresponding to the week-in-the-month code.    * <P>    * Need to find a better approach.    *     * @param count    *            an integer code representing the week-in-the-month.    *     * @return a string corresponding to the week-in-the-month code.    */   public static String weekInMonthToString(final int count) {     switch (count) {     case SerialDate.FIRST_WEEK_IN_MONTH:       return "First";     case SerialDate.SECOND_WEEK_IN_MONTH:       return "Second";     case SerialDate.THIRD_WEEK_IN_MONTH:       return "Third";     case SerialDate.FOURTH_WEEK_IN_MONTH:       return "Fourth";     case SerialDate.LAST_WEEK_IN_MONTH:       return "Last";     default:       return "SerialDate.weekInMonthToString(): invalid code.";     }   }   /**    * Returns a string representing the supplied 'relative'.    * <P>    * Need to find a better approach.    *     * @param relative    *            a constant representing the 'relative'.    *     * @return a string representing the supplied 'relative'.    */   public static String relativeToString(final int relative) {     switch (relative) {     case SerialDate.PRECEDING:       return "Preceding";     case SerialDate.NEAREST:       return "Nearest";     case SerialDate.FOLLOWING:       return "Following";     default:       return "ERROR : Relative To String";     }   }   /**    * Factory method that returns an instance of some concrete subclass of    * {@link SerialDate}.    *     * @param day    *            the day (1-31).    * @param month    *            the month (1-12).    * @param yyyy    *            the year (in the range 1900 to 9999).    *     * @return An instance of {@link SerialDate}.    */   public static SerialDate createInstance(final int day, final int month,       final int yyyy) {     return new SpreadsheetDate(day, month, yyyy);   }   /**    * Factory method that returns an instance of some concrete subclass of    * {@link SerialDate}.    *     * @param serial    *            the serial number for the day (1 January 1900 = 2).    *     * @return a instance of SerialDate.    */   public static SerialDate createInstance(final int serial) {     return new SpreadsheetDate(serial);   }   /**    * Factory method that returns an instance of a subclass of SerialDate.    *     * @param date    *            A Java date object.    *     * @return a instance of SerialDate.    */   public static SerialDate createInstance(final java.util.Date date) {     final GregorianCalendar calendar = new GregorianCalendar();     calendar.setTime(date);     return new SpreadsheetDate(calendar.get(Calendar.DATE),         calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR));   }   /**    * Returns the serial number for the date, where 1 January 1900 = 2 (this    * corresponds, almost, to the numbering system used in Microsoft Excel for    * Windows and Lotus 1-2-3).    *     * @return the serial number for the date.    */   public abstract int toSerial();   /**    * Returns a java.util.Date. Since java.util.Date has more precision than    * SerialDate, we need to define a convention for the 'time of day'.    *     * @return this as <code>java.util.Date</code>.    */   public abstract java.util.Date toDate();   /**    * Returns the description that is attached to the date. It is not required    * that a date have a description, but for some applications it is useful.    *     * @return The description (possibly <code>null</code>).    */   public String getDescription() {     return this.description;   }   /**    * Sets the description for the date.    *     * @param description    *            the description for this date (<code>null</code> permitted).    */   public void setDescription(final String description) {     this.description = description;   }   /**    * Converts the date to a string.    *     * @return a string representation of the date.    */   public String toString() {     return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth())         + "-" + getYYYY();   }   /**    * Returns the year (assume a valid range of 1900 to 9999).    *     * @return the year.    */   public abstract int getYYYY();   /**    * Returns the month (January = 1, February = 2, March = 3).    *     * @return the month of the year.    */   public abstract int getMonth();   /**    * Returns the day of the month.    *     * @return the day of the month.    */   public abstract int getDayOfMonth();   /**    * Returns the day of the week.    *     * @return the day of the week.    */   public abstract int getDayOfWeek();   /**    * Returns the difference (in days) between this date and the specified    * 'other' date.    * <P>    * The result is positive if this date is after the 'other' date and    * negative if it is before the 'other' date.    *     * @param other    *            the date being compared to.    *     * @return the difference between this and the other date.    */   public abstract int compare(SerialDate other);   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public abstract boolean isOn(SerialDate other);   /**    * Returns true if this SerialDate represents an earlier date compared to    * the specified SerialDate.    *     * @param other    *            The date being compared to.    *     * @return <code>true</code> if this SerialDate represents an earlier date    *         compared to the specified SerialDate.    */   public abstract boolean isBefore(SerialDate other);   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true<code> if this SerialDate represents the same date    *         as the specified SerialDate.    */   public abstract boolean isOnOrBefore(SerialDate other);   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public abstract boolean isAfter(SerialDate other);   /**    * Returns true if this SerialDate represents the same date as the specified    * SerialDate.    *     * @param other    *            the date being compared to.    *     * @return <code>true</code> if this SerialDate represents the same date as    *         the specified SerialDate.    */   public abstract boolean isOnOrAfter(SerialDate other);   /**    * Returns <code>true</code> if this {@link SerialDate} is within the    * specified range (INCLUSIVE). The date order of d1 and d2 is not    * important.    *     * @param d1    *            a boundary date for the range.    * @param d2    *            the other boundary date for the range.    *     * @return A boolean.    */   public abstract boolean isInRange(SerialDate d1, SerialDate d2);   /**    * Returns <code>true</code> if this {@link SerialDate} is within the    * specified range (caller specifies whether or not the end-points are    * included). The date order of d1 and d2 is not important.    *     * @param d1    *            a boundary date for the range.    * @param d2    *            the other boundary date for the range.    * @param include    *            a code that controls whether or not the start and end dates    *            are included in the range.    *     * @return A boolean.    */   public abstract boolean isInRange(SerialDate d1, SerialDate d2, int include);   /**    * Returns the latest date that falls on the specified day-of-the-week and    * is BEFORE this date.    *     * @param targetDOW    *            a code for the target day-of-the-week.    *     * @return the latest date that falls on the specified day-of-the-week and    *         is BEFORE this date.    */   public SerialDate getPreviousDayOfWeek(final int targetDOW) {     return getPreviousDayOfWeek(targetDOW, this);   }   /**    * Returns the earliest date that falls on the specified day-of-the-week and    * is AFTER this date.    *     * @param targetDOW    *            a code for the target day-of-the-week.    *     * @return the earliest date that falls on the specified day-of-the-week and    *         is AFTER this date.    */   public SerialDate getFollowingDayOfWeek(final int targetDOW) {     return getFollowingDayOfWeek(targetDOW, this);   }   /**    * Returns the nearest date that falls on the specified day-of-the-week.    *     * @param targetDOW    *            a code for the target day-of-the-week.    *     * @return the nearest date that falls on the specified day-of-the-week.    */   public SerialDate getNearestDayOfWeek(final int targetDOW) {     return getNearestDayOfWeek(targetDOW, this);   } } /**  * Useful constants for months.  Note that these are NOT equivalent to the  * constants defined by java.util.Calendar (where JANUARY=0 and DECEMBER=11).  * <P>  * Used by the SerialDate and RegularTimePeriod classes.  *  * @author David Gilbert  */  interface MonthConstants {     /** Constant for January. */     public static final int JANUARY = 1;     /** Constant for February. */     public static final int FEBRUARY = 2;     /** Constant for March. */     public static final int MARCH = 3;     /** Constant for April. */     public static final int APRIL = 4;     /** Constant for May. */     public static final int MAY = 5;     /** Constant for June. */     public static final int JUNE = 6;     /** Constant for July. */     public static final int JULY = 7;     /** Constant for August. */     public static final int AUGUST = 8;     /** Constant for September. */     public static final int SEPTEMBER = 9;     /** Constant for October. */     public static final int OCTOBER = 10;     /** Constant for November. */     public static final int NOVEMBER = 11;     /** Constant for December. */     public static final int DECEMBER = 12; }