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.1 2005/10/25 21:35:24 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 * 055 */ 056 057 package org.jfree.data.time; 058 059 import java.io.Serializable; 060 import java.util.Calendar; 061 import java.util.Date; 062 import java.util.TimeZone; 063 064 import org.jfree.date.MonthConstants; 065 import org.jfree.date.SerialDate; 066 067 /** 068 * Defines a quarter (in a given year). The range supported is Q1 1900 to 069 * Q4 9999. This class is immutable, which is a requirement for all 070 * {@link RegularTimePeriod} subclasses. 071 */ 072 public class Quarter extends RegularTimePeriod implements Serializable { 073 074 /** For serialization. */ 075 private static final long serialVersionUID = 3810061714380888671L; 076 077 /** Constant for quarter 1. */ 078 public static final int FIRST_QUARTER = 1; 079 080 /** Constant for quarter 4. */ 081 public static final int LAST_QUARTER = 4; 082 083 /** The first month in each quarter. */ 084 public static final int[] FIRST_MONTH_IN_QUARTER = { 085 0, MonthConstants.JANUARY, MonthConstants.APRIL, MonthConstants.JULY, 086 MonthConstants.OCTOBER 087 }; 088 089 /** The last month in each quarter. */ 090 public static final int[] LAST_MONTH_IN_QUARTER = { 091 0, MonthConstants.MARCH, MonthConstants.JUNE, MonthConstants.SEPTEMBER, 092 MonthConstants.DECEMBER 093 }; 094 095 /** The year in which the quarter falls. */ 096 private Year year; 097 098 /** The quarter (1-4). */ 099 private int quarter; 100 101 /** 102 * Constructs a new Quarter, based on the current system date/time. 103 */ 104 public Quarter() { 105 this(new Date()); 106 } 107 108 /** 109 * Constructs a new quarter. 110 * 111 * @param year the year (1900 to 9999). 112 * @param quarter the quarter (1 to 4). 113 */ 114 public Quarter(int quarter, int year) { 115 this(quarter, new Year(year)); 116 } 117 118 /** 119 * Constructs a new quarter. 120 * 121 * @param quarter the quarter (1 to 4). 122 * @param year the year (1900 to 9999). 123 */ 124 public Quarter(int quarter, Year year) { 125 if ((quarter < FIRST_QUARTER) && (quarter > LAST_QUARTER)) { 126 throw new IllegalArgumentException("Quarter outside valid range."); 127 } 128 this.year = year; 129 this.quarter = quarter; 130 } 131 132 /** 133 * Constructs a new Quarter, based on a date/time and the default time zone. 134 * 135 * @param time the date/time. 136 */ 137 public Quarter(Date time) { 138 this(time, RegularTimePeriod.DEFAULT_TIME_ZONE); 139 } 140 141 /** 142 * Constructs a Quarter, based on a date/time and time zone. 143 * 144 * @param time the date/time. 145 * @param zone the zone. 146 */ 147 public Quarter(Date time, TimeZone zone) { 148 149 Calendar calendar = Calendar.getInstance(zone); 150 calendar.setTime(time); 151 int month = calendar.get(Calendar.MONTH) + 1; 152 this.quarter = SerialDate.monthCodeToQuarter(month); 153 this.year = new Year(calendar.get(Calendar.YEAR)); 154 155 } 156 157 /** 158 * Returns the quarter. 159 * 160 * @return The quarter. 161 */ 162 public int getQuarter() { 163 return this.quarter; 164 } 165 166 /** 167 * Returns the year. 168 * 169 * @return The year. 170 */ 171 public Year getYear() { 172 return this.year; 173 } 174 175 /** 176 * Returns the quarter preceding this one. 177 * 178 * @return The quarter preceding this one (or null if this is Q1 1900). 179 */ 180 public RegularTimePeriod previous() { 181 182 Quarter result; 183 if (this.quarter > FIRST_QUARTER) { 184 result = new Quarter(this.quarter - 1, this.year); 185 } 186 else { 187 Year prevYear = (Year) this.year.previous(); 188 if (prevYear != null) { 189 result = new Quarter(LAST_QUARTER, prevYear); 190 } 191 else { 192 result = null; 193 } 194 } 195 return result; 196 197 } 198 199 /** 200 * Returns the quarter following this one. 201 * 202 * @return The quarter following this one (or null if this is Q4 9999). 203 */ 204 public RegularTimePeriod next() { 205 206 Quarter result; 207 if (this.quarter < LAST_QUARTER) { 208 result = new Quarter(this.quarter + 1, this.year); 209 } 210 else { 211 Year nextYear = (Year) this.year.next(); 212 if (nextYear != null) { 213 result = new Quarter(FIRST_QUARTER, nextYear); 214 } 215 else { 216 result = null; 217 } 218 } 219 return result; 220 221 } 222 223 /** 224 * Returns a serial index number for the quarter. 225 * 226 * @return The serial index number. 227 */ 228 public long getSerialIndex() { 229 return this.year.getYear() * 4L + this.quarter; 230 } 231 232 /** 233 * Tests the equality of this Quarter object to an arbitrary object. 234 * Returns true if the target is a Quarter instance representing the same 235 * quarter as this object. In all other cases, returns false. 236 * 237 * @param obj the object. 238 * 239 * @return <code>true</code> if quarter and year of this and the object are 240 * the same. 241 */ 242 public boolean equals(Object obj) { 243 244 if (obj != null) { 245 if (obj instanceof Quarter) { 246 Quarter target = (Quarter) obj; 247 return ( 248 (this.quarter == target.getQuarter()) 249 && (this.year.equals(target.getYear())) 250 ); 251 } 252 else { 253 return false; 254 } 255 } 256 else { 257 return false; 258 } 259 260 } 261 262 /** 263 * Returns a hash code for this object instance. The approach described by 264 * Joshua Bloch in "Effective Java" has been used here: 265 * <p> 266 * <code>http://developer.java.sun.com/developer/Books/effectivejava 267 * /Chapter3.pdf</code> 268 * 269 * @return A hash code. 270 */ 271 public int hashCode() { 272 int result = 17; 273 result = 37 * result + this.quarter; 274 result = 37 * result + this.year.hashCode(); 275 return result; 276 } 277 278 /** 279 * Returns an integer indicating the order of this Quarter object relative 280 * to the specified object: 281 * 282 * negative == before, zero == same, positive == after. 283 * 284 * @param o1 the object to compare 285 * 286 * @return negative == before, zero == same, positive == after. 287 */ 288 public int compareTo(Object o1) { 289 290 int result; 291 292 // CASE 1 : Comparing to another Quarter object 293 // -------------------------------------------- 294 if (o1 instanceof Quarter) { 295 Quarter q = (Quarter) o1; 296 result = this.year.getYear() - q.getYear().getYear(); 297 if (result == 0) { 298 result = this.quarter - q.getQuarter(); 299 } 300 } 301 302 // CASE 2 : Comparing to another TimePeriod object 303 // ----------------------------------------------- 304 else if (o1 instanceof RegularTimePeriod) { 305 // more difficult case - evaluate later... 306 result = 0; 307 } 308 309 // CASE 3 : Comparing to a non-TimePeriod object 310 // --------------------------------------------- 311 else { 312 // consider time periods to be ordered after general objects 313 result = 1; 314 } 315 316 return result; 317 318 } 319 320 /** 321 * Returns a string representing the quarter (e.g. "Q1/2002"). 322 * 323 * @return A string representing the quarter. 324 */ 325 public String toString() { 326 return "Q" + this.quarter + "/" + this.year; 327 } 328 329 /** 330 * Returns the first millisecond in the Quarter, evaluated using the 331 * supplied calendar (which determines the time zone). 332 * 333 * @param calendar the calendar. 334 * 335 * @return The first millisecond in the Quarter. 336 */ 337 public long getFirstMillisecond(Calendar calendar) { 338 339 int month = Quarter.FIRST_MONTH_IN_QUARTER[this.quarter]; 340 Day first = new Day(1, month, this.year.getYear()); 341 return first.getFirstMillisecond(calendar); 342 343 } 344 345 /** 346 * Returns the last millisecond of the Quarter, evaluated using the 347 * supplied calendar (which determines the time zone). 348 * 349 * @param calendar the calendar. 350 * 351 * @return The last millisecond of the Quarter. 352 */ 353 public long getLastMillisecond(Calendar calendar) { 354 355 int month = Quarter.LAST_MONTH_IN_QUARTER[this.quarter]; 356 int eom = SerialDate.lastDayOfMonth(month, this.year.getYear()); 357 Day last = new Day(eom, month, this.year.getYear()); 358 return last.getLastMillisecond(calendar); 359 360 } 361 362 /** 363 * Parses the string argument as a quarter. 364 * <P> 365 * This method should accept the following formats: "YYYY-QN" and "QN-YYYY", 366 * where the "-" can be a space, a forward-slash (/), comma or a dash (-). 367 * @param s A string representing the quarter. 368 * 369 * @return The quarter. 370 */ 371 public static Quarter parseQuarter(String s) { 372 373 // find the Q and the integer following it (remove both from the 374 // string)... 375 int i = s.indexOf("Q"); 376 if (i == -1) { 377 throw new TimePeriodFormatException("Missing Q."); 378 } 379 380 if (i == s.length() - 1) { 381 throw new TimePeriodFormatException("Q found at end of string."); 382 } 383 384 String qstr = s.substring(i + 1, i + 2); 385 int quarter = Integer.parseInt(qstr); 386 String remaining = s.substring(0, i) + s.substring(i + 2, s.length()); 387 388 // replace any / , or - with a space 389 remaining = remaining.replace('/', ' '); 390 remaining = remaining.replace(',', ' '); 391 remaining = remaining.replace('-', ' '); 392 393 // parse the string... 394 Year year = Year.parseYear(remaining.trim()); 395 Quarter result = new Quarter(quarter, year); 396 return result; 397 398 } 399 400 }