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