Frames | No Frames |
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: }