001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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-2008, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 11-Oct-2001 : Version 1 (DG);
038     * 18-Dec-2001 : Changed order of parameters in constructor (DG);
039     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
040     * 29-Jan-2002 : Added a new method parseQuarter(String) (DG);
041     * 14-Feb-2002 : Fixed bug in Quarter(Date) constructor (DG);
042     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to
043     *               evaluate with reference to a particular time zone (DG);
044     * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
045     * 24-Jun-2002 : Removed main method (just test code) (DG);
046     * 10-Sep-2002 : Added getSerialIndex() method (DG);
047     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
048     * 10-Jan-2003 : Changed base class and method names (DG);
049     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented
050     *               Serializable (DG);
051     * 21-Oct-2003 : Added hashCode() method (DG);
052     * 10-Dec-2005 : Fixed argument checking bug (1377239) in constructor (DG);
053     * ------------- JFREECHART 1.0.x ---------------------------------------------
054     * 05-Oct-2006 : Updated API docs (DG);
055     * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
056     * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG);
057     * 25-Nov-2008 : Added new constructor with Locale (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.Locale;
067    import java.util.TimeZone;
068    
069    import org.jfree.date.MonthConstants;
070    import org.jfree.date.SerialDate;
071    
072    /**
073     * Defines a quarter (in a given year).  The range supported is Q1 1900 to
074     * Q4 9999.  This class is immutable, which is a requirement for all
075     * {@link RegularTimePeriod} subclasses.
076     */
077    public class Quarter extends RegularTimePeriod implements Serializable {
078    
079        /** For serialization. */
080        private static final long serialVersionUID = 3810061714380888671L;
081    
082        /** Constant for quarter 1. */
083        public static final int FIRST_QUARTER = 1;
084    
085        /** Constant for quarter 4. */
086        public static final int LAST_QUARTER = 4;
087    
088        /** The first month in each quarter. */
089        public static final int[] FIRST_MONTH_IN_QUARTER = {
090            0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY,
091            MonthConstants.OCTOBER
092        };
093    
094        /** The last month in each quarter. */
095        public static final int[] LAST_MONTH_IN_QUARTER = {
096            0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER,
097            MonthConstants.DECEMBER
098        };
099    
100        /** The year in which the quarter falls. */
101        private short year;
102    
103        /** The quarter (1-4). */
104        private byte quarter;
105    
106        /** The first millisecond. */
107        private long firstMillisecond;
108    
109        /** The last millisecond. */
110        private long lastMillisecond;
111    
112        /**
113         * Constructs a new Quarter, based on the current system date/time.
114         */
115        public Quarter() {
116            this(new Date());
117        }
118    
119        /**
120         * Constructs a new quarter.
121         *
122         * @param year  the year (1900 to 9999).
123         * @param quarter  the quarter (1 to 4).
124         */
125        public Quarter(int quarter, int year) {
126            if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) {
127                throw new IllegalArgumentException("Quarter outside valid range.");
128            }
129            this.year = (short) year;
130            this.quarter = (byte) quarter;
131            peg(Calendar.getInstance());
132        }
133    
134        /**
135         * Constructs a new quarter.
136         *
137         * @param quarter  the quarter (1 to 4).
138         * @param year  the year (1900 to 9999).
139         */
140        public Quarter(int quarter, Year year) {
141            if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) {
142                throw new IllegalArgumentException("Quarter outside valid range.");
143            }
144            this.year = (short) year.getYear();
145            this.quarter = (byte) quarter;
146            peg(Calendar.getInstance());
147        }
148    
149        /**
150         * Constructs a new instance, based on a date/time and the default time
151         * zone.
152         *
153         * @param time  the date/time (<code>null</code> not permitted).
154         *
155         * @see #Quarter(Date, TimeZone)
156         */
157        public Quarter(Date time) {
158            this(time, TimeZone.getDefault());
159        }
160    
161        /**
162         * Constructs a Quarter, based on a date/time and time zone.
163         *
164         * @param time  the date/time.
165         * @param zone  the zone (<code>null</code> not permitted).
166         *
167         * @deprecated Since 1.0.12, use {@link #Quarter(Date, TimeZone, Locale)}
168         *     instead.
169         */
170        public Quarter(Date time, TimeZone zone) {
171            this(time, zone, Locale.getDefault());
172        }
173    
174        /**
175         * Creates a new <code>Quarter</code> instance, using the specified
176         * zone and locale.
177         *
178         * @param time  the current time.
179         * @param zone  the time zone.
180         * @param locale  the locale.
181         *
182         * @since 1.0.12
183         */
184        public Quarter(Date time, TimeZone zone, Locale locale) {
185            Calendar calendar = Calendar.getInstance(zone, locale);
186            calendar.setTime(time);
187            int month = calendar.get(Calendar.MONTH) + 1;
188            this.quarter = (byte) SerialDate.monthCodeToQuarter(month);
189            this.year = (short) calendar.get(Calendar.YEAR);
190            peg(calendar);
191        }
192    
193        /**
194         * Returns the quarter.
195         *
196         * @return The quarter.
197         */
198        public int getQuarter() {
199            return this.quarter;
200        }
201    
202        /**
203         * Returns the year.
204         *
205         * @return The year.
206         */
207        public Year getYear() {
208            return new Year(this.year);
209        }
210    
211        /**
212         * Returns the year.
213         *
214         * @return The year.
215         *
216         * @since 1.0.3
217         */
218        public int getYearValue() {
219            return this.year;
220        }
221    
222        /**
223         * Returns the first millisecond of the quarter.  This will be determined
224         * relative to the time zone specified in the constructor, or in the
225         * calendar instance passed in the most recent call to the
226         * {@link #peg(Calendar)} method.
227         *
228         * @return The first millisecond of the quarter.
229         *
230         * @see #getLastMillisecond()
231         */
232        public long getFirstMillisecond() {
233            return this.firstMillisecond;
234        }
235    
236        /**
237         * Returns the last millisecond of the quarter.  This will be
238         * determined relative to the time zone specified in the constructor, or
239         * in the calendar instance passed in the most recent call to the
240         * {@link #peg(Calendar)} method.
241         *
242         * @return The last millisecond of the quarter.
243         *
244         * @see #getFirstMillisecond()
245         */
246        public long getLastMillisecond() {
247            return this.lastMillisecond;
248        }
249    
250        /**
251         * Recalculates the start date/time and end date/time for this time period
252         * relative to the supplied calendar (which incorporates a time zone).
253         *
254         * @param calendar  the calendar (<code>null</code> not permitted).
255         *
256         * @since 1.0.3
257         */
258        public void peg(Calendar calendar) {
259            this.firstMillisecond = getFirstMillisecond(calendar);
260            this.lastMillisecond = getLastMillisecond(calendar);
261        }
262    
263        /**
264         * Returns the quarter preceding this one.
265         *
266         * @return The quarter preceding this one (or <code>null</code> if this is
267         *     Q1 1900).
268         */
269        public RegularTimePeriod previous() {
270            Quarter result;
271            if (this.quarter > FIRST_QUARTER) {
272                result = new Quarter(this.quarter - 1, this.year);
273            }
274            else {
275                if (this.year > 1900) {
276                    result = new Quarter(LAST_QUARTER, this.year - 1);
277                }
278                else {
279                    result = null;
280                }
281            }
282            return result;
283        }
284    
285        /**
286         * Returns the quarter following this one.
287         *
288         * @return The quarter following this one (or null if this is Q4 9999).
289         */
290        public RegularTimePeriod next() {
291            Quarter result;
292            if (this.quarter < LAST_QUARTER) {
293                result = new Quarter(this.quarter + 1, this.year);
294            }
295            else {
296                if (this.year < 9999) {
297                    result = new Quarter(FIRST_QUARTER, this.year + 1);
298                }
299                else {
300                    result = null;
301                }
302            }
303            return result;
304        }
305    
306        /**
307         * Returns a serial index number for the quarter.
308         *
309         * @return The serial index number.
310         */
311        public long getSerialIndex() {
312            return this.year * 4L + this.quarter;
313        }
314    
315        /**
316         * Tests the equality of this Quarter object to an arbitrary object.
317         * Returns <code>true</code> if the target is a Quarter instance
318         * representing the same quarter as this object.  In all other cases,
319         * returns <code>false</code>.
320         *
321         * @param obj  the object (<code>null</code> permitted).
322         *
323         * @return <code>true</code> if quarter and year of this and the object are
324         *         the same.
325         */
326        public boolean equals(Object obj) {
327    
328            if (obj != null) {
329                if (obj instanceof Quarter) {
330                    Quarter target = (Quarter) obj;
331                    return (this.quarter == target.getQuarter()
332                            && (this.year == target.getYearValue()));
333                }
334                else {
335                    return false;
336                }
337            }
338            else {
339                return false;
340            }
341    
342        }
343    
344        /**
345         * Returns a hash code for this object instance.  The approach described by
346         * Joshua Bloch in "Effective Java" has been used here:
347         * <p>
348         * <code>http://developer.java.sun.com/developer/Books/effectivejava
349         * /Chapter3.pdf</code>
350         *
351         * @return A hash code.
352         */
353        public int hashCode() {
354            int result = 17;
355            result = 37 * result + this.quarter;
356            result = 37 * result + this.year;
357            return result;
358        }
359    
360        /**
361         * Returns an integer indicating the order of this Quarter object relative
362         * to the specified object:
363         *
364         * negative == before, zero == same, positive == after.
365         *
366         * @param o1  the object to compare
367         *
368         * @return negative == before, zero == same, positive == after.
369         */
370        public int compareTo(Object o1) {
371    
372            int result;
373    
374            // CASE 1 : Comparing to another Quarter object
375            // --------------------------------------------
376            if (o1 instanceof Quarter) {
377                Quarter q = (Quarter) o1;
378                result = this.year - q.getYearValue();
379                if (result == 0) {
380                    result = this.quarter - q.getQuarter();
381                }
382            }
383    
384            // CASE 2 : Comparing to another TimePeriod object
385            // -----------------------------------------------
386            else if (o1 instanceof RegularTimePeriod) {
387                // more difficult case - evaluate later...
388                result = 0;
389            }
390    
391            // CASE 3 : Comparing to a non-TimePeriod object
392            // ---------------------------------------------
393            else {
394                // consider time periods to be ordered after general objects
395                result = 1;
396            }
397    
398            return result;
399    
400        }
401    
402        /**
403         * Returns a string representing the quarter (e.g. "Q1/2002").
404         *
405         * @return A string representing the quarter.
406         */
407        public String toString() {
408            return "Q" + this.quarter + "/" + this.year;
409        }
410    
411        /**
412         * Returns the first millisecond in the Quarter, evaluated using the
413         * supplied calendar (which determines the time zone).
414         *
415         * @param calendar  the calendar (<code>null</code> not permitted).
416         *
417         * @return The first millisecond in the Quarter.
418         *
419         * @throws NullPointerException if <code>calendar</code> is
420         *     <code>null</code>.
421         */
422        public long getFirstMillisecond(Calendar calendar) {
423            int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter];
424            calendar.set(this.year, month - 1, 1, 0, 0, 0);
425            calendar.set(Calendar.MILLISECOND, 0);
426            // in the following line, we'd rather call calendar.getTimeInMillis()
427            // to avoid object creation, but that isn't supported in Java 1.3.1
428            return calendar.getTime().getTime();
429        }
430    
431        /**
432         * Returns the last millisecond of the Quarter, evaluated using the
433         * supplied calendar (which determines the time zone).
434         *
435         * @param calendar  the calendar (<code>null</code> not permitted).
436         *
437         * @return The last millisecond of the Quarter.
438         *
439         * @throws NullPointerException if <code>calendar</code> is
440         *     <code>null</code>.
441         */
442        public long getLastMillisecond(Calendar calendar) {
443            int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter];
444            int eom = SerialDate.lastDayOfMonth(month, this.year);
445            calendar.set(this.year, month - 1, eom, 23, 59, 59);
446            calendar.set(Calendar.MILLISECOND, 999);
447            // in the following line, we'd rather call calendar.getTimeInMillis()
448            // to avoid object creation, but that isn't supported in Java 1.3.1
449            return calendar.getTime().getTime();
450        }
451    
452        /**
453         * Parses the string argument as a quarter.
454         * <P>
455         * This method should accept the following formats: "YYYY-QN" and "QN-YYYY",
456         * where the "-" can be a space, a forward-slash (/), comma or a dash (-).
457         * @param s A string representing the quarter.
458         *
459         * @return The quarter.
460         */
461        public static Quarter parseQuarter(String s) {
462    
463            // find the Q and the integer following it (remove both from the
464            // string)...
465            int i = s.indexOf("Q");
466            if (i == -1) {
467                throw new TimePeriodFormatException("Missing Q.");
468            }
469    
470            if (i == s.length() - 1) {
471                throw new TimePeriodFormatException("Q found at end of string.");
472            }
473    
474            String qstr = s.substring(i + 1, i + 2);
475            int quarter = Integer.parseInt(qstr);
476            String remaining = s.substring(0, i) + s.substring(i + 2, s.length());
477    
478            // replace any / , or - with a space
479            remaining = remaining.replace('/', ' ');
480            remaining = remaining.replace(',', ' ');
481            remaining = remaining.replace('-', ' ');
482    
483            // parse the string...
484            Year year = Year.parseYear(remaining.trim());
485            Quarter result = new Quarter(quarter, year);
486            return result;
487    
488        }
489    
490    }