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