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: * Changes 36: * ------- 37: * 11-Oct-2001 : Version 1 (DG); 38: * 18-Dec-2001 : Changed order of parameters in constructor (DG); 39: * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG); 40: * 29-Jan-2002 : Added a new method parseQuarter(String) (DG); 41: * 14-Feb-2002 : Fixed bug in Quarter(Date) constructor (DG); 42: * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 43: * evaluate with reference to a particular time zone (DG); 44: * 19-Mar-2002 : Changed API for TimePeriod classes (DG); 45: * 24-Jun-2002 : Removed main method (just test code) (DG); 46: * 10-Sep-2002 : Added getSerialIndex() method (DG); 47: * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 48: * 10-Jan-2003 : Changed base class and method names (DG); 49: * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 50: * Serializable (DG); 51: * 21-Oct-2003 : Added hashCode() method (DG); 52: * 10-Dec-2005 : Fixed argument checking bug (1377239) in constructor (DG); 53: * ------------- JFREECHART 1.0.x --------------------------------------------- 54: * 05-Oct-2006 : Updated API docs (DG); 55: * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG); 56: * 57: */ 58: 59: package org.jfree.data.time; 60: 61: import java.io.Serializable; 62: import java.util.Calendar; 63: import java.util.Date; 64: import java.util.TimeZone; 65: 66: import org.jfree.date.MonthConstants; 67: import org.jfree.date.SerialDate; 68: 69: /** 70: * Defines a quarter (in a given year). The range supported is Q1 1900 to 71: * Q4 9999. This class is immutable, which is a requirement for all 72: * {@link RegularTimePeriod} subclasses. 73: */ 74: public class Quarter extends RegularTimePeriod implements Serializable { 75: 76: /** For serialization. */ 77: private static final long serialVersionUID = 3810061714380888671L; 78: 79: /** Constant for quarter 1. */ 80: public static final int FIRST_QUARTER = 1; 81: 82: /** Constant for quarter 4. */ 83: public static final int LAST_QUARTER = 4; 84: 85: /** The first month in each quarter. */ 86: public static final int[] FIRST_MONTH_IN_QUARTER = { 87: 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 88: MonthConstants.OCTOBER 89: }; 90: 91: /** The last month in each quarter. */ 92: public static final int[] LAST_MONTH_IN_QUARTER = { 93: 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 94: MonthConstants.DECEMBER 95: }; 96: 97: /** The year in which the quarter falls. */ 98: private short year; 99: 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("Quarter outside valid range."); 125: } 126: this.year = (short) year; 127: this.quarter = (byte) quarter; 128: peg(Calendar.getInstance()); 129: } 130: 131: /** 132: * Constructs a new quarter. 133: * 134: * @param quarter the quarter (1 to 4). 135: * @param year the year (1900 to 9999). 136: */ 137: public Quarter(int quarter, Year year) { 138: if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) { 139: throw new IllegalArgumentException("Quarter outside valid range."); 140: } 141: this.year = (short) year.getYear(); 142: this.quarter = (byte) quarter; 143: peg(Calendar.getInstance()); 144: } 145: 146: /** 147: * Constructs a new Quarter, based on a date/time and the default time zone. 148: * 149: * @param time the date/time. 150: */ 151: public Quarter(Date time) { 152: this(time, RegularTimePeriod.DEFAULT_TIME_ZONE); 153: } 154: 155: /** 156: * Constructs a Quarter, based on a date/time and time zone. 157: * 158: * @param time the date/time. 159: * @param zone the zone (<code>null</code> not permitted). 160: */ 161: public Quarter(Date time, TimeZone zone) { 162: Calendar calendar = Calendar.getInstance(zone); 163: calendar.setTime(time); 164: int month = calendar.get(Calendar.MONTH) + 1; 165: this.quarter = (byte) SerialDate.monthCodeToQuarter(month); 166: this.year = (short) calendar.get(Calendar.YEAR); 167: peg(calendar); 168: } 169: 170: /** 171: * Returns the quarter. 172: * 173: * @return The quarter. 174: */ 175: public int getQuarter() { 176: return this.quarter; 177: } 178: 179: /** 180: * Returns the year. 181: * 182: * @return The year. 183: */ 184: public Year getYear() { 185: return new Year(this.year); 186: } 187: 188: /** 189: * Returns the year. 190: * 191: * @return The year. 192: * 193: * @since 1.0.3 194: */ 195: public int getYearValue() { 196: return this.year; 197: } 198: 199: /** 200: * Returns the first millisecond of the quarter. This will be determined 201: * relative to the time zone specified in the constructor, or in the 202: * calendar instance passed in the most recent call to the 203: * {@link #peg(Calendar)} method. 204: * 205: * @return The first millisecond of the quarter. 206: * 207: * @see #getLastMillisecond() 208: */ 209: public long getFirstMillisecond() { 210: return this.firstMillisecond; 211: } 212: 213: /** 214: * Returns the last millisecond of the quarter. This will be 215: * determined relative to the time zone specified in the constructor, or 216: * in the calendar instance passed in the most recent call to the 217: * {@link #peg(Calendar)} method. 218: * 219: * @return The last millisecond of the quarter. 220: * 221: * @see #getFirstMillisecond() 222: */ 223: public long getLastMillisecond() { 224: return this.lastMillisecond; 225: } 226: 227: /** 228: * Recalculates the start date/time and end date/time for this time period 229: * relative to the supplied calendar (which incorporates a time zone). 230: * 231: * @param calendar the calendar (<code>null</code> not permitted). 232: * 233: * @since 1.0.3 234: */ 235: public void peg(Calendar calendar) { 236: this.firstMillisecond = getFirstMillisecond(calendar); 237: this.lastMillisecond = getLastMillisecond(calendar); 238: } 239: 240: /** 241: * Returns the quarter preceding this one. 242: * 243: * @return The quarter preceding this one (or <code>null</code> if this is 244: * Q1 1900). 245: */ 246: public RegularTimePeriod previous() { 247: Quarter result; 248: if (this.quarter > FIRST_QUARTER) { 249: result = new Quarter(this.quarter - 1, this.year); 250: } 251: else { 252: if (this.year > 1900) { 253: result = new Quarter(LAST_QUARTER, this.year - 1); 254: } 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: } 272: else { 273: if (this.year < 9999) { 274: result = new Quarter(FIRST_QUARTER, this.year + 1); 275: } 276: else { 277: result = null; 278: } 279: } 280: return result; 281: } 282: 283: /** 284: * Returns a serial index number for the quarter. 285: * 286: * @return The serial index number. 287: */ 288: public long getSerialIndex() { 289: return this.year * 4L + this.quarter; 290: } 291: 292: /** 293: * Tests the equality of this Quarter object to an arbitrary object. 294: * Returns <code>true</code> if the target is a Quarter instance 295: * representing the same quarter as this object. In all other cases, 296: * returns <code>false</code>. 297: * 298: * @param obj the object (<code>null</code> permitted). 299: * 300: * @return <code>true</code> if quarter and year of this and the object are 301: * the same. 302: */ 303: public boolean equals(Object obj) { 304: 305: if (obj != null) { 306: if (obj instanceof Quarter) { 307: Quarter target = (Quarter) obj; 308: return (this.quarter == target.getQuarter() 309: && (this.year == target.getYearValue())); 310: } 311: else { 312: return false; 313: } 314: } 315: else { 316: return false; 317: } 318: 319: } 320: 321: /** 322: * Returns a hash code for this object instance. The approach described by 323: * Joshua Bloch in "Effective Java" has been used here: 324: * <p> 325: * <code>http://developer.java.sun.com/developer/Books/effectivejava 326: * /Chapter3.pdf</code> 327: * 328: * @return A hash code. 329: */ 330: public int hashCode() { 331: int result = 17; 332: result = 37 * result + this.quarter; 333: result = 37 * result + this.year; 334: return result; 335: } 336: 337: /** 338: * Returns an integer indicating the order of this Quarter object relative 339: * to the specified object: 340: * 341: * negative == before, zero == same, positive == after. 342: * 343: * @param o1 the object to compare 344: * 345: * @return negative == before, zero == same, positive == after. 346: */ 347: public int compareTo(Object o1) { 348: 349: int result; 350: 351: // CASE 1 : Comparing to another Quarter object 352: // -------------------------------------------- 353: if (o1 instanceof Quarter) { 354: Quarter q = (Quarter) o1; 355: result = this.year - q.getYearValue(); 356: if (result == 0) { 357: result = this.quarter - q.getQuarter(); 358: } 359: } 360: 361: // CASE 2 : Comparing to another TimePeriod object 362: // ----------------------------------------------- 363: else if (o1 instanceof RegularTimePeriod) { 364: // more difficult case - evaluate later... 365: result = 0; 366: } 367: 368: // CASE 3 : Comparing to a non-TimePeriod object 369: // --------------------------------------------- 370: else { 371: // consider time periods to be ordered after general objects 372: result = 1; 373: } 374: 375: return result; 376: 377: } 378: 379: /** 380: * Returns a string representing the quarter (e.g. "Q1/2002"). 381: * 382: * @return A string representing the quarter. 383: */ 384: public String toString() { 385: return "Q" + this.quarter + "/" + this.year; 386: } 387: 388: /** 389: * Returns the first millisecond in the Quarter, evaluated using the 390: * supplied calendar (which determines the time zone). 391: * 392: * @param calendar the calendar (<code>null</code> not permitted). 393: * 394: * @return The first millisecond in the Quarter. 395: * 396: * @throws NullPointerException if <code>calendar</code> is 397: * <code>null</code>. 398: */ 399: public long getFirstMillisecond(Calendar calendar) { 400: int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter]; 401: calendar.set(this.year, month - 1, 1, 0, 0, 0); 402: calendar.set(Calendar.MILLISECOND, 0); 403: // in the following line, we'd rather call calendar.getTimeInMillis() 404: // to avoid object creation, but that isn't supported in Java 1.3.1 405: return calendar.getTime().getTime(); 406: } 407: 408: /** 409: * Returns the last millisecond of the Quarter, evaluated using the 410: * supplied calendar (which determines the time zone). 411: * 412: * @param calendar the calendar (<code>null</code> not permitted). 413: * 414: * @return The last millisecond of the Quarter. 415: * 416: * @throws NullPointerException if <code>calendar</code> is 417: * <code>null</code>. 418: */ 419: public long getLastMillisecond(Calendar calendar) { 420: int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter]; 421: int eom = SerialDate.lastDayOfMonth(month, this.year); 422: calendar.set(this.year, month - 1, eom, 23, 59, 59); 423: calendar.set(Calendar.MILLISECOND, 999); 424: // in the following line, we'd rather call calendar.getTimeInMillis() 425: // to avoid object creation, but that isn't supported in Java 1.3.1 426: return calendar.getTime().getTime(); 427: } 428: 429: /** 430: * Parses the string argument as a quarter. 431: * <P> 432: * This method should accept the following formats: "YYYY-QN" and "QN-YYYY", 433: * where the "-" can be a space, a forward-slash (/), comma or a dash (-). 434: * @param s A string representing the quarter. 435: * 436: * @return The quarter. 437: */ 438: public static Quarter parseQuarter(String s) { 439: 440: // find the Q and the integer following it (remove both from the 441: // string)... 442: int i = s.indexOf("Q"); 443: if (i == -1) { 444: throw new TimePeriodFormatException("Missing Q."); 445: } 446: 447: if (i == s.length() - 1) { 448: throw new TimePeriodFormatException("Q found at end of string."); 449: } 450: 451: String qstr = s.substring(i + 1, i + 2); 452: int quarter = Integer.parseInt(qstr); 453: String remaining = s.substring(0, i) + s.substring(i + 2, s.length()); 454: 455: // replace any / , or - with a space 456: remaining = remaining.replace('/', ' '); 457: remaining = remaining.replace(',', ' '); 458: remaining = remaining.replace('-', ' '); 459: 460: // parse the string... 461: Year year = Year.parseYear(remaining.trim()); 462: Quarter result = new Quarter(quarter, year); 463: return result; 464: 465: } 466: 467: }