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
056 package org.jfree.date;
057
058 import java.util.Calendar;
059 import 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 */
081 public 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 }