    001/* ========================================================================
    002 * JCommon : a free general purpose class library for the Java(tm) platform
    003 * ========================================================================
    004 *
    005 * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jcommon/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025 * in the United States and other countries.]
    026 *
    027 * ---------------
    028 * SerialDate.java
    029 * ---------------
    030 * (C) Copyright 2001-2006, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * $Id: SerialDate.java,v 1.9 2011/10/17 20:08:22 mungady Exp $
    036 *
    037 * Changes (from 11-Oct-2001)
    038 * --------------------------
    039 * 11-Oct-2001 : Re-organised the class and moved it to new package
    040 *               com.jrefinery.date (DG);
    041 * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate
    042 *               class (DG);
    043 * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate
    044 *               class is gone (DG);  Changed getPreviousDayOfWeek(),
    045 *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct
    046 *               bugs (DG);
    047 * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
    048 * 29-May-2002 : Moved the month constants into a separate interface
    049 *               (MonthConstants) (DG);
    050 * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
    051 * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    052 * 13-Mar-2003 : Implemented Serializable (DG);
    053 * 29-May-2003 : Fixed bug in addMonths method (DG);
    054 * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);
    055 * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
    056 *
    057 */
    058
    059package org.jfree.date;
    060
    061import java.io.Serializable;
    062import java.text.DateFormatSymbols;
    063import java.text.SimpleDateFormat;
    064import java.util.Calendar;
    065import java.util.GregorianCalendar;
    066
    067/**
    068 *  An abstract class that defines our requirements for manipulating dates,
    069 *  without tying down a particular implementation.
    070 *  <P>
    071 *  Requirement 1 : match at least what Excel does for dates;
    072 *  Requirement 2 : the date represented by the class is immutable;
    073 *  <P>
    074 *  Why not just use java.util.Date?  We will, when it makes sense.  At times,
    075 *  java.util.Date can be *too* precise - it represents an instant in time,
    076 *  accurate to 1/1000th of a second (with the date itself depending on the
    077 *  time-zone).  Sometimes we just want to represent a particular day (e.g. 21
    078 *  January 2015) without concerning ourselves about the time of day, or the
    079 *  time-zone, or anything else.  That's what we've defined SerialDate for.
    080 *  <P>
    081 *  You can call getInstance() to get a concrete subclass of SerialDate,
    082 *  without worrying about the exact implementation.
    083 *
    084 * @author David Gilbert
    085 */
    086public abstract class SerialDate implements Comparable,
    087                                            Serializable,
    088                                            MonthConstants {
    089
    090    /** For serialization. */
    091    private static final long serialVersionUID = -293716040467423637L;
    092
    093    /** Date format symbols. */
    094    public static final DateFormatSymbols
    095        DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols();
    096
    097    /** The serial number for 1 January 1900. */
    098    public static final int SERIAL_LOWER_BOUND = 2;
    099
    100    /** The serial number for 31 December 9999. */
    101    public static final int SERIAL_UPPER_BOUND = 2958465;
    102
    103    /** The lowest year value supported by this date format. */
    104    public static final int MINIMUM_YEAR_SUPPORTED = 1900;
    105
    106    /** The highest year value supported by this date format. */
    107    public static final int MAXIMUM_YEAR_SUPPORTED = 9999;
    108
    109    /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */
    110    public static final int MONDAY = Calendar.MONDAY;
    111
    112    /**
    113     * Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY.
    114     */
    115    public static final int TUESDAY = Calendar.TUESDAY;
    116
    117    /**
    118     * Useful constant for Wednesday. Equivalent to
    119     * java.util.Calendar.WEDNESDAY.
    120     */
    121    public static final int WEDNESDAY = Calendar.WEDNESDAY;
    122
    123    /**
    124     * Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY.
    125     */
    126    public static final int THURSDAY = Calendar.THURSDAY;
    127
    128    /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */
    129    public static final int FRIDAY = Calendar.FRIDAY;
    130
    131    /**
    132     * Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY.
    133     */
    134    public static final int SATURDAY = Calendar.SATURDAY;
    135
    136    /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */
    137    public static final int SUNDAY = Calendar.SUNDAY;
    138
    139    /** The number of days in each month in non leap years. */
    140    static final int[] LAST_DAY_OF_MONTH =
    141        {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    142
    143    /** The number of days in a (non-leap) year up to the end of each month. */
    144    static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH =
    145        {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
    146
    147    /** The number of days in a year up to the end of the preceding month. */
    148    static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
    149        {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
    150
    151    /** The number of days in a leap year up to the end of each month. */
    152    static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH =
    153        {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
    154
    155    /**
    156     * The number of days in a leap year up to the end of the preceding month.
    157     */
    158    static final int[]
    159        LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
    160            {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
    161
    162    /** A useful constant for referring to the first week in a month. */
    163    public static final int FIRST_WEEK_IN_MONTH = 1;
    164
    165    /** A useful constant for referring to the second week in a month. */
    166    public static final int SECOND_WEEK_IN_MONTH = 2;
    167
    168    /** A useful constant for referring to the third week in a month. */
    169    public static final int THIRD_WEEK_IN_MONTH = 3;
    170
    171    /** A useful constant for referring to the fourth week in a month. */
    172    public static final int FOURTH_WEEK_IN_MONTH = 4;
    173
    174    /** A useful constant for referring to the last week in a month. */
    175    public static final int LAST_WEEK_IN_MONTH = 0;
    176
    177    /** Useful range constant. */
    178    public static final int INCLUDE_NONE = 0;
    179
    180    /** Useful range constant. */
    181    public static final int INCLUDE_FIRST = 1;
    182
    183    /** Useful range constant. */
    184    public static final int INCLUDE_SECOND = 2;
    185
    186    /** Useful range constant. */
    187    public static final int INCLUDE_BOTH = 3;
    188
    189    /**
    190     * Useful constant for specifying a day of the week relative to a fixed
    191     * date.
    192     */
    193    public static final int PRECEDING = -1;
    194
    195    /**
    196     * Useful constant for specifying a day of the week relative to a fixed
    197     * date.
    198     */
    199    public static final int NEAREST = 0;
    200
    201    /**
    202     * Useful constant for specifying a day of the week relative to a fixed
    203     * date.
    204     */
    205    public static final int FOLLOWING = 1;
    206
    207    /** A description for the date. */
    208    private String description;
    209
    210    /**
    211     * Default constructor.
    212     */
    213    protected SerialDate() {
    214    }
    215
    216    /**
    217     * Returns <code>true</code> if the supplied integer code represents a
    218     * valid day-of-the-week, and <code>false</code> otherwise.
    219     *
    220     * @param code  the code being checked for validity.
    221     *
    222     * @return <code>true</code> if the supplied integer code represents a
    223     *         valid day-of-the-week, and <code>false</code> otherwise.
    224     */
    225    public static boolean isValidWeekdayCode(final int code) {
    226
    227        switch(code) {
    228            case SUNDAY:
    229            case MONDAY:
    230            case TUESDAY:
    231            case WEDNESDAY:
    232            case THURSDAY:
    233            case FRIDAY:
    234            case SATURDAY:
    235                return true;
    236            default:
    237                return false;
    238        }
    239
    240    }
    241
    242    /**
    243     * Converts the supplied string to a day of the week.
    244     *
    245     * @param s  a string representing the day of the week.
    246     *
    247     * @return <code>-1</code> if the string is not convertable, the day of
    248     *         the week otherwise.
    249     */
    250    public static int stringToWeekdayCode(String s) {
    251
    252        final String[] shortWeekdayNames
    253            = DATE_FORMAT_SYMBOLS.getShortWeekdays();
    254        final String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays();
    255
    256        int result = -1;
    257        s = s.trim();
    258        for (int i = 0; i < weekDayNames.length; i++) {
    259            if (s.equals(shortWeekdayNames[i])) {
    260                result = i;
    261                break;
    262            }
    263            if (s.equals(weekDayNames[i])) {
    264                result = i;
    265                break;
    266            }
    267        }
    268        return result;
    269
    270    }
    271
    272    /**
    273     * Returns a string representing the supplied day-of-the-week.
    274     * <P>
    275     * Need to find a better approach.
    276     *
    277     * @param weekday  the day of the week.
    278     *
    279     * @return a string representing the supplied day-of-the-week.
    280     */
    281    public static String weekdayCodeToString(final int weekday) {
    282
    283        final String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays();
    284        return weekdays[weekday];
    285
    286    }
    287
    288    /**
    289     * Returns an array of month names.
    290     *
    291     * @return an array of month names.
    292     */
    293    public static String[] getMonths() {
    294
    295        return getMonths(false);
    296
    297    }
    298
    299    /**
    300     * Returns an array of month names.
    301     *
    302     * @param shortened  a flag indicating that shortened month names should
    303     *                   be returned.
    304     *
    305     * @return an array of month names.
    306     */
    307    public static String[] getMonths(final boolean shortened) {
    308
    309        if (shortened) {
    310            return DATE_FORMAT_SYMBOLS.getShortMonths();
    311        }
    312        else {
    313            return DATE_FORMAT_SYMBOLS.getMonths();
    314        }
    315
    316    }
    317
    318    /**
    319     * Returns true if the supplied integer code represents a valid month.
    320     *
    321     * @param code  the code being checked for validity.
    322     *
    323     * @return <code>true</code> if the supplied integer code represents a
    324     *         valid month.
    325     */
    326    public static boolean isValidMonthCode(final int code) {
    327
    328        switch(code) {
    329            case JANUARY:
    330            case FEBRUARY:
    331            case MARCH:
    332            case APRIL:
    333            case MAY:
    334            case JUNE:
    335            case JULY:
    336            case AUGUST:
    337            case SEPTEMBER:
    338            case OCTOBER:
    339            case NOVEMBER:
    340            case DECEMBER:
    341                return true;
    342            default:
    343                return false;
    344        }
    345
    346    }
    347
    348    /**
    349     * Returns the quarter for the specified month.
    350     *
    351     * @param code  the month code (1-12).
    352     *
    353     * @return the quarter that the month belongs to.
    354     */
    355    public static int monthCodeToQuarter(final int code) {
    356
    357        switch(code) {
    358            case JANUARY:
    359            case FEBRUARY:
    360            case MARCH: return 1;
    361            case APRIL:
    362            case MAY:
    363            case JUNE: return 2;
    364            case JULY:
    365            case AUGUST:
    366            case SEPTEMBER: return 3;
    367            case OCTOBER:
    368            case NOVEMBER:
    369            case DECEMBER: return 4;
    370            default: throw new IllegalArgumentException(
    371                "SerialDate.monthCodeToQuarter: invalid month code.");
    372        }
    373
    374    }
    375
    376    /**
    377     * Returns a string representing the supplied month.
    378     * <P>
    379     * The string returned is the long form of the month name taken from the
    380     * default locale.
    381     *
    382     * @param month  the month.
    383     *
    384     * @return a string representing the supplied month.
    385     */
    386    public static String monthCodeToString(final int month) {
    387
    388        return monthCodeToString(month, false);
    389
    390    }
    391
    392    /**
    393     * Returns a string representing the supplied month.
    394     * <P>
    395     * The string returned is the long or short form of the month name taken
    396     * from the default locale.
    397     *
    398     * @param month  the month.
    399     * @param shortened  if <code>true</code> return the abbreviation of the
    400     *                   month.
    401     *
    402     * @return a string representing the supplied month.
    403     */
    404    public static String monthCodeToString(final int month,
    405                                           final boolean shortened) {
    406
    407        // check arguments...
    408        if (!isValidMonthCode(month)) {
    409            throw new IllegalArgumentException(
    410                "SerialDate.monthCodeToString: month outside valid range.");
    411        }
    412
    413        final String[] months;
    414
    415        if (shortened) {
    416            months = DATE_FORMAT_SYMBOLS.getShortMonths();
    417        }
    418        else {
    419            months = DATE_FORMAT_SYMBOLS.getMonths();
    420        }
    421
    422        return months[month - 1];
    423
    424    }
    425
    426    /**
    427     * Converts a string to a month code.
    428     * <P>
    429     * This method will return one of the constants JANUARY, FEBRUARY, ...,
    430     * DECEMBER that corresponds to the string.  If the string is not
    431     * recognised, this method returns -1.
    432     *
    433     * @param s  the string to parse.
    434     *
    435     * @return <code>-1</code> if the string is not parseable, the month of the
    436     *         year otherwise.
    437     */
    438    public static int stringToMonthCode(String s) {
    439
    440        final String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths();
    441        final String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths();
    442
    443        int result = -1;
    444        s = s.trim();
    445
    446        // first try parsing the string as an integer (1-12)...
    447        try {
    448            result = Integer.parseInt(s);
    449        }
    450        catch (NumberFormatException e) {
    451            // suppress
    452        }
    453
    454        // now search through the month names...
    455        if ((result < 1) || (result > 12)) {
    456            for (int i = 0; i < monthNames.length; i++) {
    457                if (s.equals(shortMonthNames[i])) {
    458                    result = i + 1;
    459                    break;
    460                }
    461                if (s.equals(monthNames[i])) {
    462                    result = i + 1;
    463                    break;
    464                }
    465            }
    466        }
    467
    468        return result;
    469
    470    }
    471
    472    /**
    473     * Returns true if the supplied integer code represents a valid
    474     * week-in-the-month, and false otherwise.
    475     *
    476     * @param code  the code being checked for validity.
    477     * @return <code>true</code> if the supplied integer code represents a
    478     *         valid week-in-the-month.
    479     */
    480    public static boolean isValidWeekInMonthCode(final int code) {
    481
    482        switch(code) {
    483            case FIRST_WEEK_IN_MONTH:
    484            case SECOND_WEEK_IN_MONTH:
    485            case THIRD_WEEK_IN_MONTH:
    486            case FOURTH_WEEK_IN_MONTH:
    487            case LAST_WEEK_IN_MONTH: return true;
    488            default: return false;
    489        }
    490
    491    }
    492
    493    /**
    494     * Determines whether or not the specified year is a leap year.
    495     *
    496     * @param yyyy  the year (in the range 1900 to 9999).
    497     *
    498     * @return <code>true</code> if the specified year is a leap year.
    499     */
    500    public static boolean isLeapYear(final int yyyy) {
    501
    502        if ((yyyy % 4) != 0) {
    503            return false;
    504        }
    505        else if ((yyyy % 400) == 0) {
    506            return true;
    507        }
    508        else if ((yyyy % 100) == 0) {
    509            return false;
    510        }
    511        else {
    512            return true;
    513        }
    514
    515    }
    516
    517    /**
    518     * Returns the number of leap years from 1900 to the specified year
    519     * INCLUSIVE.
    520     * <P>
    521     * Note that 1900 is not a leap year.
    522     *
    523     * @param yyyy  the year (in the range 1900 to 9999).
    524     *
    525     * @return the number of leap years from 1900 to the specified year.
    526     */
    527    public static int leapYearCount(final int yyyy) {
    528
    529        final int leap4 = (yyyy - 1896) / 4;
    530        final int leap100 = (yyyy - 1800) / 100;
    531        final int leap400 = (yyyy - 1600) / 400;
    532        return leap4 - leap100 + leap400;
    533
    534    }
    535
    536    /**
    537     * Returns the number of the last day of the month, taking into account
    538     * leap years.
    539     *
    540     * @param month  the month.
    541     * @param yyyy  the year (in the range 1900 to 9999).
    542     *
    543     * @return the number of the last day of the month.
    544     */
    545    public static int lastDayOfMonth(final int month, final int yyyy) {
    546
    547        final int result = LAST_DAY_OF_MONTH[month];
    548        if (month != FEBRUARY) {
    549            return result;
    550        }
    551        else if (isLeapYear(yyyy)) {
    552            return result + 1;
    553        }
    554        else {
    555            return result;
    556        }
    557
    558    }
    559
    560    /**
    561     * Creates a new date by adding the specified number of days to the base
    562     * date.
    563     *
    564     * @param days  the number of days to add (can be negative).
    565     * @param base  the base date.
    566     *
    567     * @return a new date.
    568     */
    569    public static SerialDate addDays(final int days, final SerialDate base) {
    570
    571        final int serialDayNumber = base.toSerial() + days;
    572        return SerialDate.createInstance(serialDayNumber);
    573
    574    }
    575
    576    /**
    577     * Creates a new date by adding the specified number of months to the base
    578     * date.
    579     * <P>
    580     * If the base date is close to the end of the month, the day on the result
    581     * may be adjusted slightly:  31 May + 1 month = 30 June.
    582     *
    583     * @param months  the number of months to add (can be negative).
    584     * @param base  the base date.
    585     *
    586     * @return a new date.
    587     */
    588    public static SerialDate addMonths(final int months,
    589                                       final SerialDate base) {
    590
    591        final int yy = (12 * base.getYYYY() + base.getMonth() + months - 1)
    592                       / 12;
    593        final int mm = (12 * base.getYYYY() + base.getMonth() + months - 1)
    594                       % 12 + 1;
    595        final int dd = Math.min(
    596            base.getDayOfMonth(), SerialDate.lastDayOfMonth(mm, yy)
    597        );
    598        return SerialDate.createInstance(dd, mm, yy);
    599
    600    }
    601
    602    /**
    603     * Creates a new date by adding the specified number of years to the base
    604     * date.
    605     *
    606     * @param years  the number of years to add (can be negative).
    607     * @param base  the base date.
    608     *
    609     * @return A new date.
    610     */
    611    public static SerialDate addYears(final int years, final SerialDate base) {
    612
    613        final int baseY = base.getYYYY();
    614        final int baseM = base.getMonth();
    615        final int baseD = base.getDayOfMonth();
    616
    617        final int targetY = baseY + years;
    618        final int targetD = Math.min(
    619            baseD, SerialDate.lastDayOfMonth(baseM, targetY)
    620        );
    621
    622        return SerialDate.createInstance(targetD, baseM, targetY);
    623
    624    }
    625
    626    /**
    627     * Returns the latest date that falls on the specified day-of-the-week and
    628     * is BEFORE the base date.
    629     *
    630     * @param targetWeekday  a code for the target day-of-the-week.
    631     * @param base  the base date.
    632     *
    633     * @return the latest date that falls on the specified day-of-the-week and
    634     *         is BEFORE the base date.
    635     */
    636    public static SerialDate getPreviousDayOfWeek(final int targetWeekday,
    637                                                  final SerialDate base) {
    638
    639        // check arguments...
    640        if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
    641            throw new IllegalArgumentException(
    642                "Invalid day-of-the-week code."
    643            );
    644        }
    645
    646        // find the date...
    647        final int adjust;
    648        final int baseDOW = base.getDayOfWeek();
    649        if (baseDOW > targetWeekday) {
    650            adjust = Math.min(0, targetWeekday - baseDOW);
    651        }
    652        else {
    653            adjust = -7 + Math.max(0, targetWeekday - baseDOW);
    654        }
    655
    656        return SerialDate.addDays(adjust, base);
    657
    658    }
    659
    660    /**
    661     * Returns the earliest date that falls on the specified day-of-the-week
    662     * and is AFTER the base date.
    663     *
    664     * @param targetWeekday  a code for the target day-of-the-week.
    665     * @param base  the base date.
    666     *
    667     * @return the earliest date that falls on the specified day-of-the-week
    668     *         and is AFTER the base date.
    669     */
    670    public static SerialDate getFollowingDayOfWeek(final int targetWeekday,
    671                                                   final SerialDate base) {
    672
    673        // check arguments...
    674        if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
    675            throw new IllegalArgumentException(
    676                "Invalid day-of-the-week code."
    677            );
    678        }
    679
    680        // find the date...
    681        final int adjust;
    682        final int baseDOW = base.getDayOfWeek();
    683        if (baseDOW > targetWeekday) {
    684            adjust = 7 + Math.min(0, targetWeekday - baseDOW);
    685        }
    686        else {
    687            adjust = Math.max(0, targetWeekday - baseDOW);
    688        }
    689
    690        return SerialDate.addDays(adjust, base);
    691    }
    692
    693    /**
    694     * Returns the date that falls on the specified day-of-the-week and is
    695     * CLOSEST to the base date.
    696     *
    697     * @param targetDOW  a code for the target day-of-the-week.
    698     * @param base  the base date.
    699     *
    700     * @return the date that falls on the specified day-of-the-week and is
    701     *         CLOSEST to the base date.
    702     */
    703    public static SerialDate getNearestDayOfWeek(final int targetDOW,
    704                                                 final SerialDate base) {
    705
    706        // check arguments...
    707        if (!SerialDate.isValidWeekdayCode(targetDOW)) {
    708            throw new IllegalArgumentException(
    709                "Invalid day-of-the-week code."
    710            );
    711        }
    712
    713        // find the date...
    714        final int baseDOW = base.getDayOfWeek();
    715        int adjust = -Math.abs(targetDOW - baseDOW);
    716        if (adjust >= 4) {
    717            adjust = 7 - adjust;
    718        }
    719        if (adjust <= -4) {
    720            adjust = 7 + adjust;
    721        }
    722        return SerialDate.addDays(adjust, base);
    723
    724    }
    725
    726    /**
    727     * Rolls the date forward to the last day of the month.
    728     *
    729     * @param base  the base date.
    730     *
    731     * @return a new serial date.
    732     */
    733    public SerialDate getEndOfCurrentMonth(final SerialDate base) {
    734        final int last = SerialDate.lastDayOfMonth(
    735            base.getMonth(), base.getYYYY()
    736        );
    737        return SerialDate.createInstance(last, base.getMonth(), base.getYYYY());
    738    }
    739
    740    /**
    741     * Returns a string corresponding to the week-in-the-month code.
    742     * <P>
    743     * Need to find a better approach.
    744     *
    745     * @param count  an integer code representing the week-in-the-month.
    746     *
    747     * @return a string corresponding to the week-in-the-month code.
    748     */
    749    public static String weekInMonthToString(final int count) {
    750
    751        switch (count) {
    752            case SerialDate.FIRST_WEEK_IN_MONTH : return "First";
    753            case SerialDate.SECOND_WEEK_IN_MONTH : return "Second";
    754            case SerialDate.THIRD_WEEK_IN_MONTH : return "Third";
    755            case SerialDate.FOURTH_WEEK_IN_MONTH : return "Fourth";
    756            case SerialDate.LAST_WEEK_IN_MONTH : return "Last";
    757            default :
    758                return "SerialDate.weekInMonthToString(): invalid code.";
    759        }
    760
    761    }
    762
    763    /**
    764     * Returns a string representing the supplied 'relative'.
    765     * <P>
    766     * Need to find a better approach.
    767     *
    768     * @param relative  a constant representing the 'relative'.
    769     *
    770     * @return a string representing the supplied 'relative'.
    771     */
    772    public static String relativeToString(final int relative) {
    773
    774        switch (relative) {
    775            case SerialDate.PRECEDING : return "Preceding";
    776            case SerialDate.NEAREST : return "Nearest";
    777            case SerialDate.FOLLOWING : return "Following";
    778            default : return "ERROR : Relative To String";
    779        }
    780
    781    }
    782
    783    /**
    784     * Factory method that returns an instance of some concrete subclass of
    785     * {@link SerialDate}.
    786     *
    787     * @param day  the day (1-31).
    788     * @param month  the month (1-12).
    789     * @param yyyy  the year (in the range 1900 to 9999).
    790     *
    791     * @return An instance of {@link SerialDate}.
    792     */
    793    public static SerialDate createInstance(final int day, final int month,
    794                                            final int yyyy) {
    795        return new SpreadsheetDate(day, month, yyyy);
    796    }
    797
    798    /**
    799     * Factory method that returns an instance of some concrete subclass of
    800     * {@link SerialDate}.
    801     *
    802     * @param serial  the serial number for the day (1 January 1900 = 2).
    803     *
    804     * @return a instance of SerialDate.
    805     */
    806    public static SerialDate createInstance(final int serial) {
    807        return new SpreadsheetDate(serial);
    808    }
    809
    810    /**
    811     * Factory method that returns an instance of a subclass of SerialDate.
    812     *
    813     * @param date  A Java date object.
    814     *
    815     * @return a instance of SerialDate.
    816     */
    817    public static SerialDate createInstance(final java.util.Date date) {
    818
    819        final GregorianCalendar calendar = new GregorianCalendar();
    820        calendar.setTime(date);
    821        return new SpreadsheetDate(calendar.get(Calendar.DATE),
    822                                   calendar.get(Calendar.MONTH) + 1,
    823                                   calendar.get(Calendar.YEAR));
    824
    825    }
    826
    827    /**
    828     * Returns the serial number for the date, where 1 January 1900 = 2 (this
    829     * corresponds, almost, to the numbering system used in Microsoft Excel for
    830     * Windows and Lotus 1-2-3).
    831     *
    832     * @return the serial number for the date.
    833     */
    834    public abstract int toSerial();
    835
    836    /**
    837     * Returns a java.util.Date.  Since java.util.Date has more precision than
    838     * SerialDate, we need to define a convention for the 'time of day'.
    839     *
    840     * @return this as <code>java.util.Date</code>.
    841     */
    842    public abstract java.util.Date toDate();
    843
    844    /**
    845     * Returns the description that is attached to the date.  It is not
    846     * required that a date have a description, but for some applications it
    847     * is useful.
    848     *
    849     * @return The description (possibly <code>null</code>).
    850     */
    851    public String getDescription() {
    852        return this.description;
    853    }
    854
    855    /**
    856     * Sets the description for the date.
    857     *
    858     * @param description  the description for this date (<code>null</code>
    859     *                     permitted).
    860     */
    861    public void setDescription(final String description) {
    862        this.description = description;
    863    }
    864
    865    /**
    866     * Converts the date to a string.
    867     *
    868     * @return  a string representation of the date.
    869     */
    870    public String toString() {
    871        return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth())
    872                               + "-" + getYYYY();
    873    }
    874
    875    /**
    876     * Returns the year (assume a valid range of 1900 to 9999).
    877     *
    878     * @return the year.
    879     */
    880    public abstract int getYYYY();
    881
    882    /**
    883     * Returns the month (January = 1, February = 2, March = 3).
    884     *
    885     * @return the month of the year.
    886     */
    887    public abstract int getMonth();
    888
    889    /**
    890     * Returns the day of the month.
    891     *
    892     * @return the day of the month.
    893     */
    894    public abstract int getDayOfMonth();
    895
    896    /**
    897     * Returns the day of the week.
    898     *
    899     * @return the day of the week.
    900     */
    901    public abstract int getDayOfWeek();
    902
    903    /**
    904     * Returns the difference (in days) between this date and the specified
    905     * 'other' date.
    906     * <P>
    907     * The result is positive if this date is after the 'other' date and
    908     * negative if it is before the 'other' date.
    909     *
    910     * @param other  the date being compared to.
    911     *
    912     * @return the difference between this and the other date.
    913     */
    914    public abstract int compare(SerialDate other);
    915
    916    /**
    917     * Returns true if this SerialDate represents the same date as the
    918     * specified SerialDate.
    919     *
    920     * @param other  the date being compared to.
    921     *
    922     * @return <code>true</code> if this SerialDate represents the same date as
    923     *         the specified SerialDate.
    924     */
    925    public abstract boolean isOn(SerialDate other);
    926
    927    /**
    928     * Returns true if this SerialDate represents an earlier date compared to
    929     * the specified SerialDate.
    930     *
    931     * @param other  The date being compared to.
    932     *
    933     * @return <code>true</code> if this SerialDate represents an earlier date
    934     *         compared to the specified SerialDate.
    935     */
    936    public abstract boolean isBefore(SerialDate other);
    937
    938    /**
    939     * Returns true if this SerialDate represents the same date as the
    940     * specified SerialDate.
    941     *
    942     * @param other  the date being compared to.
    943     *
    944     * @return <code>true</code> if this SerialDate represents the same date
    945     *         as the specified SerialDate.
    946     */
    947    public abstract boolean isOnOrBefore(SerialDate other);
    948
    949    /**
    950     * Returns true if this SerialDate represents the same date as the
    951     * specified SerialDate.
    952     *
    953     * @param other  the date being compared to.
    954     *
    955     * @return <code>true</code> if this SerialDate represents the same date
    956     *         as the specified SerialDate.
    957     */
    958    public abstract boolean isAfter(SerialDate other);
    959
    960    /**
    961     * Returns true if this SerialDate represents the same date as the
    962     * specified SerialDate.
    963     *
    964     * @param other  the date being compared to.
    965     *
    966     * @return <code>true</code> if this SerialDate represents the same date
    967     *         as the specified SerialDate.
    968     */
    969    public abstract boolean isOnOrAfter(SerialDate other);
    970
    971    /**
    972     * Returns <code>true</code> if this {@link SerialDate} is within the
    973     * specified range (INCLUSIVE).  The date order of d1 and d2 is not
    974     * important.
    975     *
    976     * @param d1  a boundary date for the range.
    977     * @param d2  the other boundary date for the range.
    978     *
    979     * @return A boolean.
    980     */
    981    public abstract boolean isInRange(SerialDate d1, SerialDate d2);
    982
    983    /**
    984     * Returns <code>true</code> if this {@link SerialDate} is within the
    985     * specified range (caller specifies whether or not the end-points are
    986     * included).  The date order of d1 and d2 is not important.
    987     *
    988     * @param d1  a boundary date for the range.
    989     * @param d2  the other boundary date for the range.
    990     * @param include  a code that controls whether or not the start and end
    991     *                 dates are included in the range.
    992     *
    993     * @return A boolean.
    994     */
    995    public abstract boolean isInRange(SerialDate d1, SerialDate d2,
    996                                      int include);
    997
    998    /**
    999     * Returns the latest date that falls on the specified day-of-the-week and
    1000     * is BEFORE this date.
    1001     *
    1002     * @param targetDOW  a code for the target day-of-the-week.
    1003     *
    1004     * @return the latest date that falls on the specified day-of-the-week and
    1005     *         is BEFORE this date.
    1006     */
    1007    public SerialDate getPreviousDayOfWeek(final int targetDOW) {
    1008        return getPreviousDayOfWeek(targetDOW, this);
    1009    }
    1010
    1011    /**
    1012     * Returns the earliest date that falls on the specified day-of-the-week
    1013     * and is AFTER this date.
    1014     *
    1015     * @param targetDOW  a code for the target day-of-the-week.
    1016     *
    1017     * @return the earliest date that falls on the specified day-of-the-week
    1018     *         and is AFTER this date.
    1019     */
    1020    public SerialDate getFollowingDayOfWeek(final int targetDOW) {
    1021        return getFollowingDayOfWeek(targetDOW, this);
    1022    }
    1023
    1024    /**
    1025     * Returns the nearest date that falls on the specified day-of-the-week.
    1026     *
    1027     * @param targetDOW  a code for the target day-of-the-week.
    1028     *
    1029     * @return the nearest date that falls on the specified day-of-the-week.
    1030     */
    1031    public SerialDate getNearestDayOfWeek(final int targetDOW) {
    1032        return getNearestDayOfWeek(targetDOW, this);
    1033    }
    1034
    1035}