001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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-2005, 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.2 2005/12/10 20:34:21 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 * 056 */ 057 058 package org.jfree.data.time; 059 060 import java.io.Serializable; 061 import java.util.Calendar; 062 import java.util.Date; 063 import java.util.TimeZone; 064 065 import org.jfree.date.MonthConstants; 066 import org.jfree.date.SerialDate; 067 068 /** 069 * Defines a quarter (in a given year). The range supported is Q1 1900 to 070 * Q4 9999. This class is immutable, which is a requirement for all 071 * {@link RegularTimePeriod} subclasses. 072 */ 073 public class Quarter extends RegularTimePeriod implements Serializable { 074 075 /** For serialization. */ 076 private static final long serialVersionUID = 3810061714380888671L; 077 078 /** Constant for quarter 1. */ 079 public static final int FIRST_QUARTER = 1; 080 081 /** Constant for quarter 4. */ 082 public static final int LAST_QUARTER = 4; 083 084 /** The first month in each quarter. */ 085 public static final int[] FIRST_MONTH_IN_QUARTER = { 086 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 087 MonthConstants.OCTOBER 088 }; 089 090 /** The last month in each quarter. */ 091 public static final int[] LAST_MONTH_IN_QUARTER = { 092 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 093 MonthConstants.DECEMBER 094 }; 095 096 /** The year in which the quarter falls. */ 097 private Year year; 098 099 /** 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 }