    001    /* ===========================================================
    002     * JFreeChart : a free chart 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/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     * Quarter.java
    029     * ------------
    030     * (C) Copyright 2001-2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: Quarter.java,v 1.6.2.2 2005/12/10 20:34:21 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 11-Oct-2001 : Version 1 (DG);
    040     * 18-Dec-2001 : Changed order of parameters in constructor (DG);
    041     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
    042     * 29-Jan-2002 : Added a new method parseQuarter(String) (DG);
    043     * 14-Feb-2002 : Fixed bug in Quarter(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     * 24-Jun-2002 : Removed main method (just test code) (DG);
    048     * 10-Sep-2002 : Added getSerialIndex() method (DG);
    049     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    050     * 10-Jan-2003 : Changed base class and method names (DG);
    051     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented
    052     *               Serializable (DG);
    053     * 21-Oct-2003 : Added hashCode() method (DG);
    054     * 10-Dec-2005 : Fixed argument checking bug (1377239) in constructor (DG);
    055     *
    056     */
    057
    058    package org.jfree.data.time;
    059
    060    import java.io.Serializable;
    061    import java.util.Calendar;
    062    import java.util.Date;
    063    import java.util.TimeZone;
    064
    065    import org.jfree.date.MonthConstants;
    066    import org.jfree.date.SerialDate;
    067
    068    /**
    069     * Defines a quarter (in a given year).  The range supported is Q1 1900 to
    070     * Q4 9999.  This class is immutable, which is a requirement for all
    071     * {@link RegularTimePeriod} subclasses.
    072     */
    073    public class Quarter extends RegularTimePeriod implements Serializable {
    074
    075        /** For serialization. */
    076        private static final long serialVersionUID = 3810061714380888671L;
    077
    078        /** Constant for quarter 1. */
    079        public static final int FIRST_QUARTER = 1;
    080
    081        /** Constant for quarter 4. */
    082        public static final int LAST_QUARTER = 4;
    083
    084        /** The first month in each quarter. */
    085        public static final int[] FIRST_MONTH_IN_QUARTER = {
    086            0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY,
    087            MonthConstants.OCTOBER
    088        };
    089
    090        /** The last month in each quarter. */
    091        public static final int[] LAST_MONTH_IN_QUARTER = {
    092            0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER,
    093            MonthConstants.DECEMBER
    094        };
    095
    096        /** The year in which the quarter falls. */
    097        private Year year;
    098
    099        /** The quarter (1-4). */
    100        private int quarter;
    101
    102        /**
    103         * Constructs a new Quarter, based on the current system date/time.
    104         */
    105        public Quarter() {
    106            this(new Date());
    107        }
    108
    109        /**
    110         * Constructs a new quarter.
    111         *
    112         * @param year  the year (1900 to 9999).
    113         * @param quarter  the quarter (1 to 4).
    114         */
    115        public Quarter(int quarter, int year) {
    116            this(quarter, new Year(year));
    117        }
    118
    119        /**
    120         * Constructs a new quarter.
    121         *
    122         * @param quarter  the quarter (1 to 4).
    123         * @param year  the year (1900 to 9999).
    124         */
    125        public Quarter(int quarter, Year year) {
    126            if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) {
    127                throw new IllegalArgumentException("Quarter outside valid range.");
    128            }
    129            this.year = year;
    130            this.quarter = quarter;
    131        }
    132
    133        /**
    134         * Constructs a new Quarter, based on a date/time and the default time zone.
    135         *
    136         * @param time  the date/time.
    137         */
    138        public Quarter(Date time) {
    139            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
    140        }
    141
    142        /**
    143         * Constructs a Quarter, based on a date/time and time zone.
    144         *
    145         * @param time  the date/time.
    146         * @param zone  the zone.
    147         */
    148        public Quarter(Date time, TimeZone zone) {
    149
    150            Calendar calendar = Calendar.getInstance(zone);
    151            calendar.setTime(time);
    152            int month = calendar.get(Calendar.MONTH) + 1;
    153            this.quarter = SerialDate.monthCodeToQuarter(month);
    154            this.year = new Year(calendar.get(Calendar.YEAR));
    155
    156        }
    157
    158        /**
    159         * Returns the quarter.
    160         *
    161         * @return The quarter.
    162         */
    163        public int getQuarter() {
    164            return this.quarter;
    165        }
    166
    167        /**
    168         * Returns the year.
    169         *
    170         * @return The year.
    171         */
    172        public Year getYear() {
    173            return this.year;
    174        }
    175
    176        /**
    177         * Returns the quarter preceding this one.
    178         *
    179         * @return The quarter preceding this one (or null if this is Q1 1900).
    180         */
    181        public RegularTimePeriod previous() {
    182
    183            Quarter result;
    184            if (this.quarter > FIRST_QUARTER) {
    185                result = new Quarter(this.quarter - 1, this.year);
    186            }
    187            else {
    188                Year prevYear = (Year) this.year.previous();
    189                if (prevYear != null) {
    190                    result = new Quarter(LAST_QUARTER, prevYear);
    191                }
    192                else {
    193                    result = null;
    194                }
    195            }
    196            return result;
    197
    198        }
    199
    200        /**
    201         * Returns the quarter following this one.
    202         *
    203         * @return The quarter following this one (or null if this is Q4 9999).
    204         */
    205        public RegularTimePeriod next() {
    206
    207            Quarter result;
    208            if (this.quarter < LAST_QUARTER) {
    209                result = new Quarter(this.quarter + 1, this.year);
    210            }
    211            else {
    212                Year nextYear = (Year) this.year.next();
    213                if (nextYear != null) {
    214                    result = new Quarter(FIRST_QUARTER, nextYear);
    215                }
    216                else {
    217                    result = null;
    218                }
    219            }
    220            return result;
    221
    222        }
    223
    224        /**
    225         * Returns a serial index number for the quarter.
    226         *
    227         * @return The serial index number.
    228         */
    229        public long getSerialIndex() {
    230            return this.year.getYear() * 4L + this.quarter;
    231        }
    232
    233        /**
    234         * Tests the equality of this Quarter object to an arbitrary object.
    235         * Returns true if the target is a Quarter instance representing the same
    236         * quarter as this object.  In all other cases, returns false.
    237         *
    238         * @param obj  the object.
    239         *
    240         * @return <code>true</code> if quarter and year of this and the object are
    241         *         the same.
    242         */
    243        public boolean equals(Object obj) {
    244
    245            if (obj != null) {
    246                if (obj instanceof Quarter) {
    247                    Quarter target = (Quarter) obj;
    248                    return (
    249                        (this.quarter == target.getQuarter())
    250                        && (this.year.equals(target.getYear()))
    251                    );
    252                }
    253                else {
    254                    return false;
    255                }
    256            }
    257            else {
    258                return false;
    259            }
    260
    261        }
    262
    263        /**
    264         * Returns a hash code for this object instance.  The approach described by
    265         * Joshua Bloch in "Effective Java" has been used here:
    266         * <p>
    267         * <code>http://developer.java.sun.com/developer/Books/effectivejava
    268         * /Chapter3.pdf</code>
    269         *
    270         * @return A hash code.
    271         */
    272        public int hashCode() {
    273            int result = 17;
    274            result = 37 * result + this.quarter;
    275            result = 37 * result + this.year.hashCode();
    276            return result;
    277        }
    278
    279        /**
    280         * Returns an integer indicating the order of this Quarter object relative
    281         * to the specified object:
    282         *
    283         * negative == before, zero == same, positive == after.
    284         *
    285         * @param o1  the object to compare
    286         *
    287         * @return negative == before, zero == same, positive == after.
    288         */
    289        public int compareTo(Object o1) {
    290
    291            int result;
    292
    293            // CASE 1 : Comparing to another Quarter object
    294            // --------------------------------------------
    295            if (o1 instanceof Quarter) {
    296                Quarter q = (Quarter) o1;
    297                result = this.year.getYear() - q.getYear().getYear();
    298                if (result == 0) {
    299                    result = this.quarter - q.getQuarter();
    300                }
    301            }
    302
    303            // CASE 2 : Comparing to another TimePeriod object
    304            // -----------------------------------------------
    305            else if (o1 instanceof RegularTimePeriod) {
    306                // more difficult case - evaluate later...
    307                result = 0;
    308            }
    309
    310            // CASE 3 : Comparing to a non-TimePeriod object
    311            // ---------------------------------------------
    312            else {
    313                // consider time periods to be ordered after general objects
    314                result = 1;
    315            }
    316
    317            return result;
    318
    319        }
    320
    321        /**
    322         * Returns a string representing the quarter (e.g. "Q1/2002").
    323         *
    324         * @return A string representing the quarter.
    325         */
    326        public String toString() {
    327            return "Q" + this.quarter + "/" + this.year;
    328        }
    329
    330        /**
    331         * Returns the first millisecond in the Quarter, evaluated using the
    332         * supplied calendar (which determines the time zone).
    333         *
    334         * @param calendar  the calendar.
    335         *
    336         * @return The first millisecond in the Quarter.
    337         */
    338        public long getFirstMillisecond(Calendar calendar) {
    339
    340            int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter];
    341            Day first = new Day(1, month, this.year.getYear());
    342            return first.getFirstMillisecond(calendar);
    343
    344        }
    345
    346        /**
    347         * Returns the last millisecond of the Quarter, evaluated using the
    348         * supplied calendar (which determines the time zone).
    349         *
    350         * @param calendar  the calendar.
    351         *
    352         * @return The last millisecond of the Quarter.
    353         */
    354        public long getLastMillisecond(Calendar calendar) {
    355
    356            int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter];
    357            int eom = SerialDate.lastDayOfMonth(month, this.year.getYear());
    358            Day last = new Day(eom, month, this.year.getYear());
    359            return last.getLastMillisecond(calendar);
    360
    361        }
    362
    363        /**
    364         * Parses the string argument as a quarter.
    365         * <P>
    366         * This method should accept the following formats: "YYYY-QN" and "QN-YYYY",
    367         * where the "-" can be a space, a forward-slash (/), comma or a dash (-).
    368         * @param s A string representing the quarter.
    369         *
    370         * @return The quarter.
    371         */
    372        public static Quarter parseQuarter(String s) {
    373
    374            // find the Q and the integer following it (remove both from the
    375            // string)...
    376            int i = s.indexOf("Q");
    377            if (i == -1) {
    378                throw new TimePeriodFormatException("Missing Q.");
    379            }
    380
    381            if (i == s.length() - 1) {
    382                throw new TimePeriodFormatException("Q found at end of string.");
    383            }
    384
    385            String qstr = s.substring(i + 1, i + 2);
    386            int quarter = Integer.parseInt(qstr);
    387            String remaining = s.substring(0, i) + s.substring(i + 2, s.length());
    388
    389            // replace any / , or - with a space
    390            remaining = remaining.replace('/', ' ');
    391            remaining = remaining.replace(',', ' ');
    392            remaining = remaining.replace('-', ' ');
    393
    394            // parse the string...
    395            Year year = Year.parseYear(remaining.trim());
    396            Quarter result = new Quarter(quarter, year);
    397            return result;
    398
    399        }
    400
    401    }