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