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 * Changes 036 * ------- 037 * 11-Oct-2001 : Version 1 (DG); 038 * 18-Dec-2001 : Changed order of parameters in constructor (DG); 039 * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG); 040 * 29-Jan-2002 : Added a new method parseQuarter(String) (DG); 041 * 14-Feb-2002 : Fixed bug in Quarter(Date) constructor (DG); 042 * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 043 * evaluate with reference to a particular time zone (DG); 044 * 19-Mar-2002 : Changed API for TimePeriod classes (DG); 045 * 24-Jun-2002 : Removed main method (just test code) (DG); 046 * 10-Sep-2002 : Added getSerialIndex() method (DG); 047 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 048 * 10-Jan-2003 : Changed base class and method names (DG); 049 * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 050 * Serializable (DG); 051 * 21-Oct-2003 : Added hashCode() method (DG); 052 * 10-Dec-2005 : Fixed argument checking bug (1377239) in constructor (DG); 053 * ------------- JFREECHART 1.0.x --------------------------------------------- 054 * 05-Oct-2006 : Updated API docs (DG); 055 * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG); 056 * 057 */ 058 059 package org.jfree.data.time; 060 061 import java.io.Serializable; 062 import java.util.Calendar; 063 import java.util.Date; 064 import java.util.TimeZone; 065 066 import org.jfree.date.MonthConstants; 067 import org.jfree.date.SerialDate; 068 069 /** 070 * Defines a quarter (in a given year). The range supported is Q1 1900 to 071 * Q4 9999. This class is immutable, which is a requirement for all 072 * {@link RegularTimePeriod} subclasses. 073 */ 074 public class Quarter extends RegularTimePeriod implements Serializable { 075 076 /** For serialization. */ 077 private static final long serialVersionUID = 3810061714380888671L; 078 079 /** Constant for quarter 1. */ 080 public static final int FIRST_QUARTER = 1; 081 082 /** Constant for quarter 4. */ 083 public static final int LAST_QUARTER = 4; 084 085 /** The first month in each quarter. */ 086 public static final int[] FIRST_MONTH_IN_QUARTER = { 087 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 088 MonthConstants.OCTOBER 089 }; 090 091 /** The last month in each quarter. */ 092 public static final int[] LAST_MONTH_IN_QUARTER = { 093 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 094 MonthConstants.DECEMBER 095 }; 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("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 }