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,
109: final int year) {
110:
111: if ((year >= 1900) && (year <= 9999)) {
112: this .year = year;
113: } else {
114: throw new IllegalArgumentException(
115: "The 'year' argument must be in range 1900 to 9999.");
116: }
117:
118: if ((month >= MonthConstants.JANUARY)
119: && (month <= MonthConstants.DECEMBER)) {
120: this .month = month;
121: } else {
122: throw new IllegalArgumentException(
123: "The 'month' argument must be in the range 1 to 12.");
124: }
125:
126: if ((day >= 1)
127: && (day <= SerialDate.lastDayOfMonth(month, year))) {
128: this .day = day;
129: } else {
130: throw new IllegalArgumentException(
131: "Invalid 'day' argument.");
132: }
133:
134: // the serial number needs to be synchronised with the day-month-year...
135: this .serial = calcSerial(day, month, year);
136:
137: }
138:
139: /**
140: * Standard constructor - creates a new date object representing the
141: * specified day number (which should be in the range 2 to 2958465.
142: *
143: * @param serial the serial number for the day (range: 2 to 2958465).
144: */
145: public SpreadsheetDate(final int serial) {
146:
147: if ((serial >= SERIAL_LOWER_BOUND)
148: && (serial <= SERIAL_UPPER_BOUND)) {
149: this .serial = serial;
150: } else {
151: throw new IllegalArgumentException(
152: "SpreadsheetDate: Serial must be in range 2 to 2958465.");
153: }
154:
155: // the day-month-year needs to be synchronised with the serial number...
156: // get the year from the serial date
157: final int days = this .serial - SERIAL_LOWER_BOUND;
158: // overestimated because we ignored leap days
159: final int overestimatedYYYY = 1900 + (days / 365);
160: final int leaps = SerialDate.leapYearCount(overestimatedYYYY);
161: final int nonleapdays = days - leaps;
162: // underestimated because we overestimated years
163: int underestimatedYYYY = 1900 + (nonleapdays / 365);
164:
165: if (underestimatedYYYY == overestimatedYYYY) {
166: this .year = underestimatedYYYY;
167: } else {
168: int ss1 = calcSerial(1, 1, underestimatedYYYY);
169: while (ss1 <= this .serial) {
170: underestimatedYYYY = underestimatedYYYY + 1;
171: ss1 = calcSerial(1, 1, underestimatedYYYY);
172: }
173: this .year = underestimatedYYYY - 1;
174: }
175:
176: final int ss2 = calcSerial(1, 1, this .year);
177:
178: int[] daysToEndOfPrecedingMonth = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
179:
180: if (isLeapYear(this .year)) {
181: daysToEndOfPrecedingMonth = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
182: }
183:
184: // get the month from the serial date
185: int mm = 1;
186: int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
187: while (sss < this .serial) {
188: mm = mm + 1;
189: sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
190: }
191: this .month = mm - 1;
192:
193: // what's left is d(+1);
194: this .day = this .serial - ss2
195: - daysToEndOfPrecedingMonth[this .month] + 1;
196:
197: }
198:
199: /**
200: * Returns the serial number for the date, where 1 January 1900 = 2
201: * (this corresponds, almost, to the numbering system used in Microsoft
202: * Excel for Windows and Lotus 1-2-3).
203: *
204: * @return The serial number of this date.
205: */
206: public int toSerial() {
207: return this .serial;
208: }
209:
210: /**
211: * Returns a <code>java.util.Date</code> equivalent to this date.
212: *
213: * @return The date.
214: */
215: public Date toDate() {
216: final Calendar calendar = Calendar.getInstance();
217: calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0,
218: 0);
219: return calendar.getTime();
220: }
221:
222: /**
223: * Returns the year (assume a valid range of 1900 to 9999).
224: *
225: * @return The year.
226: */
227: public int getYYYY() {
228: return this .year;
229: }
230:
231: /**
232: * Returns the month (January = 1, February = 2, March = 3).
233: *
234: * @return The month of the year.
235: */
236: public int getMonth() {
237: return this .month;
238: }
239:
240: /**
241: * Returns the day of the month.
242: *
243: * @return The day of the month.
244: */
245: public int getDayOfMonth() {
246: return this .day;
247: }
248:
249: /**
250: * Returns a code representing the day of the week.
251: * <P>
252: * The codes are defined in the {@link SerialDate} class as:
253: * <code>SUNDAY</code>, <code>MONDAY</code>, <code>TUESDAY</code>,
254: * <code>WEDNESDAY</code>, <code>THURSDAY</code>, <code>FRIDAY</code>, and
255: * <code>SATURDAY</code>.
256: *
257: * @return A code representing the day of the week.
258: */
259: public int getDayOfWeek() {
260: return (this .serial + 6) % 7 + 1;
261: }
262:
263: /**
264: * Tests the equality of this date with an arbitrary object.
265: * <P>
266: * This method will return true ONLY if the object is an instance of the
267: * {@link SerialDate} base class, and it represents the same day as this
268: * {@link SpreadsheetDate}.
269: *
270: * @param object the object to compare (<code>null</code> permitted).
271: *
272: * @return A boolean.
273: */
274: public boolean equals(final Object object) {
275:
276: if (object instanceof SerialDate) {
277: final SerialDate s = (SerialDate) object;
278: return (s.toSerial() == this .toSerial());
279: } else {
280: return false;
281: }
282:
283: }
284:
285: /**
286: * Returns a hash code for this object instance.
287: *
288: * @return A hash code.
289: */
290: public int hashCode() {
291: return toSerial();
292: }
293:
294: /**
295: * Returns the difference (in days) between this date and the specified
296: * 'other' date.
297: *
298: * @param other the date being compared to.
299: *
300: * @return The difference (in days) between this date and the specified
301: * 'other' date.
302: */
303: public int compare(final SerialDate other) {
304: return this .serial - other.toSerial();
305: }
306:
307: /**
308: * Implements the method required by the Comparable interface.
309: *
310: * @param other the other object (usually another SerialDate).
311: *
312: * @return A negative integer, zero, or a positive integer as this object
313: * is less than, equal to, or greater than the specified object.
314: */
315: public int compareTo(final Object other) {
316: return compare((SerialDate) other);
317: }
318:
319: /**
320: * Returns true if this SerialDate represents the same date as the
321: * specified SerialDate.
322: *
323: * @param other the date being compared to.
324: *
325: * @return <code>true</code> if this SerialDate represents the same date as
326: * the specified SerialDate.
327: */
328: public boolean isOn(final SerialDate other) {
329: return (this .serial == other.toSerial());
330: }
331:
332: /**
333: * Returns true if this SerialDate represents an earlier date compared to
334: * the specified SerialDate.
335: *
336: * @param other the date being compared to.
337: *
338: * @return <code>true</code> if this SerialDate represents an earlier date
339: * compared to the specified SerialDate.
340: */
341: public boolean isBefore(final SerialDate other) {
342: return (this .serial < other.toSerial());
343: }
344:
345: /**
346: * Returns true if this SerialDate represents the same date as the
347: * specified SerialDate.
348: *
349: * @param other the date being compared to.
350: *
351: * @return <code>true</code> if this SerialDate represents the same date
352: * as the specified SerialDate.
353: */
354: public boolean isOnOrBefore(final SerialDate other) {
355: return (this .serial <= other.toSerial());
356: }
357:
358: /**
359: * Returns true if this SerialDate represents the same date as the
360: * specified SerialDate.
361: *
362: * @param other the date being compared to.
363: *
364: * @return <code>true</code> if this SerialDate represents the same date
365: * as the specified SerialDate.
366: */
367: public boolean isAfter(final SerialDate other) {
368: return (this .serial > other.toSerial());
369: }
370:
371: /**
372: * Returns true if this SerialDate represents the same date as the
373: * specified SerialDate.
374: *
375: * @param other the date being compared to.
376: *
377: * @return <code>true</code> if this SerialDate represents the same date as
378: * the specified SerialDate.
379: */
380: public boolean isOnOrAfter(final SerialDate other) {
381: return (this .serial >= other.toSerial());
382: }
383:
384: /**
385: * Returns <code>true</code> if this {@link SerialDate} is within the
386: * specified range (INCLUSIVE). The date order of d1 and d2 is not
387: * important.
388: *
389: * @param d1 a boundary date for the range.
390: * @param d2 the other boundary date for the range.
391: *
392: * @return A boolean.
393: */
394: public boolean isInRange(final SerialDate d1, final SerialDate d2) {
395: return isInRange(d1, d2, SerialDate.INCLUDE_BOTH);
396: }
397:
398: /**
399: * Returns true if this SerialDate is within the specified range (caller
400: * specifies whether or not the end-points are included). The order of d1
401: * and d2 is not important.
402: *
403: * @param d1 one boundary date for the range.
404: * @param d2 a second boundary date for the range.
405: * @param include a code that controls whether or not the start and end
406: * dates are included in the range.
407: *
408: * @return <code>true</code> if this SerialDate is within the specified
409: * range.
410: */
411: public boolean isInRange(final SerialDate d1, final SerialDate d2,
412: final int include) {
413: final int s1 = d1.toSerial();
414: final int s2 = d2.toSerial();
415: final int start = Math.min(s1, s2);
416: final int end = Math.max(s1, s2);
417:
418: final int s = toSerial();
419: if (include == SerialDate.INCLUDE_BOTH) {
420: return (s >= start && s <= end);
421: } else if (include == SerialDate.INCLUDE_FIRST) {
422: return (s >= start && s < end);
423: } else if (include == SerialDate.INCLUDE_SECOND) {
424: return (s > start && s <= end);
425: } else {
426: return (s > start && s < end);
427: }
428: }
429:
430: /**
431: * Calculate the serial number from the day, month and year.
432: * <P>
433: * 1-Jan-1900 = 2.
434: *
435: * @param d the day.
436: * @param m the month.
437: * @param y the year.
438: *
439: * @return the serial number from the day, month and year.
440: */
441: private int calcSerial(final int d, final int m, final int y) {
442: final int yy = ((y - 1900) * 365)
443: + SerialDate.leapYearCount(y - 1);
444: int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m];
445: if (m > MonthConstants.FEBRUARY) {
446: if (SerialDate.isLeapYear(y)) {
447: mm = mm + 1;
448: }
449: }
450: final int dd = d;
451: return yy + mm + dd + 1;
452: }
453:
454: }
|