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