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 }