Date Type Android

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 .
 *
 * [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.
 * 


 * 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:
 * 


 * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
 * 


 * 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 java.util.Date 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.
   * 


   * The codes are defined in the {@link SerialDate} class as:
   * SUNDAYMONDAYTUESDAY,
   * WEDNESDAYTHURSDAYFRIDAY, and
   * SATURDAY.
   * 
   * @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.
   * 


   * 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 (null 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 true 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 true 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 true 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 true 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 true if this SerialDate represents the same date as
   *         the specified SerialDate.
   */
  public boolean isOnOrAfter(final SerialDate other) {
    return (this.serial >= other.toSerial());
  }
  /**
   * Returns true 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 true 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.
   * 


   * 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.
 * 


 * Requirement 1 : match at least what Excel does for dates; Requirement 2 : the
 * date represented by the class is immutable;
 * 


 * 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.
 * 


 * 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 true if the supplied integer code represents a valid
   * day-of-the-week, and false otherwise.
   * 
   * @param code
   *            the code being checked for validity.
   * 
   * @return true if the supplied integer code represents a valid
   *         day-of-the-week, and false 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 -1 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.
   * 


   * 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 true 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.
   * 


   * 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.
   * 


   * 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 true 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.
   * 


   * 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 -1 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 true 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 true 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.
   * 


   * 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.
   * 


   * 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.
   * 


   * 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'.
   * 


   * 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 java.util.Date.
   */
  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 null).
   */
  public String getDescription() {
    return this.description;
  }
  /**
   * Sets the description for the date.
   * 
   * @param description
   *            the description for this date (null 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.
   * 


   * 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 true 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 true 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 true 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 true 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 true if this SerialDate represents the same date as
   *         the specified SerialDate.
   */
  public abstract boolean isOnOrAfter(SerialDate other);
  /**
   * Returns true 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 true 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).
 * 


 * 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;
}