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