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.8 2006/08/29 13:44:16 mungady Exp $
036     *
037     * Changes (from 11-Oct-2001)
038     * --------------------------
039     * 11-Oct-2001 : Re-organised the class and moved it to new package 
040     *               com.jrefinery.date (DG);
041     * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate 
042     *               class (DG);
043     * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate 
044     *               class is gone (DG);  Changed getPreviousDayOfWeek(), 
045     *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct 
046     *               bugs (DG);
047     * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
048     * 29-May-2002 : Moved the month constants into a separate interface 
049     *               (MonthConstants) (DG);
050     * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
051     * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
052     * 13-Mar-2003 : Implemented Serializable (DG);
053     * 29-May-2003 : Fixed bug in addMonths method (DG);
054     * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);
055     * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
056     * 
057     */
058    
059    package org.jfree.date;
060    
061    import java.io.Serializable;
062    import java.text.DateFormatSymbols;
063    import java.text.SimpleDateFormat;
064    import java.util.Calendar;
065    import java.util.GregorianCalendar;
066    
067    /**
068     *  An abstract class that defines our requirements for manipulating dates,
069     *  without tying down a particular implementation.
070     *  <P>
071     *  Requirement 1 : match at least what Excel does for dates;
072     *  Requirement 2 : 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     */
086    public abstract class SerialDate implements Comparable, 
087                                                Serializable, 
088                                                MonthConstants {
089    
090        /** For serialization. */
091        private static final long serialVersionUID = -293716040467423637L;
092        
093        /** Date format symbols. */
094        public static final DateFormatSymbols
095            DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols();
096    
097        /** The serial number for 1 January 1900. */
098        public static final int SERIAL_LOWER_BOUND = 2;
099    
100        /** The serial number for 31 December 9999. */
101        public static final int SERIAL_UPPER_BOUND = 2958465;
102    
103        /** The lowest year value supported by this date format. */
104        public static final int MINIMUM_YEAR_SUPPORTED = 1900;
105    
106        /** The highest year value supported by this date format. */
107        public static final int MAXIMUM_YEAR_SUPPORTED = 9999;
108    
109        /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */
110        public static final int MONDAY = Calendar.MONDAY;
111    
112        /** 
113         * Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY. 
114         */
115        public static final int TUESDAY = Calendar.TUESDAY;
116    
117        /** 
118         * Useful constant for Wednesday. Equivalent to 
119         * java.util.Calendar.WEDNESDAY. 
120         */
121        public static final int WEDNESDAY = Calendar.WEDNESDAY;
122    
123        /** 
124         * Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY. 
125         */
126        public static final int THURSDAY = Calendar.THURSDAY;
127    
128        /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */
129        public static final int FRIDAY = Calendar.FRIDAY;
130    
131        /** 
132         * Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY.
133         */
134        public static final int SATURDAY = Calendar.SATURDAY;
135    
136        /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */
137        public static final int SUNDAY = Calendar.SUNDAY;
138    
139        /** The number of days in each month in non leap years. */
140        static final int[] LAST_DAY_OF_MONTH =
141            {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
142    
143        /** The number of days in a (non-leap) year up to the end of each month. */
144        static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH =
145            {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
146    
147        /** The number of days in a year up to the end of the preceding month. */
148        static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
149            {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
150    
151        /** The number of days in a leap year up to the end of each month. */
152        static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH =
153            {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
154    
155        /** 
156         * The number of days in a leap year up to the end of the preceding month. 
157         */
158        static final int[] 
159            LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
160                {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
161    
162        /** A useful constant for referring to the first week in a month. */
163        public static final int FIRST_WEEK_IN_MONTH = 1;
164    
165        /** A useful constant for referring to the second week in a month. */
166        public static final int SECOND_WEEK_IN_MONTH = 2;
167    
168        /** A useful constant for referring to the third week in a month. */
169        public static final int THIRD_WEEK_IN_MONTH = 3;
170    
171        /** A useful constant for referring to the fourth week in a month. */
172        public static final int FOURTH_WEEK_IN_MONTH = 4;
173    
174        /** A useful constant for referring to the last week in a month. */
175        public static final int LAST_WEEK_IN_MONTH = 0;
176    
177        /** Useful range constant. */
178        public static final int INCLUDE_NONE = 0;
179    
180        /** Useful range constant. */
181        public static final int INCLUDE_FIRST = 1;
182    
183        /** Useful range constant. */
184        public static final int INCLUDE_SECOND = 2;
185    
186        /** Useful range constant. */
187        public static final int INCLUDE_BOTH = 3;
188    
189        /** 
190         * Useful constant for specifying a day of the week relative to a fixed 
191         * date. 
192         */
193        public static final int PRECEDING = -1;
194    
195        /** 
196         * Useful constant for specifying a day of the week relative to a fixed 
197         * date. 
198         */
199        public static final int NEAREST = 0;
200    
201        /** 
202         * Useful constant for specifying a day of the week relative to a fixed 
203         * date. 
204         */
205        public static final int FOLLOWING = 1;
206    
207        /** A description for the date. */
208        private String description;
209    
210        /**
211         * Default constructor.
212         */
213        protected SerialDate() {
214        }
215    
216        /**
217         * Returns <code>true</code> if the supplied integer code represents a 
218         * valid day-of-the-week, and <code>false</code> otherwise.
219         *
220         * @param code  the code being checked for validity.
221         *
222         * @return <code>true</code> if the supplied integer code represents a 
223         *         valid day-of-the-week, and <code>false</code> otherwise.
224         */
225        public static boolean isValidWeekdayCode(final int code) {
226    
227            switch(code) {
228                case SUNDAY: 
229                case MONDAY: 
230                case TUESDAY: 
231                case WEDNESDAY: 
232                case THURSDAY: 
233                case FRIDAY: 
234                case SATURDAY: 
235                    return true;
236                default: 
237                    return false;
238            }
239    
240        }
241    
242        /**
243         * Converts the supplied string to a day of the week.
244         *
245         * @param s  a string representing the day of the week.
246         *
247         * @return <code>-1</code> if the string is not convertable, the day of 
248         *         the week otherwise.
249         */
250        public static int stringToWeekdayCode(String s) {
251    
252            final String[] shortWeekdayNames 
253                = DATE_FORMAT_SYMBOLS.getShortWeekdays();
254            final String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays();
255    
256            int result = -1;
257            s = s.trim();
258            for (int i = 0; i < weekDayNames.length; i++) {
259                if (s.equals(shortWeekdayNames[i])) {
260                    result = i;
261                    break;
262                }
263                if (s.equals(weekDayNames[i])) {
264                    result = i;
265                    break;
266                }
267            }
268            return result;
269    
270        }
271    
272        /**
273         * Returns a string representing the supplied day-of-the-week.
274         * <P>
275         * Need to find a better approach.
276         *
277         * @param weekday  the day of the week.
278         *
279         * @return a string representing the supplied day-of-the-week.
280         */
281        public static String weekdayCodeToString(final int weekday) {
282    
283            final String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays();
284            return weekdays[weekday];
285    
286        }
287    
288        /**
289         * Returns an array of month names.
290         *
291         * @return an array of month names.
292         */
293        public static String[] getMonths() {
294    
295            return getMonths(false);
296    
297        }
298    
299        /**
300         * Returns an array of month names.
301         *
302         * @param shortened  a flag indicating that shortened month names should 
303         *                   be returned.
304         *
305         * @return an array of month names.
306         */
307        public static String[] getMonths(final boolean shortened) {
308    
309            if (shortened) {
310                return DATE_FORMAT_SYMBOLS.getShortMonths();
311            }
312            else {
313                return DATE_FORMAT_SYMBOLS.getMonths();
314            }
315    
316        }
317    
318        /**
319         * Returns true if the supplied integer code represents a valid month.
320         *
321         * @param code  the code being checked for validity.
322         *
323         * @return <code>true</code> if the supplied integer code represents a 
324         *         valid month.
325         */
326        public static boolean isValidMonthCode(final int code) {
327    
328            switch(code) {
329                case JANUARY: 
330                case FEBRUARY: 
331                case MARCH: 
332                case APRIL: 
333                case MAY: 
334                case JUNE: 
335                case JULY: 
336                case AUGUST: 
337                case SEPTEMBER: 
338                case OCTOBER: 
339                case NOVEMBER: 
340                case DECEMBER: 
341                    return true;
342                default: 
343                    return false;
344            }
345    
346        }
347    
348        /**
349         * Returns the quarter for the specified month.
350         *
351         * @param code  the month code (1-12).
352         *
353         * @return the quarter that the month belongs to.
354         */
355        public static int monthCodeToQuarter(final int code) {
356    
357            switch(code) {
358                case JANUARY: 
359                case FEBRUARY: 
360                case MARCH: return 1;
361                case APRIL: 
362                case MAY: 
363                case JUNE: return 2;
364                case JULY: 
365                case AUGUST: 
366                case SEPTEMBER: return 3;
367                case OCTOBER: 
368                case NOVEMBER: 
369                case DECEMBER: return 4;
370                default: throw new IllegalArgumentException(
371                    "SerialDate.monthCodeToQuarter: invalid month code.");
372            }
373    
374        }
375    
376        /**
377         * Returns a string representing the supplied month.
378         * <P>
379         * The string returned is the long form of the month name taken from the 
380         * default locale.
381         *
382         * @param month  the month.
383         *
384         * @return a string representing the supplied month.
385         */
386        public static String monthCodeToString(final int month) {
387    
388            return monthCodeToString(month, false);
389    
390        }
391    
392        /**
393         * Returns a string representing the supplied month.
394         * <P>
395         * The string returned is the long or short form of the month name taken 
396         * from the default locale.
397         *
398         * @param month  the month.
399         * @param shortened  if <code>true</code> return the abbreviation of the 
400         *                   month.
401         *
402         * @return a string representing the supplied month.
403         */
404        public static String monthCodeToString(final int month, 
405                                               final boolean shortened) {
406    
407            // check arguments...
408            if (!isValidMonthCode(month)) {
409                throw new IllegalArgumentException(
410                    "SerialDate.monthCodeToString: month outside valid range.");
411            }
412    
413            final String[] months;
414    
415            if (shortened) {
416                months = DATE_FORMAT_SYMBOLS.getShortMonths();
417            }
418            else {
419                months = DATE_FORMAT_SYMBOLS.getMonths();
420            }
421    
422            return months[month - 1];
423    
424        }
425    
426        /**
427         * Converts a string to a month code.
428         * <P>
429         * This method will return one of the constants JANUARY, FEBRUARY, ..., 
430         * DECEMBER that corresponds to the string.  If the string is not 
431         * recognised, this method returns -1.
432         *
433         * @param s  the string to parse.
434         *
435         * @return <code>-1</code> if the string is not parseable, the month of the
436         *         year otherwise.
437         */
438        public static int stringToMonthCode(String s) {
439    
440            final String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths();
441            final String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths();
442    
443            int result = -1;
444            s = s.trim();
445    
446            // first try parsing the string as an integer (1-12)...
447            try {
448                result = Integer.parseInt(s);
449            }
450            catch (NumberFormatException e) {
451                // suppress
452            }
453    
454            // now search through the month names...
455            if ((result < 1) || (result > 12)) {
456                for (int i = 0; i < monthNames.length; i++) {
457                    if (s.equals(shortMonthNames[i])) {
458                        result = i + 1;
459                        break;
460                    }
461                    if (s.equals(monthNames[i])) {
462                        result = i + 1;
463                        break;
464                    }
465                }
466            }
467    
468            return result;
469    
470        }
471    
472        /**
473         * Returns true if the supplied integer code represents a valid 
474         * week-in-the-month, and false otherwise.
475         *
476         * @param code  the code being checked for validity.
477         * @return <code>true</code> if the supplied integer code represents a 
478         *         valid week-in-the-month.
479         */
480        public static boolean isValidWeekInMonthCode(final int code) {
481    
482            switch(code) {
483                case FIRST_WEEK_IN_MONTH: 
484                case SECOND_WEEK_IN_MONTH: 
485                case THIRD_WEEK_IN_MONTH: 
486                case FOURTH_WEEK_IN_MONTH: 
487                case LAST_WEEK_IN_MONTH: return true;
488                default: return false;
489            }
490    
491        }
492    
493        /**
494         * Determines whether or not the specified year is a leap year.
495         *
496         * @param yyyy  the year (in the range 1900 to 9999).
497         *
498         * @return <code>true</code> if the specified year is a leap year.
499         */
500        public static boolean isLeapYear(final int yyyy) {
501    
502            if ((yyyy % 4) != 0) {
503                return false;
504            }
505            else if ((yyyy % 400) == 0) {
506                return true;
507            }
508            else if ((yyyy % 100) == 0) {
509                return false;
510            }
511            else {
512                return true;
513            }
514    
515        }
516    
517        /**
518         * Returns the number of leap years from 1900 to the specified year 
519         * INCLUSIVE.
520         * <P>
521         * Note that 1900 is not a leap year.
522         *
523         * @param yyyy  the year (in the range 1900 to 9999).
524         *
525         * @return the number of leap years from 1900 to the specified year.
526         */
527        public static int leapYearCount(final int yyyy) {
528    
529            final int leap4 = (yyyy - 1896) / 4;
530            final int leap100 = (yyyy - 1800) / 100;
531            final int leap400 = (yyyy - 1600) / 400;
532            return leap4 - leap100 + leap400;
533    
534        }
535    
536        /**
537         * Returns the number of the last day of the month, taking into account 
538         * leap years.
539         *
540         * @param month  the month.
541         * @param yyyy  the year (in the range 1900 to 9999).
542         *
543         * @return the number of the last day of the month.
544         */
545        public static int lastDayOfMonth(final int month, final int yyyy) {
546    
547            final int result = LAST_DAY_OF_MONTH[month];
548            if (month != FEBRUARY) {
549                return result;
550            }
551            else if (isLeapYear(yyyy)) {
552                return result + 1;
553            }
554            else {
555                return result;
556            }
557    
558        }
559    
560        /**
561         * Creates a new date by adding the specified number of days to the base 
562         * date.
563         *
564         * @param days  the number of days to add (can be negative).
565         * @param base  the base date.
566         *
567         * @return a new date.
568         */
569        public static SerialDate addDays(final int days, final SerialDate base) {
570    
571            final int serialDayNumber = base.toSerial() + days;
572            return SerialDate.createInstance(serialDayNumber);
573    
574        }
575    
576        /**
577         * Creates a new date by adding the specified number of months to the base 
578         * date.
579         * <P>
580         * If the base date is close to the end of the month, the day on the result
581         * may be adjusted slightly:  31 May + 1 month = 30 June.
582         *
583         * @param months  the number of months to add (can be negative).
584         * @param base  the base date.
585         *
586         * @return a new date.
587         */
588        public static SerialDate addMonths(final int months, 
589                                           final SerialDate base) {
590    
591            final int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) 
592                           / 12;
593            final int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) 
594                           % 12 + 1;
595            final int dd = Math.min(
596                base.getDayOfMonth(), SerialDate.lastDayOfMonth(mm, yy)
597            );
598            return SerialDate.createInstance(dd, mm, yy);
599    
600        }
601    
602        /**
603         * Creates a new date by adding the specified number of years to the base 
604         * date.
605         *
606         * @param years  the number of years to add (can be negative).
607         * @param base  the base date.
608         *
609         * @return A new date.
610         */
611        public static SerialDate addYears(final int years, final SerialDate base) {
612    
613            final int baseY = base.getYYYY();
614            final int baseM = base.getMonth();
615            final int baseD = base.getDayOfMonth();
616    
617            final int targetY = baseY + years;
618            final int targetD = Math.min(
619                baseD, SerialDate.lastDayOfMonth(baseM, targetY)
620            );
621    
622            return SerialDate.createInstance(targetD, baseM, targetY);
623    
624        }
625    
626        /**
627         * Returns the latest date that falls on the specified day-of-the-week and 
628         * is BEFORE the base date.
629         *
630         * @param targetWeekday  a code for the target day-of-the-week.
631         * @param base  the base date.
632         *
633         * @return the latest date that falls on the specified day-of-the-week and 
634         *         is BEFORE the base date.
635         */
636        public static SerialDate getPreviousDayOfWeek(final int targetWeekday, 
637                                                      final SerialDate base) {
638    
639            // check arguments...
640            if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
641                throw new IllegalArgumentException(
642                    "Invalid day-of-the-week code."
643                );
644            }
645    
646            // find the date...
647            final int adjust;
648            final int baseDOW = base.getDayOfWeek();
649            if (baseDOW > targetWeekday) {
650                adjust = Math.min(0, targetWeekday - baseDOW);
651            }
652            else {
653                adjust = -7 + Math.max(0, targetWeekday - baseDOW);
654            }
655    
656            return SerialDate.addDays(adjust, base);
657    
658        }
659    
660        /**
661         * Returns the earliest date that falls on the specified day-of-the-week
662         * and is AFTER the base date.
663         *
664         * @param targetWeekday  a code for the target day-of-the-week.
665         * @param base  the base date.
666         *
667         * @return the earliest date that falls on the specified day-of-the-week 
668         *         and is AFTER the base date.
669         */
670        public static SerialDate getFollowingDayOfWeek(final int targetWeekday, 
671                                                       final SerialDate base) {
672    
673            // check arguments...
674            if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
675                throw new IllegalArgumentException(
676                    "Invalid day-of-the-week code."
677                );
678            }
679    
680            // find the date...
681            final int adjust;
682            final int baseDOW = base.getDayOfWeek();
683            if (baseDOW > targetWeekday) {
684                adjust = 7 + Math.min(0, targetWeekday - baseDOW);
685            }
686            else {
687                adjust = Math.max(0, targetWeekday - baseDOW);
688            }
689    
690            return SerialDate.addDays(adjust, base);
691        }
692    
693        /**
694         * Returns the date that falls on the specified day-of-the-week and is
695         * CLOSEST to the base date.
696         *
697         * @param targetDOW  a code for the target day-of-the-week.
698         * @param base  the base date.
699         *
700         * @return the date that falls on the specified day-of-the-week and is 
701         *         CLOSEST to the base date.
702         */
703        public static SerialDate getNearestDayOfWeek(final int targetDOW,  
704                                                     final SerialDate base) {
705    
706            // check arguments...
707            if (!SerialDate.isValidWeekdayCode(targetDOW)) {
708                throw new IllegalArgumentException(
709                    "Invalid day-of-the-week code."
710                );
711            }
712    
713            // find the date...
714            final int baseDOW = base.getDayOfWeek();
715            int adjust = -Math.abs(targetDOW - baseDOW);
716            if (adjust >= 4) {
717                adjust = 7 - adjust;
718            }
719            if (adjust <= -4) {
720                adjust = 7 + adjust;
721            }
722            return SerialDate.addDays(adjust, base);
723    
724        }
725    
726        /**
727         * Rolls the date forward to the last day of the month.
728         *
729         * @param base  the base date.
730         *
731         * @return a new serial date.
732         */
733        public SerialDate getEndOfCurrentMonth(final SerialDate base) {
734            final int last = SerialDate.lastDayOfMonth(
735                base.getMonth(), base.getYYYY()
736            );
737            return SerialDate.createInstance(last, base.getMonth(), base.getYYYY());
738        }
739    
740        /**
741         * Returns a string corresponding to the week-in-the-month code.
742         * <P>
743         * Need to find a better approach.
744         *
745         * @param count  an integer code representing the week-in-the-month.
746         *
747         * @return a string corresponding to the week-in-the-month code.
748         */
749        public static String weekInMonthToString(final int count) {
750    
751            switch (count) {
752                case SerialDate.FIRST_WEEK_IN_MONTH : return "First";
753                case SerialDate.SECOND_WEEK_IN_MONTH : return "Second";
754                case SerialDate.THIRD_WEEK_IN_MONTH : return "Third";
755                case SerialDate.FOURTH_WEEK_IN_MONTH : return "Fourth";
756                case SerialDate.LAST_WEEK_IN_MONTH : return "Last";
757                default :
758                    return "SerialDate.weekInMonthToString(): invalid code.";
759            }
760    
761        }
762    
763        /**
764         * Returns a string representing the supplied 'relative'.
765         * <P>
766         * Need to find a better approach.
767         *
768         * @param relative  a constant representing the 'relative'.
769         *
770         * @return a string representing the supplied 'relative'.
771         */
772        public static String relativeToString(final int relative) {
773    
774            switch (relative) {
775                case SerialDate.PRECEDING : return "Preceding";
776                case SerialDate.NEAREST : return "Nearest";
777                case SerialDate.FOLLOWING : return "Following";
778                default : return "ERROR : Relative To String";
779            }
780    
781        }
782    
783        /**
784         * Factory method that returns an instance of some concrete subclass of 
785         * {@link SerialDate}.
786         *
787         * @param day  the day (1-31).
788         * @param month  the month (1-12).
789         * @param yyyy  the year (in the range 1900 to 9999).
790         *
791         * @return An instance of {@link SerialDate}.
792         */
793        public static SerialDate createInstance(final int day, final int month, 
794                                                final int yyyy) {
795            return new SpreadsheetDate(day, month, yyyy);
796        }
797    
798        /**
799         * Factory method that returns an instance of some concrete subclass of 
800         * {@link SerialDate}.
801         *
802         * @param serial  the serial number for the day (1 January 1900 = 2).
803         *
804         * @return a instance of SerialDate.
805         */
806        public static SerialDate createInstance(final int serial) {
807            return new SpreadsheetDate(serial);
808        }
809    
810        /**
811         * Factory method that returns an instance of a subclass of SerialDate.
812         *
813         * @param date  A Java date object.
814         *
815         * @return a instance of SerialDate.
816         */
817        public static SerialDate createInstance(final java.util.Date date) {
818    
819            final GregorianCalendar calendar = new GregorianCalendar();
820            calendar.setTime(date);
821            return new SpreadsheetDate(calendar.get(Calendar.DATE),
822                                       calendar.get(Calendar.MONTH) + 1,
823                                       calendar.get(Calendar.YEAR));
824    
825        }
826    
827        /**
828         * Returns the serial number for the date, where 1 January 1900 = 2 (this
829         * corresponds, almost, to the numbering system used in Microsoft Excel for
830         * Windows and Lotus 1-2-3).
831         *
832         * @return the serial number for the date.
833         */
834        public abstract int toSerial();
835    
836        /**
837         * Returns a java.util.Date.  Since java.util.Date has more precision than
838         * SerialDate, we need to define a convention for the 'time of day'.
839         *
840         * @return this as <code>java.util.Date</code>.
841         */
842        public abstract java.util.Date toDate();
843    
844        /**
845         * Returns 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    }