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 }