Source for org.jfree.data.time.Quarter

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