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 * Month.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: Month.java,v 1.7.2.2 2005/11/02 13:20:29 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 11-Oct-2001 : Version 1 (DG); 040 * 14-Nov-2001 : Added method to get year as primitive (DG); 041 * Override for toString() method (DG); 042 * 18-Dec-2001 : Changed order of parameters in constructor (DG); 043 * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG); 044 * 29-Jan-2002 : Worked on the parseMonth() method (DG); 045 * 14-Feb-2002 : Fixed bugs in the Month constructors (DG); 046 * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 047 * evaluate with reference to a particular time zone (DG); 048 * 19-Mar-2002 : Changed API for TimePeriod classes (DG); 049 * 10-Sep-2002 : Added getSerialIndex() method (DG); 050 * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG); 051 * 10-Jan-2003 : Changed base class and method names (DG); 052 * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 053 * Serializable (DG); 054 * 21-Oct-2003 : Added hashCode() method (DG); 055 * 01-Nov-2005 : Fixed bug 1345383 (argument check in constructor) (DG); 056 * 057 */ 058 059 package org.jfree.data.time; 060 061 import java.io.Serializable; 062 import java.util.Calendar; 063 import java.util.Date; 064 import java.util.TimeZone; 065 066 import org.jfree.date.MonthConstants; 067 import org.jfree.date.SerialDate; 068 069 /** 070 * Represents a single month. This class is immutable, which is a requirement 071 * for all {@link RegularTimePeriod} subclasses. 072 */ 073 public class Month extends RegularTimePeriod implements Serializable { 074 075 /** For serialization. */ 076 private static final long serialVersionUID = -5090216912548722570L; 077 078 /** The month (1-12). */ 079 private int month; 080 081 /** The year in which the month falls. */ 082 private Year year; 083 084 /** 085 * Constructs a new Month, based on the current system time. 086 */ 087 public Month() { 088 this(new Date()); 089 } 090 091 /** 092 * Constructs a new month instance. 093 * 094 * @param month the month (in the range 1 to 12). 095 * @param year the year. 096 */ 097 public Month(int month, int year) { 098 this(month, new Year(year)); 099 } 100 101 /** 102 * Constructs a new month instance. 103 * 104 * @param month the month (in the range 1 to 12). 105 * @param year the year. 106 */ 107 public Month(int month, Year year) { 108 109 if ((month < 1) || (month > 12)) { 110 throw new IllegalArgumentException("Month outside valid range."); 111 } 112 this.month = month; 113 this.year = year; 114 115 } 116 117 /** 118 * Constructs a new month instance, based on a date/time and the default 119 * time zone. 120 * 121 * @param time the date/time. 122 */ 123 public Month(Date time) { 124 this(time, RegularTimePeriod.DEFAULT_TIME_ZONE); 125 } 126 127 /** 128 * Constructs a Month, based on a date/time and a time zone. 129 * 130 * @param time the date/time. 131 * @param zone the time zone. 132 */ 133 public Month(Date time, TimeZone zone) { 134 Calendar calendar = Calendar.getInstance(zone); 135 calendar.setTime(time); 136 this.month = calendar.get(Calendar.MONTH) + 1; 137 this.year = new Year(calendar.get(Calendar.YEAR)); 138 } 139 140 /** 141 * Returns the year in which the month falls. 142 * 143 * @return The year in which the month falls (as a Year object). 144 */ 145 public Year getYear() { 146 return this.year; 147 } 148 149 /** 150 * Returns the year in which the month falls. 151 * 152 * @return The year in which the monht falls (as an int). 153 */ 154 public int getYearValue() { 155 return this.year.getYear(); 156 } 157 158 /** 159 * Returns the month. Note that 1=JAN, 2=FEB, ... 160 * 161 * @return The month. 162 */ 163 public int getMonth() { 164 return this.month; 165 } 166 167 /** 168 * Returns the month preceding this one. 169 * 170 * @return The month preceding this one. 171 */ 172 public RegularTimePeriod previous() { 173 174 Month result; 175 if (this.month != MonthConstants.JANUARY) { 176 result = new Month(this.month - 1, this.year); 177 } 178 else { 179 Year prevYear = (Year) this.year.previous(); 180 if (prevYear != null) { 181 result = new Month(MonthConstants.DECEMBER, prevYear); 182 } 183 else { 184 result = null; 185 } 186 } 187 return result; 188 189 } 190 191 /** 192 * Returns the month following this one. 193 * 194 * @return The month following this one. 195 */ 196 public RegularTimePeriod next() { 197 Month result; 198 if (this.month != MonthConstants.DECEMBER) { 199 result = new Month(this.month + 1, this.year); 200 } 201 else { 202 Year nextYear = (Year) this.year.next(); 203 if (nextYear != null) { 204 result = new Month(MonthConstants.JANUARY, nextYear); 205 } 206 else { 207 result = null; 208 } 209 } 210 return result; 211 } 212 213 /** 214 * Returns a serial index number for the month. 215 * 216 * @return The serial index number. 217 */ 218 public long getSerialIndex() { 219 return this.year.getYear() * 12L + this.month; 220 } 221 222 /** 223 * Returns a string representing the month (e.g. "January 2002"). 224 * <P> 225 * To do: look at internationalisation. 226 * 227 * @return A string representing the month. 228 */ 229 public String toString() { 230 return SerialDate.monthCodeToString(this.month) + " " + this.year; 231 } 232 233 /** 234 * Tests the equality of this Month object to an arbitrary object. 235 * Returns true if the target is a Month instance representing the same 236 * month as this object. In all other cases, returns false. 237 * 238 * @param obj the object. 239 * 240 * @return <code>true</code> if month and year of this and object are the 241 * same. 242 */ 243 public boolean equals(Object obj) { 244 245 if (obj != null) { 246 if (obj instanceof Month) { 247 Month target = (Month) obj; 248 return ( 249 (this.month == target.getMonth()) 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.month; 275 result = 37 * result + this.year.hashCode(); 276 return result; 277 } 278 279 /** 280 * Returns an integer indicating the order of this Month object relative to 281 * the specified 282 * object: 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 Month object 293 // -------------------------------------------- 294 if (o1 instanceof Month) { 295 Month m = (Month) o1; 296 result = this.year.getYear() - m.getYear().getYear(); 297 if (result == 0) { 298 result = this.month - m.getMonth(); 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 the first millisecond of the month, evaluated using the supplied 322 * calendar (which determines the time zone). 323 * 324 * @param calendar the calendar. 325 * 326 * @return The first millisecond of the month. 327 */ 328 public long getFirstMillisecond(Calendar calendar) { 329 Day first = new Day(1, this.month, this.year.getYear()); 330 return first.getFirstMillisecond(calendar); 331 } 332 333 /** 334 * Returns the last millisecond of the month, evaluated using the supplied 335 * calendar (which determines the time zone). 336 * 337 * @param calendar the calendar. 338 * 339 * @return The last millisecond of the month. 340 */ 341 public long getLastMillisecond(Calendar calendar) { 342 int eom = SerialDate.lastDayOfMonth(this.month, this.year.getYear()); 343 Day last = new Day(eom, this.month, this.year.getYear()); 344 return last.getLastMillisecond(calendar); 345 } 346 347 /** 348 * Parses the string argument as a month. 349 * <P> 350 * This method is required to accept the format "YYYY-MM". It will also 351 * accept "MM-YYYY". Anything else, at the moment, is a bonus. 352 * 353 * @param s the string to parse. 354 * 355 * @return <code>null</code> if the string is not parseable, the month 356 * otherwise. 357 */ 358 public static Month parseMonth(String s) { 359 360 Month result = null; 361 if (s != null) { 362 363 // trim whitespace from either end of the string 364 s = s.trim(); 365 366 int i = Month.findSeparator(s); 367 if (i != -1) { 368 String s1 = s.substring(0, i).trim(); 369 String s2 = s.substring(i + 1, s.length()).trim(); 370 371 Year year = Month.evaluateAsYear(s1); 372 int month; 373 if (year != null) { 374 month = SerialDate.stringToMonthCode(s2); 375 if (month == -1) { 376 throw new TimePeriodFormatException( 377 "Can't evaluate the month." 378 ); 379 } 380 result = new Month(month, year); 381 } 382 else { 383 year = Month.evaluateAsYear(s2); 384 if (year != null) { 385 month = SerialDate.stringToMonthCode(s1); 386 if (month == -1) { 387 throw new TimePeriodFormatException( 388 "Can't evaluate the month." 389 ); 390 } 391 result = new Month(month, year); 392 } 393 else { 394 throw new TimePeriodFormatException( 395 "Can't evaluate the year." 396 ); 397 } 398 } 399 400 } 401 else { 402 throw new TimePeriodFormatException( 403 "Could not find separator." 404 ); 405 } 406 407 } 408 return result; 409 410 } 411 412 /** 413 * Finds the first occurrence of ' ', '-', ',' or '.' 414 * 415 * @param s the string to parse. 416 * 417 * @return <code>-1</code> if none of the characters where found, the 418 * position of the first occurence otherwise. 419 */ 420 private static int findSeparator(String s) { 421 422 int result = s.indexOf('-'); 423 if (result == -1) { 424 result = s.indexOf(','); 425 } 426 if (result == -1) { 427 result = s.indexOf(' '); 428 } 429 if (result == -1) { 430 result = s.indexOf('.'); 431 } 432 return result; 433 } 434 435 /** 436 * Creates a year from a string, or returns null (format exceptions 437 * suppressed). 438 * 439 * @param s the string to parse. 440 * 441 * @return <code>null</code> if the string is not parseable, the year 442 * otherwise. 443 */ 444 private static Year evaluateAsYear(String s) { 445 446 Year result = null; 447 try { 448 result = Year.parseYear(s); 449 } 450 catch (TimePeriodFormatException e) { 451 // suppress 452 } 453 return result; 454 455 } 456 457 }