001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 */ 028 029package org.jfree.chart.date; 030 031import java.util.Calendar; 032import java.util.Date; 033 034/** 035 * Represents a date using an integer, in a similar fashion to the 036 * implementation in Microsoft Excel. The range of dates supported is 037 * 1-Jan-1900 to 31-Dec-9999. 038 * <P> 039 * Be aware that there is a deliberate bug in Excel that recognises the year 040 * 1900 as a leap year when in fact it is not a leap year. You can find more 041 * information on the Microsoft website in article Q181370: 042 * <P> 043 * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp 044 * <P> 045 * Excel uses the convention that 1-Jan-1900 = 1. This class uses the 046 * convention 1-Jan-1900 = 2. 047 * The result is that the day number in this class will be different to the 048 * Excel figure for January and February 1900...but then Excel adds in an extra 049 * day (29-Feb-1900 which does not actually exist!) and from that point forward 050 * the day numbers will match. 051 */ 052public class SpreadsheetDate extends SerialDate { 053 054 /** For serialization. */ 055 private static final long serialVersionUID = -2039586705374454461L; 056 057 /** 058 * The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 = 059 * 2958465). 060 */ 061 private final int serial; 062 063 /** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */ 064 private final int day; 065 066 /** The month of the year (1 to 12). */ 067 private final int month; 068 069 /** The year (1900 to 9999). */ 070 private final int year; 071 072 /** 073 * Creates a new date instance. 074 * 075 * @param day the day (in the range 1 to 28/29/30/31). 076 * @param month the month (in the range 1 to 12). 077 * @param year the year (in the range 1900 to 9999). 078 */ 079 public SpreadsheetDate(int day, int month, int year) { 080 081 if ((year >= 1900) && (year <= 9999)) { 082 this.year = year; 083 } 084 else { 085 throw new IllegalArgumentException( 086 "The 'year' argument must be in range 1900 to 9999."); 087 } 088 089 if ((month >= MonthConstants.JANUARY) 090 && (month <= MonthConstants.DECEMBER)) { 091 this.month = month; 092 } 093 else { 094 throw new IllegalArgumentException( 095 "The 'month' argument must be in the range 1 to 12."); 096 } 097 098 if ((day >= 1) && (day <= SerialDate.lastDayOfMonth(month, year))) { 099 this.day = day; 100 } else { 101 throw new IllegalArgumentException("Invalid 'day' argument."); 102 } 103 104 // the serial number needs to be synchronised with the day-month-year... 105 this.serial = calcSerial(day, month, year); 106 } 107 108 /** 109 * Standard constructor - creates a new date object representing the 110 * specified day number (which should be in the range 2 to 2958465. 111 * 112 * @param serial the serial number for the day (range: 2 to 2958465). 113 */ 114 public SpreadsheetDate(int serial) { 115 116 if ((serial >= SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) { 117 this.serial = serial; 118 } 119 else { 120 throw new IllegalArgumentException( 121 "SpreadsheetDate: Serial must be in range 2 to 2958465."); 122 } 123 124 // the day-month-year needs to be synchronised with the serial number... 125 // get the year from the serial date 126 final int days = this.serial - SERIAL_LOWER_BOUND; 127 // overestimated because we ignored leap days 128 final int overestimatedYYYY = 1900 + (days / 365); 129 final int leaps = SerialDate.leapYearCount(overestimatedYYYY); 130 final int nonleapdays = days - leaps; 131 // underestimated because we overestimated years 132 int underestimatedYYYY = 1900 + (nonleapdays / 365); 133 134 if (underestimatedYYYY == overestimatedYYYY) { 135 this.year = underestimatedYYYY; 136 } else { 137 int ss1 = calcSerial(1, 1, underestimatedYYYY); 138 while (ss1 <= this.serial) { 139 underestimatedYYYY = underestimatedYYYY + 1; 140 ss1 = calcSerial(1, 1, underestimatedYYYY); 141 } 142 this.year = underestimatedYYYY - 1; 143 } 144 145 final int ss2 = calcSerial(1, 1, this.year); 146 147 int[] daysToEndOfPrecedingMonth 148 = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH; 149 150 if (isLeapYear(this.year)) { 151 daysToEndOfPrecedingMonth 152 = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH; 153 } 154 155 // get the month from the serial date 156 int mm = 1; 157 int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1; 158 while (sss < this.serial) { 159 mm = mm + 1; 160 sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1; 161 } 162 this.month = mm - 1; 163 164 // what's left is d(+1); 165 this.day = this.serial - ss2 166 - daysToEndOfPrecedingMonth[this.month] + 1; 167 168 } 169 170 /** 171 * Returns the serial number for the date, where 1 January 1900 = 2 172 * (this corresponds, almost, to the numbering system used in Microsoft 173 * Excel for Windows and Lotus 1-2-3). 174 * 175 * @return The serial number of this date. 176 */ 177 @Override 178 public int toSerial() { 179 return this.serial; 180 } 181 182 /** 183 * Returns a {@code java.util.Date} equivalent to this date. 184 * 185 * @return The date. 186 */ 187 @Override 188 public Date toDate() { 189 Calendar calendar = Calendar.getInstance(); 190 calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0, 0); 191 return calendar.getTime(); 192 } 193 194 /** 195 * Returns the year (assume a valid range of 1900 to 9999). 196 * 197 * @return The year. 198 */ 199 @Override 200 public int getYYYY() { 201 return this.year; 202 } 203 204 /** 205 * Returns the month (January = 1, February = 2, March = 3). 206 * 207 * @return The month of the year. 208 */ 209 @Override 210 public int getMonth() { 211 return this.month; 212 } 213 214 /** 215 * Returns the day of the month. 216 * 217 * @return The day of the month. 218 */ 219 @Override 220 public int getDayOfMonth() { 221 return this.day; 222 } 223 224 /** 225 * Returns a code representing the day of the week. 226 * <P> 227 * The codes are defined in the {@link SerialDate} class as: 228 * {@code SUNDAY}, {@code MONDAY}, {@code TUESDAY}, 229 * {@code WEDNESDAY}, {@code THURSDAY}, {@code FRIDAY}, and 230 * {@code SATURDAY}. 231 * 232 * @return A code representing the day of the week. 233 */ 234 @Override 235 public int getDayOfWeek() { 236 return (this.serial + 6) % 7 + 1; 237 } 238 239 /** 240 * Tests the equality of this date with an arbitrary object. 241 * <P> 242 * This method will return true ONLY if the object is an instance of the 243 * {@link SerialDate} base class, and it represents the same day as this 244 * {@link SpreadsheetDate}. 245 * 246 * @param object the object to compare ({@code null} permitted). 247 * 248 * @return A boolean. 249 */ 250 @Override 251 public boolean equals(Object object) { 252 253 if (object instanceof SerialDate) { 254 SerialDate s = (SerialDate) object; 255 return (s.toSerial() == this.toSerial()); 256 } else { 257 return false; 258 } 259 260 } 261 262 /** 263 * Returns a hash code for this object instance. 264 * 265 * @return A hash code. 266 */ 267 @Override 268 public int hashCode() { 269 return toSerial(); 270 } 271 272 /** 273 * Returns the difference (in days) between this date and the specified 274 * 'other' date. 275 * 276 * @param other the date being compared to. 277 * 278 * @return The difference (in days) between this date and the specified 279 * 'other' date. 280 */ 281 @Override 282 public int compare(SerialDate other) { 283 return this.serial - other.toSerial(); 284 } 285 286 /** 287 * Implements the method required by the Comparable interface. 288 * 289 * @param other the other object (usually another SerialDate). 290 * 291 * @return A negative integer, zero, or a positive integer as this object 292 * is less than, equal to, or greater than the specified object. 293 */ 294 @Override 295 public int compareTo(Object other) { 296 return compare((SerialDate) other); 297 } 298 299 /** 300 * Returns true if this SerialDate represents the same date as the 301 * specified SerialDate. 302 * 303 * @param other the date being compared to. 304 * 305 * @return {@code true} if this SerialDate represents the same date as 306 * the specified SerialDate. 307 */ 308 @Override 309 public boolean isOn(SerialDate other) { 310 return (this.serial == other.toSerial()); 311 } 312 313 /** 314 * Returns true if this SerialDate represents an earlier date compared to 315 * the specified SerialDate. 316 * 317 * @param other the date being compared to. 318 * 319 * @return {@code true} if this SerialDate represents an earlier date 320 * compared to the specified SerialDate. 321 */ 322 @Override 323 public boolean isBefore(SerialDate other) { 324 return (this.serial < other.toSerial()); 325 } 326 327 /** 328 * Returns true if this SerialDate represents the same date as the 329 * specified SerialDate. 330 * 331 * @param other the date being compared to. 332 * 333 * @return {@code true} if this SerialDate represents the same date 334 * as the specified SerialDate. 335 */ 336 @Override 337 public boolean isOnOrBefore(SerialDate other) { 338 return (this.serial <= other.toSerial()); 339 } 340 341 /** 342 * Returns true if this SerialDate represents the same date as the 343 * specified SerialDate. 344 * 345 * @param other the date being compared to. 346 * 347 * @return {@code true} if this SerialDate represents the same date 348 * as the specified SerialDate. 349 */ 350 @Override 351 public boolean isAfter(SerialDate other) { 352 return (this.serial > other.toSerial()); 353 } 354 355 /** 356 * Returns true if this SerialDate represents the same date as the 357 * specified SerialDate. 358 * 359 * @param other the date being compared to. 360 * 361 * @return {@code true} if this SerialDate represents the same date as 362 * the specified SerialDate. 363 */ 364 @Override 365 public boolean isOnOrAfter(SerialDate other) { 366 return (this.serial >= other.toSerial()); 367 } 368 369 /** 370 * Returns {@code true} if this {@link SerialDate} is within the 371 * specified range (INCLUSIVE). The date order of d1 and d2 is not 372 * important. 373 * 374 * @param d1 a boundary date for the range. 375 * @param d2 the other boundary date for the range. 376 * 377 * @return A boolean. 378 */ 379 @Override 380 public boolean isInRange(SerialDate d1, SerialDate d2) { 381 return isInRange(d1, d2, SerialDate.INCLUDE_BOTH); 382 } 383 384 /** 385 * Returns true if this SerialDate is within the specified range (caller 386 * specifies whether or not the end-points are included). The order of d1 387 * and d2 is not important. 388 * 389 * @param d1 one boundary date for the range. 390 * @param d2 a second boundary date for the range. 391 * @param include a code that controls whether or not the start and end 392 * dates are included in the range. 393 * 394 * @return {@code true} if this SerialDate is within the specified 395 * range. 396 */ 397 @Override 398 public boolean isInRange(SerialDate d1, SerialDate d2, int include) { 399 int s1 = d1.toSerial(); 400 int s2 = d2.toSerial(); 401 int start = Math.min(s1, s2); 402 int end = Math.max(s1, s2); 403 404 int s = toSerial(); 405 if (include == SerialDate.INCLUDE_BOTH) { 406 return (s >= start && s <= end); 407 } 408 else if (include == SerialDate.INCLUDE_FIRST) { 409 return (s >= start && s < end); 410 } 411 else if (include == SerialDate.INCLUDE_SECOND) { 412 return (s > start && s <= end); 413 } 414 else { 415 return (s > start && s < end); 416 } 417 } 418 419 /** 420 * Calculate the serial number from the day, month and year. 421 * <P> 422 * 1-Jan-1900 = 2. 423 * 424 * @param d the day. 425 * @param m the month. 426 * @param y the year. 427 * 428 * @return the serial number from the day, month and year. 429 */ 430 private int calcSerial(int d, int m, int y) { 431 int yy = ((y - 1900) * 365) + SerialDate.leapYearCount(y - 1); 432 int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m]; 433 if (m > MonthConstants.FEBRUARY) { 434 if (SerialDate.isLeapYear(y)) { 435 mm = mm + 1; 436 } 437 } 438 int dd = d; 439 return yy + mm + dd + 1; 440 } 441 442} 443