Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ------------ 28: * Quarter.java 29: * ------------ 30: * (C) Copyright 2001-2007, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * $Id: Quarter.java,v 1.6.2.5 2007/03/09 16:13:29 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 11-Oct-2001 : Version 1 (DG); 40: * 18-Dec-2001 : Changed order of parameters in constructor (DG); 41: * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG); 42: * 29-Jan-2002 : Added a new method parseQuarter(String) (DG); 43: * 14-Feb-2002 : Fixed bug in Quarter(Date) constructor (DG); 44: * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 45: * evaluate with reference to a particular time zone (DG); 46: * 19-Mar-2002 : Changed API for TimePeriod classes (DG); 47: * 24-Jun-2002 : Removed main method (just test code) (DG); 48: * 10-Sep-2002 : Added getSerialIndex() method (DG); 49: * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 50: * 10-Jan-2003 : Changed base class and method names (DG); 51: * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 52: * Serializable (DG); 53: * 21-Oct-2003 : Added hashCode() method (DG); 54: * 10-Dec-2005 : Fixed argument checking bug (1377239) in constructor (DG); 55: * ------------- JFREECHART 1.0.x --------------------------------------------- 56: * 05-Oct-2006 : Updated API docs (DG); 57: * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG); 58: * 59: */ 60: 61: package org.jfree.data.time; 62: 63: import java.io.Serializable; 64: import java.util.Calendar; 65: import java.util.Date; 66: import java.util.TimeZone; 67: 68: import org.jfree.date.MonthConstants; 69: import org.jfree.date.SerialDate; 70: 71: /** 72: * Defines a quarter (in a given year). The range supported is Q1 1900 to 73: * Q4 9999. This class is immutable, which is a requirement for all 74: * {@link RegularTimePeriod} subclasses. 75: */ 76: public class Quarter extends RegularTimePeriod implements Serializable { 77: 78: /** For serialization. */ 79: private static final long serialVersionUID = 3810061714380888671L; 80: 81: /** Constant for quarter 1. */ 82: public static final int FIRST_QUARTER = 1; 83: 84: /** Constant for quarter 4. */ 85: public static final int LAST_QUARTER = 4; 86: 87: /** The first month in each quarter. */ 88: public static final int[] FIRST_MONTH_IN_QUARTER = { 89: 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 90: MonthConstants.OCTOBER 91: }; 92: 93: /** The last month in each quarter. */ 94: public static final int[] LAST_MONTH_IN_QUARTER = { 95: 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 96: MonthConstants.DECEMBER 97: }; 98: 99: /** The year in which the quarter falls. */ 100: private short year; 101: 102: /** The quarter (1-4). */ 103: private byte quarter; 104: 105: /** The first millisecond. */ 106: private long firstMillisecond; 107: 108: /** The last millisecond. */ 109: private long lastMillisecond; 110: 111: /** 112: * Constructs a new Quarter, based on the current system date/time. 113: */ 114: public Quarter() { 115: this(new Date()); 116: } 117: 118: /** 119: * Constructs a new quarter. 120: * 121: * @param year the year (1900 to 9999). 122: * @param quarter the quarter (1 to 4). 123: */ 124: public Quarter(int quarter, int year) { 125: if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) { 126: throw new IllegalArgumentException("Quarter outside valid range."); 127: } 128: this.year = (short) year; 129: this.quarter = (byte) quarter; 130: peg(Calendar.getInstance()); 131: } 132: 133: /** 134: * Constructs a new quarter. 135: * 136: * @param quarter the quarter (1 to 4). 137: * @param year the year (1900 to 9999). 138: */ 139: public Quarter(int quarter, Year year) { 140: if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) { 141: throw new IllegalArgumentException("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: } 253: else { 254: if (this.year > 1900) { 255: result = new Quarter(LAST_QUARTER, this.year - 1); 256: } 257: else { 258: result = null; 259: } 260: } 261: return result; 262: } 263: 264: /** 265: * Returns the quarter following this one. 266: * 267: * @return The quarter following this one (or null if this is Q4 9999). 268: */ 269: public RegularTimePeriod next() { 270: Quarter result; 271: if (this.quarter < LAST_QUARTER) { 272: result = new Quarter(this.quarter + 1, this.year); 273: } 274: else { 275: if (this.year < 9999) { 276: result = new Quarter(FIRST_QUARTER, this.year + 1); 277: } 278: else { 279: result = null; 280: } 281: } 282: return result; 283: } 284: 285: /** 286: * Returns a serial index number for the quarter. 287: * 288: * @return The serial index number. 289: */ 290: public long getSerialIndex() { 291: return this.year * 4L + this.quarter; 292: } 293: 294: /** 295: * Tests the equality of this Quarter object to an arbitrary object. 296: * Returns <code>true</code> if the target is a Quarter instance 297: * representing the same quarter as this object. In all other cases, 298: * returns <code>false</code>. 299: * 300: * @param obj the object (<code>null</code> permitted). 301: * 302: * @return <code>true</code> if quarter and year of this and the object are 303: * the same. 304: */ 305: public boolean equals(Object obj) { 306: 307: if (obj != null) { 308: if (obj instanceof Quarter) { 309: Quarter target = (Quarter) obj; 310: return (this.quarter == target.getQuarter() 311: && (this.year == target.getYearValue())); 312: } 313: else { 314: return false; 315: } 316: } 317: else { 318: return false; 319: } 320: 321: } 322: 323: /** 324: * Returns a hash code for this object instance. The approach described by 325: * Joshua Bloch in "Effective Java" has been used here: 326: * <p> 327: * <code>http://developer.java.sun.com/developer/Books/effectivejava 328: * /Chapter3.pdf</code> 329: * 330: * @return A hash code. 331: */ 332: public int hashCode() { 333: int result = 17; 334: result = 37 * result + this.quarter; 335: result = 37 * result + this.year; 336: return result; 337: } 338: 339: /** 340: * Returns an integer indicating the order of this Quarter object relative 341: * to the specified object: 342: * 343: * negative == before, zero == same, positive == after. 344: * 345: * @param o1 the object to compare 346: * 347: * @return negative == before, zero == same, positive == after. 348: */ 349: public int compareTo(Object o1) { 350: 351: int result; 352: 353: // CASE 1 : Comparing to another Quarter object 354: // -------------------------------------------- 355: if (o1 instanceof Quarter) { 356: Quarter q = (Quarter) o1; 357: result = this.year - q.getYearValue(); 358: if (result == 0) { 359: result = this.quarter - q.getQuarter(); 360: } 361: } 362: 363: // CASE 2 : Comparing to another TimePeriod object 364: // ----------------------------------------------- 365: else if (o1 instanceof RegularTimePeriod) { 366: // more difficult case - evaluate later... 367: result = 0; 368: } 369: 370: // CASE 3 : Comparing to a non-TimePeriod object 371: // --------------------------------------------- 372: else { 373: // consider time periods to be ordered after general objects 374: result = 1; 375: } 376: 377: return result; 378: 379: } 380: 381: /** 382: * Returns a string representing the quarter (e.g. "Q1/2002"). 383: * 384: * @return A string representing the quarter. 385: */ 386: public String toString() { 387: return "Q" + this.quarter + "/" + this.year; 388: } 389: 390: /** 391: * Returns the first millisecond in the Quarter, evaluated using the 392: * supplied calendar (which determines the time zone). 393: * 394: * @param calendar the calendar (<code>null</code> not permitted). 395: * 396: * @return The first millisecond in the Quarter. 397: * 398: * @throws NullPointerException if <code>calendar</code> is 399: * <code>null</code>. 400: */ 401: public long getFirstMillisecond(Calendar calendar) { 402: int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter]; 403: calendar.set(this.year, month - 1, 1, 0, 0, 0); 404: calendar.set(Calendar.MILLISECOND, 0); 405: // in the following line, we'd rather call calendar.getTimeInMillis() 406: // to avoid object creation, but that isn't supported in Java 1.3.1 407: return calendar.getTime().getTime(); 408: } 409: 410: /** 411: * Returns the last millisecond of the Quarter, evaluated using the 412: * supplied calendar (which determines the time zone). 413: * 414: * @param calendar the calendar (<code>null</code> not permitted). 415: * 416: * @return The last millisecond of the Quarter. 417: * 418: * @throws NullPointerException if <code>calendar</code> is 419: * <code>null</code>. 420: */ 421: public long getLastMillisecond(Calendar calendar) { 422: int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter]; 423: int eom = SerialDate.lastDayOfMonth(month, this.year); 424: calendar.set(this.year, month - 1, eom, 23, 59, 59); 425: calendar.set(Calendar.MILLISECOND, 999); 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: * Parses the string argument as a quarter. 433: * <P> 434: * This method should accept the following formats: "YYYY-QN" and "QN-YYYY", 435: * where the "-" can be a space, a forward-slash (/), comma or a dash (-). 436: * @param s A string representing the quarter. 437: * 438: * @return The quarter. 439: */ 440: public static Quarter parseQuarter(String s) { 441: 442: // find the Q and the integer following it (remove both from the 443: // string)... 444: int i = s.indexOf("Q"); 445: if (i == -1) { 446: throw new TimePeriodFormatException("Missing Q."); 447: } 448: 449: if (i == s.length() - 1) { 450: throw new TimePeriodFormatException("Q found at end of string."); 451: } 452: 453: String qstr = s.substring(i + 1, i + 2); 454: int quarter = Integer.parseInt(qstr); 455: String remaining = s.substring(0, i) + s.substring(i + 2, s.length()); 456: 457: // replace any / , or - with a space 458: remaining = remaining.replace('/', ' '); 459: remaining = remaining.replace(',', ' '); 460: remaining = remaining.replace('-', ' '); 461: 462: // parse the string... 463: Year year = Year.parseYear(remaining.trim()); 464: Quarter result = new Quarter(quarter, year); 465: return result; 466: 467: } 468: 469: }