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