    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    }