    001    /* ===========================================================
    002     * JFreeChart : a free chart 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/jfreechart/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     * Year.java
    029     * ---------
    030     * (C) Copyright 2001-2006, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: Year.java,v 1.9.2.4 2006/10/06 14:00:16 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 11-Oct-2001 : Version 1 (DG);
    040     * 14-Nov-2001 : Override for toString() method (DG);
    041     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
    042     * 29-Jan-2002 : Worked on parseYear() method (DG);
    043     * 14-Feb-2002 : Fixed bug in Year(Date) constructor (DG);
    044     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to
    045     *               evaluate with reference to a particular time zone (DG);
    046     * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
    047     * 10-Sep-2002 : Added getSerialIndex() method (DG);
    048     * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    049     * 10-Jan-2003 : Changed base class and method names (DG);
    050     * 05-Mar-2003 : Fixed bug in getFirstMillisecond() picked up in JUnit
    051     *               tests (DG);
    052     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented
    053     *               Serializable (DG);
    054     * 21-Oct-2003 : Added hashCode() method (DG);
    055     * ------------- JFREECHART 1.0.x ---------------------------------------------
    056     * 05-Oct-2006 : Updated API docs (DG);
    057     * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
    058     *
    059     */
    060
    061    package org.jfree.data.time;
    062
    063    import java.io.Serializable;
    064    import java.util.Calendar;
    065    import java.util.Date;
    066    import java.util.TimeZone;
    067
    068    import org.jfree.date.SerialDate;
    069
    070    /**
    071     * Represents a year in the range 1900 to 9999.  This class is immutable, which
    072     * is a requirement for all {@link RegularTimePeriod} subclasses.
    073     */
    074    public class Year extends RegularTimePeriod implements Serializable {
    075
    076        /** For serialization. */
    077        private static final long serialVersionUID = -7659990929736074836L;
    078
    079        /** The year. */
    080        private short year;
    081
    082        /** The first millisecond. */
    083        private long firstMillisecond;
    084
    085        /** The last millisecond. */
    086        private long lastMillisecond;
    087
    088        /**
    089         * Creates a new <code>Year</code>, based on the current system date/time.
    090         */
    091        public Year() {
    092            this(new Date());
    093        }
    094
    095        /**
    096         * Creates a time period representing a single year.
    097         *
    098         * @param year  the year.
    099         */
    100        public Year(int year) {
    101            if ((year < SerialDate.MINIMUM_YEAR_SUPPORTED)
    102                || (year > SerialDate.MAXIMUM_YEAR_SUPPORTED)) {
    103
    104                throw new IllegalArgumentException(
    105                    "Year constructor: year (" + year + ") outside valid range.");
    106            }
    107            this.year = (short) year;
    108            peg(Calendar.getInstance());
    109        }
    110
    111        /**
    112         * Creates a new <code>Year</code>, based on a particular instant in time,
    113         * using the default time zone.
    114         *
    115         * @param time  the time (<code>null</code> not permitted).
    116         */
    117        public Year(Date time) {
    118            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
    119        }
    120
    121        /**
    122         * Constructs a year, based on a particular instant in time and a time zone.
    123         *
    124         * @param time  the time.
    125         * @param zone  the time zone.
    126         */
    127        public Year(Date time, TimeZone zone) {
    128            Calendar calendar = Calendar.getInstance(zone);
    129            calendar.setTime(time);
    130            this.year = (short) calendar.get(Calendar.YEAR);
    131            peg(calendar);
    132        }
    133
    134        /**
    135         * Returns the year.
    136         *
    137         * @return The year.
    138         */
    139        public int getYear() {
    140            return this.year;
    141        }
    142
    143        /**
    144         * Returns the first millisecond of the year.  This will be determined
    145         * relative to the time zone specified in the constructor, or in the
    146         * calendar instance passed in the most recent call to the
    147         * {@link #peg(Calendar)} method.
    148         *
    149         * @return The first millisecond of the year.
    150         *
    151         * @see #getLastMillisecond()
    152         */
    153        public long getFirstMillisecond() {
    154            return this.firstMillisecond;
    155        }
    156
    157        /**
    158         * Returns the last millisecond of the year.  This will be
    159         * determined relative to the time zone specified in the constructor, or
    160         * in the calendar instance passed in the most recent call to the
    161         * {@link #peg(Calendar)} method.
    162         *
    163         * @return The last millisecond of the year.
    164         *
    165         * @see #getFirstMillisecond()
    166         */
    167        public long getLastMillisecond() {
    168            return this.lastMillisecond;
    169        }
    170
    171        /**
    172         * Recalculates the start date/time and end date/time for this time period
    173         * relative to the supplied calendar (which incorporates a time zone).
    174         *
    175         * @param calendar  the calendar (<code>null</code> not permitted).
    176         *
    177         * @since 1.0.3
    178         */
    179        public void peg(Calendar calendar) {
    180            this.firstMillisecond = getFirstMillisecond(calendar);
    181            this.lastMillisecond = getLastMillisecond(calendar);
    182        }
    183
    184        /**
    185         * Returns the year preceding this one.
    186         *
    187         * @return The year preceding this one (or <code>null</code> if the
    188         *         current year is 1900).
    189         */
    190        public RegularTimePeriod previous() {
    191            if (this.year > SerialDate.MINIMUM_YEAR_SUPPORTED) {
    192                return new Year(this.year - 1);
    193            }
    194            else {
    195                return null;
    196            }
    197        }
    198
    199        /**
    200         * Returns the year following this one.
    201         *
    202         * @return The year following this one (or <code>null</code> if the current
    203         *         year is 9999).
    204         */
    205        public RegularTimePeriod next() {
    206            if (this.year < SerialDate.MAXIMUM_YEAR_SUPPORTED) {
    207                return new Year(this.year + 1);
    208            }
    209            else {
    210                return null;
    211            }
    212        }
    213
    214        /**
    215         * Returns a serial index number for the year.
    216         * <P>
    217         * The implementation simply returns the year number (e.g. 2002).
    218         *
    219         * @return The serial index number.
    220         */
    221        public long getSerialIndex() {
    222            return this.year;
    223        }
    224
    225        /**
    226         * Returns the first millisecond of the year, evaluated using the supplied
    227         * calendar (which determines the time zone).
    228         *
    229         * @param calendar  the calendar (<code>null</code> not permitted).
    230         *
    231         * @return The first millisecond of the year.
    232         *
    233         * @throws NullPointerException if <code>calendar</code> is
    234         *     <code>null</code>.
    235         */
    236        public long getFirstMillisecond(Calendar calendar) {
    237            calendar.set(this.year, Calendar.JANUARY, 1, 0, 0, 0);
    238            calendar.set(Calendar.MILLISECOND, 0);
    239            // in the following line, we'd rather call calendar.getTimeInMillis()
    240            // to avoid object creation, but that isn't supported in Java 1.3.1
    241            return calendar.getTime().getTime();
    242        }
    243
    244        /**
    245         * Returns the last millisecond of the year, evaluated using the supplied
    246         * calendar (which determines the time zone).
    247         *
    248         * @param calendar  the calendar (<code>null</code> not permitted).
    249         *
    250         * @return The last millisecond of the year.
    251         *
    252         * @throws NullPointerException if <code>calendar</code> is
    253         *     <code>null</code>.
    254         */
    255        public long getLastMillisecond(Calendar calendar) {
    256            calendar.set(this.year, Calendar.DECEMBER, 31, 23, 59, 59);
    257            calendar.set(Calendar.MILLISECOND, 999);
    258            // in the following line, we'd rather call calendar.getTimeInMillis()
    259            // to avoid object creation, but that isn't supported in Java 1.3.1
    260            return calendar.getTime().getTime();
    261        }
    262
    263        /**
    264         * Tests the equality of this <code>Year</code> object to an arbitrary
    265         * object.  Returns <code>true</code> if the target is a <code>Year</code>
    266         * instance representing the same year as this object.  In all other cases,
    267         * returns <code>false</code>.
    268         *
    269         * @param object  the object (<code>null</code> permitted).
    270         *
    271         * @return <code>true</code> if the year of this and the object are the
    272         *         same.
    273         */
    274        public boolean equals(Object object) {
    275            if (object != null) {
    276                if (object instanceof Year) {
    277                    Year target = (Year) object;
    278                    return (this.year == target.getYear());
    279                }
    280                else {
    281                    return false;
    282                }
    283            }
    284            else {
    285                return false;
    286            }
    287        }
    288
    289        /**
    290         * Returns a hash code for this object instance.  The approach described by
    291         * Joshua Bloch in "Effective Java" has been used here:
    292         * <p>
    293         * <code>http://developer.java.sun.com/developer/Books/effectivejava
    294         *     /Chapter3.pdf</code>
    295         *
    296         * @return A hash code.
    297         */
    298        public int hashCode() {
    299            int result = 17;
    300            int c = this.year;
    301            result = 37 * result + c;
    302            return result;
    303        }
    304
    305        /**
    306         * Returns an integer indicating the order of this <code>Year</code> object
    307         * relative to the specified object:
    308         *
    309         * negative == before, zero == same, positive == after.
    310         *
    311         * @param o1  the object to compare.
    312         *
    313         * @return negative == before, zero == same, positive == after.
    314         */
    315        public int compareTo(Object o1) {
    316
    317            int result;
    318
    319            // CASE 1 : Comparing to another Year object
    320            // -----------------------------------------
    321            if (o1 instanceof Year) {
    322                Year y = (Year) o1;
    323                result = this.year - y.getYear();
    324            }
    325
    326            // CASE 2 : Comparing to another TimePeriod object
    327            // -----------------------------------------------
    328            else if (o1 instanceof RegularTimePeriod) {
    329                // more difficult case - evaluate later...
    330                result = 0;
    331            }
    332
    333            // CASE 3 : Comparing to a non-TimePeriod object
    334            // ---------------------------------------------
    335            else {
    336                // consider time periods to be ordered after general objects
    337                result = 1;
    338            }
    339
    340            return result;
    341
    342        }
    343
    344        /**
    345         * Returns a string representing the year..
    346         *
    347         * @return A string representing the year.
    348         */
    349        public String toString() {
    350            return Integer.toString(this.year);
    351        }
    352
    353        /**
    354         * Parses the string argument as a year.
    355         * <P>
    356         * The string format is YYYY.
    357         *
    358         * @param s  a string representing the year.
    359         *
    360         * @return <code>null</code> if the string is not parseable, the year
    361         *         otherwise.
    362         */
    363        public static Year parseYear(String s) {
    364
    365            // parse the string...
    366            int y;
    367            try {
    368                y = Integer.parseInt(s.trim());
    369            }
    370            catch (NumberFormatException e) {
    371                throw new TimePeriodFormatException("Cannot parse string.");
    372            }
    373
    374            // create the year...
    375            try {
    376                return new Year(y);
    377            }
    378            catch (IllegalArgumentException e) {
    379                throw new TimePeriodFormatException("Year outside valid range.");
    380            }
    381        }
    382
    383    }