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: * SpreadsheetDate.java 29: * -------------------- 30: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * $Id: SpreadsheetDate.java,v 1.10 2006/08/29 13:59:30 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 11-Oct-2001 : Version 1 (DG); 40: * 05-Nov-2001 : Added getDescription() and setDescription() methods (DG); 41: * 12-Nov-2001 : Changed name from ExcelDate.java to SpreadsheetDate.java (DG); 42: * Fixed a bug in calculating day, month and year from serial 43: * number (DG); 44: * 24-Jan-2002 : Fixed a bug in calculating the serial number from the day, 45: * month and year. Thanks to Trevor Hills for the report (DG); 46: * 29-May-2002 : Added equals(Object) method (SourceForge ID 558850) (DG); 47: * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG); 48: * 13-Mar-2003 : Implemented Serializable (DG); 49: * 04-Sep-2003 : Completed isInRange() methods (DG); 50: * 05-Sep-2003 : Implemented Comparable (DG); 51: * 21-Oct-2003 : Added hashCode() method (DG); 52: * 29-Aug-2006 : Removed redundant description attribute (DG); 53: * 54: */ 55: 56: package org.jfree.date; 57: 58: import java.util.Calendar; 59: import java.util.Date; 60: 61: /** 62: * Represents a date using an integer, in a similar fashion to the 63: * implementation in Microsoft Excel. The range of dates supported is 64: * 1-Jan-1900 to 31-Dec-9999. 65: * <P> 66: * Be aware that there is a deliberate bug in Excel that recognises the year 67: * 1900 as a leap year when in fact it is not a leap year. You can find more 68: * information on the Microsoft website in article Q181370: 69: * <P> 70: * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp 71: * <P> 72: * Excel uses the convention that 1-Jan-1900 = 1. This class uses the 73: * convention 1-Jan-1900 = 2. 74: * The result is that the day number in this class will be different to the 75: * Excel figure for January and February 1900...but then Excel adds in an extra 76: * day (29-Feb-1900 which does not actually exist!) and from that point forward 77: * the day numbers will match. 78: * 79: * @author David Gilbert 80: */ 81: public class SpreadsheetDate extends SerialDate { 82: 83: /** For serialization. */ 84: private static final long serialVersionUID = -2039586705374454461L; 85: 86: /** 87: * The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 = 88: * 2958465). 89: */ 90: private final int serial; 91: 92: /** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */ 93: private final int day; 94: 95: /** The month of the year (1 to 12). */ 96: private final int month; 97: 98: /** The year (1900 to 9999). */ 99: private final int year; 100: 101: /** 102: * Creates a new date instance. 103: * 104: * @param day the day (in the range 1 to 28/29/30/31). 105: * @param month the month (in the range 1 to 12). 106: * @param year the year (in the range 1900 to 9999). 107: */ 108: public SpreadsheetDate(final int day, final int month, final int year) { 109: 110: if ((year >= 1900) && (year <= 9999)) { 111: this.year = year; 112: } 113: else { 114: throw new IllegalArgumentException( 115: "The 'year' argument must be in range 1900 to 9999." 116: ); 117: } 118: 119: if ((month >= MonthConstants.JANUARY) 120: && (month <= MonthConstants.DECEMBER)) { 121: this.month = month; 122: } 123: else { 124: throw new IllegalArgumentException( 125: "The 'month' argument must be in the range 1 to 12." 126: ); 127: } 128: 129: if ((day >= 1) && (day <= SerialDate.lastDayOfMonth(month, year))) { 130: this.day = day; 131: } 132: else { 133: throw new IllegalArgumentException("Invalid 'day' argument."); 134: } 135: 136: // the serial number needs to be synchronised with the day-month-year... 137: this.serial = calcSerial(day, month, year); 138: 139: } 140: 141: /** 142: * Standard constructor - creates a new date object representing the 143: * specified day number (which should be in the range 2 to 2958465. 144: * 145: * @param serial the serial number for the day (range: 2 to 2958465). 146: */ 147: public SpreadsheetDate(final int serial) { 148: 149: if ((serial >= SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) { 150: this.serial = serial; 151: } 152: else { 153: throw new IllegalArgumentException( 154: "SpreadsheetDate: Serial must be in range 2 to 2958465."); 155: } 156: 157: // the day-month-year needs to be synchronised with the serial number... 158: // get the year from the serial date 159: final int days = this.serial - SERIAL_LOWER_BOUND; 160: // overestimated because we ignored leap days 161: final int overestimatedYYYY = 1900 + (days / 365); 162: final int leaps = SerialDate.leapYearCount(overestimatedYYYY); 163: final int nonleapdays = days - leaps; 164: // underestimated because we overestimated years 165: int underestimatedYYYY = 1900 + (nonleapdays / 365); 166: 167: if (underestimatedYYYY == overestimatedYYYY) { 168: this.year = underestimatedYYYY; 169: } 170: else { 171: int ss1 = calcSerial(1, 1, underestimatedYYYY); 172: while (ss1 <= this.serial) { 173: underestimatedYYYY = underestimatedYYYY + 1; 174: ss1 = calcSerial(1, 1, underestimatedYYYY); 175: } 176: this.year = underestimatedYYYY - 1; 177: } 178: 179: final int ss2 = calcSerial(1, 1, this.year); 180: 181: int[] daysToEndOfPrecedingMonth 182: = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH; 183: 184: if (isLeapYear(this.year)) { 185: daysToEndOfPrecedingMonth 186: = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH; 187: } 188: 189: // get the month from the serial date 190: int mm = 1; 191: int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1; 192: while (sss < this.serial) { 193: mm = mm + 1; 194: sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1; 195: } 196: this.month = mm - 1; 197: 198: // what's left is d(+1); 199: this.day = this.serial - ss2 200: - daysToEndOfPrecedingMonth[this.month] + 1; 201: 202: } 203: 204: /** 205: * Returns the serial number for the date, where 1 January 1900 = 2 206: * (this corresponds, almost, to the numbering system used in Microsoft 207: * Excel for Windows and Lotus 1-2-3). 208: * 209: * @return The serial number of this date. 210: */ 211: public int toSerial() { 212: return this.serial; 213: } 214: 215: /** 216: * Returns a <code>java.util.Date</code> equivalent to this date. 217: * 218: * @return The date. 219: */ 220: public Date toDate() { 221: final Calendar calendar = Calendar.getInstance(); 222: calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0, 0); 223: return calendar.getTime(); 224: } 225: 226: /** 227: * Returns the year (assume a valid range of 1900 to 9999). 228: * 229: * @return The year. 230: */ 231: public int getYYYY() { 232: return this.year; 233: } 234: 235: /** 236: * Returns the month (January = 1, February = 2, March = 3). 237: * 238: * @return The month of the year. 239: */ 240: public int getMonth() { 241: return this.month; 242: } 243: 244: /** 245: * Returns the day of the month. 246: * 247: * @return The day of the month. 248: */ 249: public int getDayOfMonth() { 250: return this.day; 251: } 252: 253: /** 254: * Returns a code representing the day of the week. 255: * <P> 256: * The codes are defined in the {@link SerialDate} class as: 257: * <code>SUNDAY</code>, <code>MONDAY</code>, <code>TUESDAY</code>, 258: * <code>WEDNESDAY</code>, <code>THURSDAY</code>, <code>FRIDAY</code>, and 259: * <code>SATURDAY</code>. 260: * 261: * @return A code representing the day of the week. 262: */ 263: public int getDayOfWeek() { 264: return (this.serial + 6) % 7 + 1; 265: } 266: 267: /** 268: * Tests the equality of this date with an arbitrary object. 269: * <P> 270: * This method will return true ONLY if the object is an instance of the 271: * {@link SerialDate} base class, and it represents the same day as this 272: * {@link SpreadsheetDate}. 273: * 274: * @param object the object to compare (<code>null</code> permitted). 275: * 276: * @return A boolean. 277: */ 278: public boolean equals(final Object object) { 279: 280: if (object instanceof SerialDate) { 281: final SerialDate s = (SerialDate) object; 282: return (s.toSerial() == this.toSerial()); 283: } 284: else { 285: return false; 286: } 287: 288: } 289: 290: /** 291: * Returns a hash code for this object instance. 292: * 293: * @return A hash code. 294: */ 295: public int hashCode() { 296: return toSerial(); 297: } 298: 299: /** 300: * Returns the difference (in days) between this date and the specified 301: * 'other' date. 302: * 303: * @param other the date being compared to. 304: * 305: * @return The difference (in days) between this date and the specified 306: * 'other' date. 307: */ 308: public int compare(final SerialDate other) { 309: return this.serial - other.toSerial(); 310: } 311: 312: /** 313: * Implements the method required by the Comparable interface. 314: * 315: * @param other the other object (usually another SerialDate). 316: * 317: * @return A negative integer, zero, or a positive integer as this object 318: * is less than, equal to, or greater than the specified object. 319: */ 320: public int compareTo(final Object other) { 321: return compare((SerialDate) other); 322: } 323: 324: /** 325: * Returns true if this SerialDate represents the same date as the 326: * specified SerialDate. 327: * 328: * @param other the date being compared to. 329: * 330: * @return <code>true</code> if this SerialDate represents the same date as 331: * the specified SerialDate. 332: */ 333: public boolean isOn(final SerialDate other) { 334: return (this.serial == other.toSerial()); 335: } 336: 337: /** 338: * Returns true if this SerialDate represents an earlier date compared to 339: * the specified SerialDate. 340: * 341: * @param other the date being compared to. 342: * 343: * @return <code>true</code> if this SerialDate represents an earlier date 344: * compared to the specified SerialDate. 345: */ 346: public boolean isBefore(final SerialDate other) { 347: return (this.serial < other.toSerial()); 348: } 349: 350: /** 351: * Returns true if this SerialDate represents the same date as the 352: * specified SerialDate. 353: * 354: * @param other the date being compared to. 355: * 356: * @return <code>true</code> if this SerialDate represents the same date 357: * as the specified SerialDate. 358: */ 359: public boolean isOnOrBefore(final SerialDate other) { 360: return (this.serial <= other.toSerial()); 361: } 362: 363: /** 364: * Returns true if this SerialDate represents the same date as the 365: * specified SerialDate. 366: * 367: * @param other the date being compared to. 368: * 369: * @return <code>true</code> if this SerialDate represents the same date 370: * as the specified SerialDate. 371: */ 372: public boolean isAfter(final SerialDate other) { 373: return (this.serial > other.toSerial()); 374: } 375: 376: /** 377: * Returns true if this SerialDate represents the same date as the 378: * specified SerialDate. 379: * 380: * @param other the date being compared to. 381: * 382: * @return <code>true</code> if this SerialDate represents the same date as 383: * the specified SerialDate. 384: */ 385: public boolean isOnOrAfter(final SerialDate other) { 386: return (this.serial >= other.toSerial()); 387: } 388: 389: /** 390: * Returns <code>true</code> if this {@link SerialDate} is within the 391: * specified range (INCLUSIVE). The date order of d1 and d2 is not 392: * important. 393: * 394: * @param d1 a boundary date for the range. 395: * @param d2 the other boundary date for the range. 396: * 397: * @return A boolean. 398: */ 399: public boolean isInRange(final SerialDate d1, final SerialDate d2) { 400: return isInRange(d1, d2, SerialDate.INCLUDE_BOTH); 401: } 402: 403: /** 404: * Returns true if this SerialDate is within the specified range (caller 405: * specifies whether or not the end-points are included). The order of d1 406: * and d2 is not important. 407: * 408: * @param d1 one boundary date for the range. 409: * @param d2 a second boundary date for the range. 410: * @param include a code that controls whether or not the start and end 411: * dates are included in the range. 412: * 413: * @return <code>true</code> if this SerialDate is within the specified 414: * range. 415: */ 416: public boolean isInRange(final SerialDate d1, final SerialDate d2, 417: final int include) { 418: final int s1 = d1.toSerial(); 419: final int s2 = d2.toSerial(); 420: final int start = Math.min(s1, s2); 421: final int end = Math.max(s1, s2); 422: 423: final int s = toSerial(); 424: if (include == SerialDate.INCLUDE_BOTH) { 425: return (s >= start && s <= end); 426: } 427: else if (include == SerialDate.INCLUDE_FIRST) { 428: return (s >= start && s < end); 429: } 430: else if (include == SerialDate.INCLUDE_SECOND) { 431: return (s > start && s <= end); 432: } 433: else { 434: return (s > start && s < end); 435: } 436: } 437: 438: /** 439: * Calculate the serial number from the day, month and year. 440: * <P> 441: * 1-Jan-1900 = 2. 442: * 443: * @param d the day. 444: * @param m the month. 445: * @param y the year. 446: * 447: * @return the serial number from the day, month and year. 448: */ 449: private int calcSerial(final int d, final int m, final int y) { 450: final int yy = ((y - 1900) * 365) + SerialDate.leapYearCount(y - 1); 451: int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m]; 452: if (m > MonthConstants.FEBRUARY) { 453: if (SerialDate.isLeapYear(y)) { 454: mm = mm + 1; 455: } 456: } 457: final int dd = d; 458: return yy + mm + dd + 1; 459: } 460: 461: }