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