001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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-2022, 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        if (yy < MINIMUM_YEAR_SUPPORTED || yy > MAXIMUM_YEAR_SUPPORTED) {
552            throw new IllegalArgumentException("Call to addMonths resulted in unsupported year");
553        }
554        int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1;
555        int dd = Math.min(base.getDayOfMonth(), 
556                SerialDate.lastDayOfMonth(mm, yy));
557        return SerialDate.createInstance(dd, mm, yy);
558    }
559
560    /**
561     * Creates a new date by adding the specified number of years to the base 
562     * date.
563     *
564     * @param years  the number of years to add (can be negative).
565     * @param base  the base date.
566     *
567     * @return A new date.
568     */
569    public static SerialDate addYears(int years, SerialDate base) {
570        int baseY = base.getYYYY();
571        int baseM = base.getMonth();
572        int baseD = base.getDayOfMonth();
573
574        int targetY = baseY + years;
575        if (targetY < MINIMUM_YEAR_SUPPORTED || targetY > MAXIMUM_YEAR_SUPPORTED) {
576            throw new IllegalArgumentException("Call to addYears resulted in unsupported year");
577        }
578        int targetD = Math.min(baseD, SerialDate.lastDayOfMonth(baseM, targetY));
579        return SerialDate.createInstance(targetD, baseM, targetY);
580    }
581
582    /**
583     * Returns the latest date that falls on the specified day-of-the-week and 
584     * is BEFORE the base date.
585     *
586     * @param targetWeekday  a code for the target day-of-the-week.
587     * @param base  the base date.
588     *
589     * @return the latest date that falls on the specified day-of-the-week and 
590     *         is BEFORE the base date.
591     */
592    public static SerialDate getPreviousDayOfWeek(int targetWeekday, 
593            SerialDate base) {
594
595        // check arguments...
596        if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
597            throw new IllegalArgumentException("Invalid day-of-the-week code.");
598        }
599
600        // find the date...
601        int adjust;
602        int baseDOW = base.getDayOfWeek();
603        if (baseDOW > targetWeekday) {
604            adjust = Math.min(0, targetWeekday - baseDOW);
605        } else {
606            adjust = -7 + Math.max(0, targetWeekday - baseDOW);
607        }
608
609        return SerialDate.addDays(adjust, base);
610
611    }
612
613    /**
614     * Returns the earliest date that falls on the specified day-of-the-week
615     * and is AFTER the base date.
616     *
617     * @param targetWeekday  a code for the target day-of-the-week.
618     * @param base  the base date.
619     *
620     * @return the earliest date that falls on the specified day-of-the-week 
621     *         and is AFTER the base date.
622     */
623    public static SerialDate getFollowingDayOfWeek(int targetWeekday, 
624            SerialDate base) {
625
626        // check arguments...
627        if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
628            throw new IllegalArgumentException(
629                "Invalid day-of-the-week code."
630            );
631        }
632
633        // find the date...
634        int adjust;
635        int baseDOW = base.getDayOfWeek();
636        if (baseDOW > targetWeekday) {
637            adjust = 7 + Math.min(0, targetWeekday - baseDOW);
638        } else {
639            adjust = Math.max(0, targetWeekday - baseDOW);
640        }
641
642        return SerialDate.addDays(adjust, base);
643    }
644
645    /**
646     * Returns the date that falls on the specified day-of-the-week and is
647     * CLOSEST to the base date.
648     *
649     * @param targetDOW  a code for the target day-of-the-week.
650     * @param base  the base date.
651     *
652     * @return the date that falls on the specified day-of-the-week and is 
653     *         CLOSEST to the base date.
654     */
655    public static SerialDate getNearestDayOfWeek(int targetDOW, SerialDate base) {
656
657        // check arguments...
658        if (!SerialDate.isValidWeekdayCode(targetDOW)) {
659            throw new IllegalArgumentException("Invalid day-of-the-week code.");
660        }
661
662        // find the date...
663        final int baseDOW = base.getDayOfWeek();
664        int adjust = -Math.abs(targetDOW - baseDOW);
665        if (adjust >= 4) {
666            adjust = 7 - adjust;
667        }
668        if (adjust <= -4) {
669            adjust = 7 + adjust;
670        }
671        return SerialDate.addDays(adjust, base);
672
673    }
674
675    /**
676     * Rolls the date forward to the last day of the month.
677     *
678     * @param base  the base date.
679     *
680     * @return a new serial date.
681     */
682    public SerialDate getEndOfCurrentMonth(SerialDate base) {
683        int last = SerialDate.lastDayOfMonth(base.getMonth(), base.getYYYY());
684        return SerialDate.createInstance(last, base.getMonth(), base.getYYYY());
685    }
686
687    /**
688     * Returns a string corresponding to the week-in-the-month code.
689     * <P>
690     * Need to find a better approach.
691     *
692     * @param count  an integer code representing the week-in-the-month.
693     *
694     * @return a string corresponding to the week-in-the-month code.
695     */
696    public static String weekInMonthToString(int count) {
697
698        switch (count) {
699            case SerialDate.FIRST_WEEK_IN_MONTH : return "First";
700            case SerialDate.SECOND_WEEK_IN_MONTH : return "Second";
701            case SerialDate.THIRD_WEEK_IN_MONTH : return "Third";
702            case SerialDate.FOURTH_WEEK_IN_MONTH : return "Fourth";
703            case SerialDate.LAST_WEEK_IN_MONTH : return "Last";
704            default :
705                return "SerialDate.weekInMonthToString(): invalid code.";
706        }
707
708    }
709
710    /**
711     * Returns a string representing the supplied 'relative'.
712     * <P>
713     * Need to find a better approach.
714     *
715     * @param relative  a constant representing the 'relative'.
716     *
717     * @return a string representing the supplied 'relative'.
718     */
719    public static String relativeToString(int relative) {
720
721        switch (relative) {
722            case SerialDate.PRECEDING : return "Preceding";
723            case SerialDate.NEAREST : return "Nearest";
724            case SerialDate.FOLLOWING : return "Following";
725            default : return "ERROR : Relative To String";
726        }
727
728    }
729
730    /**
731     * Factory method that returns an instance of some concrete subclass of 
732     * {@link SerialDate}.
733     *
734     * @param day  the day (1-31).
735     * @param month  the month (1-12).
736     * @param yyyy  the year (in the range 1900 to 9999).
737     *
738     * @return An instance of {@link SerialDate}.
739     */
740    public static SerialDate createInstance(int day, int month, int yyyy) {
741        return new SpreadsheetDate(day, month, yyyy);
742    }
743
744    /**
745     * Factory method that returns an instance of some concrete subclass of 
746     * {@link SerialDate}.
747     *
748     * @param serial  the serial number for the day (1 January 1900 = 2).
749     *
750     * @return a instance of SerialDate.
751     */
752    public static SerialDate createInstance(int serial) {
753        return new SpreadsheetDate(serial);
754    }
755
756    /**
757     * Factory method that returns an instance of a subclass of SerialDate.
758     *
759     * @param date  A Java date object.
760     *
761     * @return a instance of SerialDate.
762     */
763    public static SerialDate createInstance(java.util.Date date) {
764
765        GregorianCalendar calendar = new GregorianCalendar();
766        calendar.setTime(date);
767        return new SpreadsheetDate(calendar.get(Calendar.DATE), 
768                calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR));
769
770    }
771
772    /**
773     * Returns the serial number for the date, where 1 January 1900 = 2 (this
774     * corresponds, almost, to the numbering system used in Microsoft Excel for
775     * Windows and Lotus 1-2-3).
776     *
777     * @return the serial number for the date.
778     */
779    public abstract int toSerial();
780
781    /**
782     * Returns a java.util.Date.  Since java.util.Date has more precision than
783     * SerialDate, we need to define a convention for the 'time of day'.
784     *
785     * @return this as {@code java.util.Date}.
786     */
787    public abstract java.util.Date toDate();
788
789    /**
790     * Returns the description that is attached to the date.  It is not 
791     * required that a date have a description, but for some applications it 
792     * is useful.
793     *
794     * @return The description (possibly {@code null}).
795     */
796    public String getDescription() {
797        return this.description;
798    }
799
800    /**
801     * Sets the description for the date.
802     *
803     * @param description  the description for this date ({@code null}
804     *                     permitted).
805     */
806    public void setDescription(String description) {
807        this.description = description;
808    }
809
810    /**
811     * Converts the date to a string.
812     *
813     * @return  a string representation of the date.
814     */
815    @Override
816    public String toString() {
817        return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth())
818                               + "-" + getYYYY();
819    }
820
821    /**
822     * Returns the year (assume a valid range of 1900 to 9999).
823     *
824     * @return the year.
825     */
826    public abstract int getYYYY();
827
828    /**
829     * Returns the month (January = 1, February = 2, March = 3).
830     *
831     * @return the month of the year.
832     */
833    public abstract int getMonth();
834
835    /**
836     * Returns the day of the month.
837     *
838     * @return the day of the month.
839     */
840    public abstract int getDayOfMonth();
841
842    /**
843     * Returns the day of the week.
844     *
845     * @return the day of the week.
846     */
847    public abstract int getDayOfWeek();
848
849    /**
850     * Returns the difference (in days) between this date and the specified 
851     * 'other' date.
852     * <P>
853     * The result is positive if this date is after the 'other' date and
854     * negative if it is before the 'other' date.
855     *
856     * @param other  the date being compared to.
857     *
858     * @return the difference between this and the other date.
859     */
860    public abstract int compare(SerialDate other);
861
862    /**
863     * Returns true if this SerialDate represents the same date as the 
864     * specified SerialDate.
865     *
866     * @param other  the date being compared to.
867     *
868     * @return {@code true} if this SerialDate represents the same date as 
869     *         the specified SerialDate.
870     */
871    public abstract boolean isOn(SerialDate other);
872
873    /**
874     * Returns true if this SerialDate represents an earlier date compared to
875     * the specified SerialDate.
876     *
877     * @param other  The date being compared to.
878     *
879     * @return {@code true} if this SerialDate represents an earlier date 
880     *         compared to the specified SerialDate.
881     */
882    public abstract boolean isBefore(SerialDate other);
883
884    /**
885     * Returns true if this SerialDate represents the same date as the 
886     * specified SerialDate.
887     *
888     * @param other  the date being compared to.
889     *
890     * @return {@code true} if this SerialDate represents the same date
891     *         as the specified SerialDate.
892     */
893    public abstract boolean isOnOrBefore(SerialDate other);
894
895    /**
896     * Returns true if this SerialDate represents the same date as the 
897     * specified SerialDate.
898     *
899     * @param other  the date being compared to.
900     *
901     * @return {@code true} if this SerialDate represents the same date
902     *         as the specified SerialDate.
903     */
904    public abstract boolean isAfter(SerialDate other);
905
906    /**
907     * Returns true if this SerialDate represents the same date as the 
908     * specified SerialDate.
909     *
910     * @param other  the date being compared to.
911     *
912     * @return {@code true} if this SerialDate represents the same date
913     *         as the specified SerialDate.
914     */
915    public abstract boolean isOnOrAfter(SerialDate other);
916
917    /**
918     * Returns {@code true} if this {@link SerialDate} is within the 
919     * specified range (INCLUSIVE).  The date order of d1 and d2 is not 
920     * important.
921     *
922     * @param d1  a boundary date for the range.
923     * @param d2  the other boundary date for the range.
924     *
925     * @return A boolean.
926     */
927    public abstract boolean isInRange(SerialDate d1, SerialDate d2);
928
929    /**
930     * Returns {@code true} if this {@link SerialDate} is within the 
931     * specified range (caller specifies whether or not the end-points are 
932     * included).  The date order of d1 and d2 is not important.
933     *
934     * @param d1  a boundary date for the range.
935     * @param d2  the other boundary date for the range.
936     * @param include  a code that controls whether or not the start and end 
937     *                 dates are included in the range.
938     *
939     * @return A boolean.
940     */
941    public abstract boolean isInRange(SerialDate d1, SerialDate d2, 
942                                      int include);
943
944    /**
945     * Returns the latest date that falls on the specified day-of-the-week and
946     * is BEFORE this date.
947     *
948     * @param targetDOW  a code for the target day-of-the-week.
949     *
950     * @return the latest date that falls on the specified day-of-the-week and
951     *         is BEFORE this date.
952     */
953    public SerialDate getPreviousDayOfWeek(int targetDOW) {
954        return getPreviousDayOfWeek(targetDOW, this);
955    }
956
957    /**
958     * Returns the earliest date that falls on the specified day-of-the-week
959     * and is AFTER this date.
960     *
961     * @param targetDOW  a code for the target day-of-the-week.
962     *
963     * @return the earliest date that falls on the specified day-of-the-week
964     *         and is AFTER this date.
965     */
966    public SerialDate getFollowingDayOfWeek(int targetDOW) {
967        return getFollowingDayOfWeek(targetDOW, this);
968    }
969
970    /**
971     * Returns the nearest date that falls on the specified day-of-the-week.
972     *
973     * @param targetDOW  a code for the target day-of-the-week.
974     *
975     * @return the nearest date that falls on the specified day-of-the-week.
976     */
977    public SerialDate getNearestDayOfWeek(int targetDOW) {
978        return getNearestDayOfWeek(targetDOW, this);
979    }
980
981}
982