001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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 * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG); 057 * 25-Nov-2008 : Added new constructor with Locale (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.Locale; 067 import java.util.TimeZone; 068 069 import org.jfree.date.MonthConstants; 070 import org.jfree.date.SerialDate; 071 072 /** 073 * Defines a quarter (in a given year). The range supported is Q1 1900 to 074 * Q4 9999. This class is immutable, which is a requirement for all 075 * {@link RegularTimePeriod} subclasses. 076 */ 077 public class Quarter extends RegularTimePeriod implements Serializable { 078 079 /** For serialization. */ 080 private static final long serialVersionUID = 3810061714380888671L; 081 082 /** Constant for quarter 1. */ 083 public static final int FIRST_QUARTER = 1; 084 085 /** Constant for quarter 4. */ 086 public static final int LAST_QUARTER = 4; 087 088 /** The first month in each quarter. */ 089 public static final int[] FIRST_MONTH_IN_QUARTER = { 090 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 091 MonthConstants.OCTOBER 092 }; 093 094 /** The last month in each quarter. */ 095 public static final int[] LAST_MONTH_IN_QUARTER = { 096 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 097 MonthConstants.DECEMBER 098 }; 099 100 /** The year in which the quarter falls. */ 101 private short year; 102 103 /** The quarter (1-4). */ 104 private byte quarter; 105 106 /** The first millisecond. */ 107 private long firstMillisecond; 108 109 /** The last millisecond. */ 110 private long lastMillisecond; 111 112 /** 113 * Constructs a new Quarter, based on the current system date/time. 114 */ 115 public Quarter() { 116 this(new Date()); 117 } 118 119 /** 120 * Constructs a new quarter. 121 * 122 * @param year the year (1900 to 9999). 123 * @param quarter the quarter (1 to 4). 124 */ 125 public Quarter(int quarter, int year) { 126 if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) { 127 throw new IllegalArgumentException("Quarter outside valid range."); 128 } 129 this.year = (short) year; 130 this.quarter = (byte) quarter; 131 peg(Calendar.getInstance()); 132 } 133 134 /** 135 * Constructs a new quarter. 136 * 137 * @param quarter the quarter (1 to 4). 138 * @param year the year (1900 to 9999). 139 */ 140 public Quarter(int quarter, Year year) { 141 if ((quarter < FIRST_QUARTER) || (quarter > LAST_QUARTER)) { 142 throw new IllegalArgumentException("Quarter outside valid range."); 143 } 144 this.year = (short) year.getYear(); 145 this.quarter = (byte) quarter; 146 peg(Calendar.getInstance()); 147 } 148 149 /** 150 * Constructs a new instance, based on a date/time and the default time 151 * zone. 152 * 153 * @param time the date/time (<code>null</code> not permitted). 154 * 155 * @see #Quarter(Date, TimeZone) 156 */ 157 public Quarter(Date time) { 158 this(time, TimeZone.getDefault()); 159 } 160 161 /** 162 * Constructs a Quarter, based on a date/time and time zone. 163 * 164 * @param time the date/time. 165 * @param zone the zone (<code>null</code> not permitted). 166 * 167 * @deprecated Since 1.0.12, use {@link #Quarter(Date, TimeZone, Locale)} 168 * instead. 169 */ 170 public Quarter(Date time, TimeZone zone) { 171 this(time, zone, Locale.getDefault()); 172 } 173 174 /** 175 * Creates a new <code>Quarter</code> instance, using the specified 176 * zone and locale. 177 * 178 * @param time the current time. 179 * @param zone the time zone. 180 * @param locale the locale. 181 * 182 * @since 1.0.12 183 */ 184 public Quarter(Date time, TimeZone zone, Locale locale) { 185 Calendar calendar = Calendar.getInstance(zone, locale); 186 calendar.setTime(time); 187 int month = calendar.get(Calendar.MONTH) + 1; 188 this.quarter = (byte) SerialDate.monthCodeToQuarter(month); 189 this.year = (short) calendar.get(Calendar.YEAR); 190 peg(calendar); 191 } 192 193 /** 194 * Returns the quarter. 195 * 196 * @return The quarter. 197 */ 198 public int getQuarter() { 199 return this.quarter; 200 } 201 202 /** 203 * Returns the year. 204 * 205 * @return The year. 206 */ 207 public Year getYear() { 208 return new Year(this.year); 209 } 210 211 /** 212 * Returns the year. 213 * 214 * @return The year. 215 * 216 * @since 1.0.3 217 */ 218 public int getYearValue() { 219 return this.year; 220 } 221 222 /** 223 * Returns the first millisecond of the quarter. This will be determined 224 * relative to the time zone specified in the constructor, or in the 225 * calendar instance passed in the most recent call to the 226 * {@link #peg(Calendar)} method. 227 * 228 * @return The first millisecond of the quarter. 229 * 230 * @see #getLastMillisecond() 231 */ 232 public long getFirstMillisecond() { 233 return this.firstMillisecond; 234 } 235 236 /** 237 * Returns the last millisecond of the quarter. This will be 238 * determined relative to the time zone specified in the constructor, or 239 * in the calendar instance passed in the most recent call to the 240 * {@link #peg(Calendar)} method. 241 * 242 * @return The last millisecond of the quarter. 243 * 244 * @see #getFirstMillisecond() 245 */ 246 public long getLastMillisecond() { 247 return this.lastMillisecond; 248 } 249 250 /** 251 * Recalculates the start date/time and end date/time for this time period 252 * relative to the supplied calendar (which incorporates a time zone). 253 * 254 * @param calendar the calendar (<code>null</code> not permitted). 255 * 256 * @since 1.0.3 257 */ 258 public void peg(Calendar calendar) { 259 this.firstMillisecond = getFirstMillisecond(calendar); 260 this.lastMillisecond = getLastMillisecond(calendar); 261 } 262 263 /** 264 * Returns the quarter preceding this one. 265 * 266 * @return The quarter preceding this one (or <code>null</code> if this is 267 * Q1 1900). 268 */ 269 public RegularTimePeriod previous() { 270 Quarter result; 271 if (this.quarter > FIRST_QUARTER) { 272 result = new Quarter(this.quarter - 1, this.year); 273 } 274 else { 275 if (this.year > 1900) { 276 result = new Quarter(LAST_QUARTER, this.year - 1); 277 } 278 else { 279 result = null; 280 } 281 } 282 return result; 283 } 284 285 /** 286 * Returns the quarter following this one. 287 * 288 * @return The quarter following this one (or null if this is Q4 9999). 289 */ 290 public RegularTimePeriod next() { 291 Quarter result; 292 if (this.quarter < LAST_QUARTER) { 293 result = new Quarter(this.quarter + 1, this.year); 294 } 295 else { 296 if (this.year < 9999) { 297 result = new Quarter(FIRST_QUARTER, this.year + 1); 298 } 299 else { 300 result = null; 301 } 302 } 303 return result; 304 } 305 306 /** 307 * Returns a serial index number for the quarter. 308 * 309 * @return The serial index number. 310 */ 311 public long getSerialIndex() { 312 return this.year * 4L + this.quarter; 313 } 314 315 /** 316 * Tests the equality of this Quarter object to an arbitrary object. 317 * Returns <code>true</code> if the target is a Quarter instance 318 * representing the same quarter as this object. In all other cases, 319 * returns <code>false</code>. 320 * 321 * @param obj the object (<code>null</code> permitted). 322 * 323 * @return <code>true</code> if quarter and year of this and the object are 324 * the same. 325 */ 326 public boolean equals(Object obj) { 327 328 if (obj != null) { 329 if (obj instanceof Quarter) { 330 Quarter target = (Quarter) obj; 331 return (this.quarter == target.getQuarter() 332 && (this.year == target.getYearValue())); 333 } 334 else { 335 return false; 336 } 337 } 338 else { 339 return false; 340 } 341 342 } 343 344 /** 345 * Returns a hash code for this object instance. The approach described by 346 * Joshua Bloch in "Effective Java" has been used here: 347 * <p> 348 * <code>http://developer.java.sun.com/developer/Books/effectivejava 349 * /Chapter3.pdf</code> 350 * 351 * @return A hash code. 352 */ 353 public int hashCode() { 354 int result = 17; 355 result = 37 * result + this.quarter; 356 result = 37 * result + this.year; 357 return result; 358 } 359 360 /** 361 * Returns an integer indicating the order of this Quarter object relative 362 * to the specified object: 363 * 364 * negative == before, zero == same, positive == after. 365 * 366 * @param o1 the object to compare 367 * 368 * @return negative == before, zero == same, positive == after. 369 */ 370 public int compareTo(Object o1) { 371 372 int result; 373 374 // CASE 1 : Comparing to another Quarter object 375 // -------------------------------------------- 376 if (o1 instanceof Quarter) { 377 Quarter q = (Quarter) o1; 378 result = this.year - q.getYearValue(); 379 if (result == 0) { 380 result = this.quarter - q.getQuarter(); 381 } 382 } 383 384 // CASE 2 : Comparing to another TimePeriod object 385 // ----------------------------------------------- 386 else if (o1 instanceof RegularTimePeriod) { 387 // more difficult case - evaluate later... 388 result = 0; 389 } 390 391 // CASE 3 : Comparing to a non-TimePeriod object 392 // --------------------------------------------- 393 else { 394 // consider time periods to be ordered after general objects 395 result = 1; 396 } 397 398 return result; 399 400 } 401 402 /** 403 * Returns a string representing the quarter (e.g. "Q1/2002"). 404 * 405 * @return A string representing the quarter. 406 */ 407 public String toString() { 408 return "Q" + this.quarter + "/" + this.year; 409 } 410 411 /** 412 * Returns the first millisecond in the Quarter, evaluated using the 413 * supplied calendar (which determines the time zone). 414 * 415 * @param calendar the calendar (<code>null</code> not permitted). 416 * 417 * @return The first millisecond in the Quarter. 418 * 419 * @throws NullPointerException if <code>calendar</code> is 420 * <code>null</code>. 421 */ 422 public long getFirstMillisecond(Calendar calendar) { 423 int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter]; 424 calendar.set(this.year, month - 1, 1, 0, 0, 0); 425 calendar.set(Calendar.MILLISECOND, 0); 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 * Returns the last millisecond of the Quarter, evaluated using the 433 * supplied calendar (which determines the time zone). 434 * 435 * @param calendar the calendar (<code>null</code> not permitted). 436 * 437 * @return The last millisecond of the Quarter. 438 * 439 * @throws NullPointerException if <code>calendar</code> is 440 * <code>null</code>. 441 */ 442 public long getLastMillisecond(Calendar calendar) { 443 int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter]; 444 int eom = SerialDate.lastDayOfMonth(month, this.year); 445 calendar.set(this.year, month - 1, eom, 23, 59, 59); 446 calendar.set(Calendar.MILLISECOND, 999); 447 // in the following line, we'd rather call calendar.getTimeInMillis() 448 // to avoid object creation, but that isn't supported in Java 1.3.1 449 return calendar.getTime().getTime(); 450 } 451 452 /** 453 * Parses the string argument as a quarter. 454 * <P> 455 * This method should accept the following formats: "YYYY-QN" and "QN-YYYY", 456 * where the "-" can be a space, a forward-slash (/), comma or a dash (-). 457 * @param s A string representing the quarter. 458 * 459 * @return The quarter. 460 */ 461 public static Quarter parseQuarter(String s) { 462 463 // find the Q and the integer following it (remove both from the 464 // string)... 465 int i = s.indexOf("Q"); 466 if (i == -1) { 467 throw new TimePeriodFormatException("Missing Q."); 468 } 469 470 if (i == s.length() - 1) { 471 throw new TimePeriodFormatException("Q found at end of string."); 472 } 473 474 String qstr = s.substring(i + 1, i + 2); 475 int quarter = Integer.parseInt(qstr); 476 String remaining = s.substring(0, i) + s.substring(i + 2, s.length()); 477 478 // replace any / , or - with a space 479 remaining = remaining.replace('/', ' '); 480 remaining = remaining.replace(',', ' '); 481 remaining = remaining.replace('-', ' '); 482 483 // parse the string... 484 Year year = Year.parseYear(remaining.trim()); 485 Quarter result = new Quarter(quarter, year); 486 return result; 487 488 } 489 490 }