Source for org.jfree.date.SerialDate

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ---------------
  28:  * SerialDate.java
  29:  * ---------------
  30:  * (C) Copyright 2001-2006, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: SerialDate.java,v 1.8 2006/08/29 13:44:16 mungady Exp $
  36:  *
  37:  * Changes (from 11-Oct-2001)
  38:  * --------------------------
  39:  * 11-Oct-2001 : Re-organised the class and moved it to new package 
  40:  *               com.jrefinery.date (DG);
  41:  * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate 
  42:  *               class (DG);
  43:  * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate 
  44:  *               class is gone (DG);  Changed getPreviousDayOfWeek(), 
  45:  *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct 
  46:  *               bugs (DG);
  47:  * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
  48:  * 29-May-2002 : Moved the month constants into a separate interface 
  49:  *               (MonthConstants) (DG);
  50:  * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
  51:  * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  52:  * 13-Mar-2003 : Implemented Serializable (DG);
  53:  * 29-May-2003 : Fixed bug in addMonths method (DG);
  54:  * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);
  55:  * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
  56:  * 
  57:  */
  58: 
  59: package org.jfree.date;
  60: 
  61: import java.io.Serializable;
  62: import java.text.DateFormatSymbols;
  63: import java.text.SimpleDateFormat;
  64: import java.util.Calendar;
  65: import java.util.GregorianCalendar;
  66: 
  67: /**
  68:  *  An abstract class that defines our requirements for manipulating dates,
  69:  *  without tying down a particular implementation.
  70:  *  <P>
  71:  *  Requirement 1 : match at least what Excel does for dates;
  72:  *  Requirement 2 : the date represented by the class is immutable;
  73:  *  <P>
  74:  *  Why not just use java.util.Date?  We will, when it makes sense.  At times,
  75:  *  java.util.Date can be *too* precise - it represents an instant in time,
  76:  *  accurate to 1/1000th of a second (with the date itself depending on the
  77:  *  time-zone).  Sometimes we just want to represent a particular day (e.g. 21
  78:  *  January 2015) without concerning ourselves about the time of day, or the
  79:  *  time-zone, or anything else.  That's what we've defined SerialDate for.
  80:  *  <P>
  81:  *  You can call getInstance() to get a concrete subclass of SerialDate,
  82:  *  without worrying about the exact implementation.
  83:  *
  84:  * @author David Gilbert
  85:  */
  86: public abstract class SerialDate implements Comparable, 
  87:                                             Serializable, 
  88:                                             MonthConstants {
  89: 
  90:     /** For serialization. */
  91:     private static final long serialVersionUID = -293716040467423637L;
  92:     
  93:     /** Date format symbols. */
  94:     public static final DateFormatSymbols
  95:         DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols();
  96: 
  97:     /** The serial number for 1 January 1900. */
  98:     public static final int SERIAL_LOWER_BOUND = 2;
  99: 
 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: }