    001    /* ========================================================================
    002     * JCommon : a free general purpose class library for the Java(tm) platform
    003     * ========================================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: SerialDate.java,v 1.7 2005/11/03 09:25:17 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
    059    package org.jfree.date;
    060
    061    import java.io.Serializable;
    062    import java.text.DateFormatSymbols;
    063    import java.text.SimpleDateFormat;
    064    import java.util.Calendar;
    065    import 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 : 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     */
    086    public 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 a description of the date.
    846         *
    847         * @return a description of the date.
    848         */
    849        public String getDescription() {
    850            return this.description;
    851        }
    852
    853        /**
    854         * Sets the description for the date.
    855         *
    856         * @param description  the new description for the date.
    857         */
    858        public void setDescription(final String description) {
    859            this.description = description;
    860        }
    861
    862        /**
    863         * Converts the date to a string.
    864         *
    865         * @return  a string representation of the date.
    866         */
    867        public String toString() {
    868            return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth())
    869                                   + "-" + getYYYY();
    870        }
    871
    872        /**
    873         * Returns the year (assume a valid range of 1900 to 9999).
    874         *
    875         * @return the year.
    876         */
    877        public abstract int getYYYY();
    878
    879        /**
    880         * Returns the month (January = 1, February = 2, March = 3).
    881         *
    882         * @return the month of the year.
    883         */
    884        public abstract int getMonth();
    885
    886        /**
    887         * Returns the day of the month.
    888         *
    889         * @return the day of the month.
    890         */
    891        public abstract int getDayOfMonth();
    892
    893        /**
    894         * Returns the day of the week.
    895         *
    896         * @return the day of the week.
    897         */
    898        public abstract int getDayOfWeek();
    899
    900        /**
    901         * Returns the difference (in days) between this date and the specified
    902         * 'other' date.
    903         * <P>
    904         * The result is positive if this date is after the 'other' date and
    905         * negative if it is before the 'other' date.
    906         *
    907         * @param other  the date being compared to.
    908         *
    909         * @return the difference between this and the other date.
    910         */
    911        public abstract int compare(SerialDate other);
    912
    913        /**
    914         * Returns true if this SerialDate represents the same date as the
    915         * specified SerialDate.
    916         *
    917         * @param other  the date being compared to.
    918         *
    919         * @return <code>true</code> if this SerialDate represents the same date as
    920         *         the specified SerialDate.
    921         */
    922        public abstract boolean isOn(SerialDate other);
    923
    924        /**
    925         * Returns true if this SerialDate represents an earlier date compared to
    926         * the specified SerialDate.
    927         *
    928         * @param other  The date being compared to.
    929         *
    930         * @return <code>true</code> if this SerialDate represents an earlier date
    931         *         compared to the specified SerialDate.
    932         */
    933        public abstract boolean isBefore(SerialDate other);
    934
    935        /**
    936         * Returns true if this SerialDate represents the same date as the
    937         * specified SerialDate.
    938         *
    939         * @param other  the date being compared to.
    940         *
    941         * @return <code>true<code> if this SerialDate represents the same date
    942         *         as the specified SerialDate.
    943         */
    944        public abstract boolean isOnOrBefore(SerialDate other);
    945
    946        /**
    947         * Returns true if this SerialDate represents the same date as the
    948         * specified SerialDate.
    949         *
    950         * @param other  the date being compared to.
    951         *
    952         * @return <code>true</code> if this SerialDate represents the same date
    953         *         as the specified SerialDate.
    954         */
    955        public abstract boolean isAfter(SerialDate other);
    956
    957        /**
    958         * Returns true if this SerialDate represents the same date as the
    959         * specified SerialDate.
    960         *
    961         * @param other  the date being compared to.
    962         *
    963         * @return <code>true</code> if this SerialDate represents the same date
    964         *         as the specified SerialDate.
    965         */
    966        public abstract boolean isOnOrAfter(SerialDate other);
    967
    968        /**
    969         * Returns <code>true</code> if this {@link SerialDate} is within the
    970         * specified range (INCLUSIVE).  The date order of d1 and d2 is not
    971         * important.
    972         *
    973         * @param d1  a boundary date for the range.
    974         * @param d2  the other boundary date for the range.
    975         *
    976         * @return A boolean.
    977         */
    978        public abstract boolean isInRange(SerialDate d1, SerialDate d2);
    979
    980        /**
    981         * Returns <code>true</code> if this {@link SerialDate} is within the
    982         * specified range (caller specifies whether or not the end-points are
    983         * included).  The date order of d1 and d2 is not important.
    984         *
    985         * @param d1  a boundary date for the range.
    986         * @param d2  the other boundary date for the range.
    987         * @param include  a code that controls whether or not the start and end
    988         *                 dates are included in the range.
    989         *
    990         * @return A boolean.
    991         */
    992        public abstract boolean isInRange(SerialDate d1, SerialDate d2,
    993                                          int include);
    994
    995        /**
    996         * Returns the latest date that falls on the specified day-of-the-week and
    997         * is BEFORE this date.
    998         *
    999         * @param targetDOW  a code for the target day-of-the-week.
    1000         *
    1001         * @return the latest date that falls on the specified day-of-the-week and
    1002         *         is BEFORE this date.
    1003         */
    1004        public SerialDate getPreviousDayOfWeek(final int targetDOW) {
    1005            return getPreviousDayOfWeek(targetDOW, this);
    1006        }
    1007
    1008        /**
    1009         * Returns the earliest date that falls on the specified day-of-the-week
    1010         * and is AFTER this date.
    1011         *
    1012         * @param targetDOW  a code for the target day-of-the-week.
    1013         *
    1014         * @return the earliest date that falls on the specified day-of-the-week
    1015         *         and is AFTER this date.
    1016         */
    1017        public SerialDate getFollowingDayOfWeek(final int targetDOW) {
    1018            return getFollowingDayOfWeek(targetDOW, this);
    1019        }
    1020
    1021        /**
    1022         * Returns the nearest date that falls on the specified day-of-the-week.
    1023         *
    1024         * @param targetDOW  a code for the target day-of-the-week.
    1025         *
    1026         * @return the nearest date that falls on the specified day-of-the-week.
    1027         */
    1028        public SerialDate getNearestDayOfWeek(final int targetDOW) {
    1029            return getNearestDayOfWeek(targetDOW, this);
    1030        }
    1031
    1032    }