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 * SpreadsheetDate.java 029 * -------------------- 030 * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: SpreadsheetDate.java,v 1.10 2006/08/29 13:59:30 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 11-Oct-2001 : Version 1 (DG); 040 * 05-Nov-2001 : Added getDescription() and setDescription() methods (DG); 041 * 12-Nov-2001 : Changed name from ExcelDate.java to SpreadsheetDate.java (DG); 042 * Fixed a bug in calculating day, month and year from serial 043 * number (DG); 044 * 24-Jan-2002 : Fixed a bug in calculating the serial number from the day, 045 * month and year. Thanks to Trevor Hills for the report (DG); 046 * 29-May-2002 : Added equals(Object) method (SourceForge ID 558850) (DG); 047 * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG); 048 * 13-Mar-2003 : Implemented Serializable (DG); 049 * 04-Sep-2003 : Completed isInRange() methods (DG); 050 * 05-Sep-2003 : Implemented Comparable (DG); 051 * 21-Oct-2003 : Added hashCode() method (DG); 052 * 29-Aug-2006 : Removed redundant description attribute (DG); 053 * 054 */ 055 056package org.jfree.date; 057 058import java.util.Calendar; 059import java.util.Date; 060 061/** 062 * Represents a date using an integer, in a similar fashion to the 063 * implementation in Microsoft Excel. The range of dates supported is 064 * 1-Jan-1900 to 31-Dec-9999. 065 * <P> 066 * Be aware that there is a deliberate bug in Excel that recognises the year 067 * 1900 as a leap year when in fact it is not a leap year. You can find more 068 * information on the Microsoft website in article Q181370: 069 * <P> 070 * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp 071 * <P> 072 * Excel uses the convention that 1-Jan-1900 = 1. This class uses the 073 * convention 1-Jan-1900 = 2. 074 * The result is that the day number in this class will be different to the 075 * Excel figure for January and February 1900...but then Excel adds in an extra 076 * day (29-Feb-1900 which does not actually exist!) and from that point forward 077 * the day numbers will match. 078 * 079 * @author David Gilbert 080 */ 081public class SpreadsheetDate extends SerialDate { 082 083 /** For serialization. */ 084 private static final long serialVersionUID = -2039586705374454461L; 085 086 /** 087 * The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 = 088 * 2958465). 089 */ 090 private final int serial; 091 092 /** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */ 093 private final int day; 094 095 /** The month of the year (1 to 12). */ 096 private final int month; 097 098 /** The year (1900 to 9999). */ 099 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}