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 = { 089 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 090 MonthConstants.OCTOBER 091 }; 092 093 /** The last month in each quarter. */ 094 public static final int[] LAST_MONTH_IN_QUARTER = { 095 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 096 MonthConstants.DECEMBER 097 }; 098 099 /** 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 }