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