Source for org.jfree.data.time.Month

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------
  28:  * Month.java
  29:  * ----------
  30:  * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Chris Boek;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 11-Oct-2001 : Version 1 (DG);
  38:  * 14-Nov-2001 : Added method to get year as primitive (DG);
  39:  *               Override for toString() method (DG);
  40:  * 18-Dec-2001 : Changed order of parameters in constructor (DG);
  41:  * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
  42:  * 29-Jan-2002 : Worked on the parseMonth() method (DG);
  43:  * 14-Feb-2002 : Fixed bugs in the Month constructors (DG);
  44:  * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 
  45:  *               evaluate with reference to a particular time zone (DG);
  46:  * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
  47:  * 10-Sep-2002 : Added getSerialIndex() method (DG);
  48:  * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  49:  * 10-Jan-2003 : Changed base class and method names (DG);
  50:  * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 
  51:  *               Serializable (DG);
  52:  * 21-Oct-2003 : Added hashCode() method (DG);
  53:  * 01-Nov-2005 : Fixed bug 1345383 (argument check in constructor) (DG);
  54:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  55:  * 05-Oct-2006 : Updated API docs (DG);
  56:  * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
  57:  * 04-Apr-2007 : Fixed bug in Month(Date, TimeZone) constructor (CB);
  58:  *
  59:  */
  60: 
  61: package org.jfree.data.time;
  62: 
  63: import java.io.Serializable;
  64: import java.util.Calendar;
  65: import java.util.Date;
  66: import java.util.TimeZone;
  67: 
  68: import org.jfree.date.MonthConstants;
  69: import org.jfree.date.SerialDate;
  70: 
  71: /**
  72:  * Represents a single month.  This class is immutable, which is a requirement
  73:  * for all {@link RegularTimePeriod} subclasses.
  74:  */
  75: public class Month extends RegularTimePeriod implements Serializable {
  76: 
  77:     /** For serialization. */
  78:     private static final long serialVersionUID = -5090216912548722570L;
  79: 
  80:     /** The month (1-12). */
  81:     private int month;
  82: 
  83:     /** The year in which the month falls. */
  84:     private int year;
  85: 
  86:     /** The first millisecond. */
  87:     private long firstMillisecond;
  88:     
  89:     /** The last millisecond. */
  90:     private long lastMillisecond;
  91: 
  92:     /**
  93:      * Constructs a new Month, based on the current system time.
  94:      */
  95:     public Month() {
  96:         this(new Date());
  97:     }
  98: 
  99:     /**
 100:      * Constructs a new month instance.
 101:      *
 102:      * @param month  the month (in the range 1 to 12).
 103:      * @param year  the year.
 104:      */
 105:     public Month(int month, int year) {
 106:         if ((month < 1) || (month > 12)) {
 107:             throw new IllegalArgumentException("Month outside valid range.");
 108:         }
 109:         this.month = month;
 110:         this.year = year;
 111:         peg(Calendar.getInstance());
 112:     }
 113: 
 114:     /**
 115:      * Constructs a new month instance.
 116:      *
 117:      * @param month  the month (in the range 1 to 12).
 118:      * @param year  the year.
 119:      */
 120:     public Month(int month, Year year) {
 121:         if ((month < 1) || (month > 12)) {
 122:             throw new IllegalArgumentException("Month outside valid range.");
 123:         }
 124:         this.month = month;
 125:         this.year = year.getYear();
 126:         peg(Calendar.getInstance());
 127:     }
 128: 
 129:     /**
 130:      * Constructs a new <code>Month</code> instance, based on a date/time and 
 131:      * the default time zone.
 132:      *
 133:      * @param time  the date/time.
 134:      */
 135:     public Month(Date time) {
 136:         this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
 137:     }
 138: 
 139:     /**
 140:      * Constructs a new <code>Month</code> instance, based on a date/time and 
 141:      * a time zone.  The first and last millisecond values are initially
 142:      * pegged to the given time zone also.
 143:      *
 144:      * @param time  the date/time.
 145:      * @param zone  the time zone (<code>null</code> not permitted).
 146:      */
 147:     public Month(Date time, TimeZone zone) {
 148:         Calendar calendar = Calendar.getInstance(zone);
 149:         calendar.setTime(time);
 150:         this.month = calendar.get(Calendar.MONTH) + 1;
 151:         this.year = calendar.get(Calendar.YEAR);
 152:         peg(calendar);
 153:     }
 154: 
 155:     /**
 156:      * Returns the year in which the month falls.
 157:      *
 158:      * @return The year in which the month falls (as a Year object).
 159:      */
 160:     public Year getYear() {
 161:         return new Year(this.year);
 162:     }
 163: 
 164:     /**
 165:      * Returns the year in which the month falls.
 166:      *
 167:      * @return The year in which the month falls (as an int).
 168:      */
 169:     public int getYearValue() {
 170:         return this.year;
 171:     }
 172: 
 173:     /**
 174:      * Returns the month.  Note that 1=JAN, 2=FEB, ...
 175:      *
 176:      * @return The month.
 177:      */
 178:     public int getMonth() {
 179:         return this.month;
 180:     }
 181: 
 182:     /**
 183:      * Returns the first millisecond of the month.  This will be determined 
 184:      * relative to the time zone specified in the constructor, or in the 
 185:      * calendar instance passed in the most recent call to the 
 186:      * {@link #peg(Calendar)} method.
 187:      *
 188:      * @return The first millisecond of the month.
 189:      * 
 190:      * @see #getLastMillisecond()
 191:      */
 192:     public long getFirstMillisecond() {
 193:         return this.firstMillisecond;
 194:     }
 195: 
 196:     /**
 197:      * Returns the last millisecond of the month.  This will be 
 198:      * determined relative to the time zone specified in the constructor, or
 199:      * in the calendar instance passed in the most recent call to the 
 200:      * {@link #peg(Calendar)} method.
 201:      *
 202:      * @return The last millisecond of the month.
 203:      * 
 204:      * @see #getFirstMillisecond()
 205:      */
 206:     public long getLastMillisecond() {
 207:         return this.lastMillisecond;
 208:     }
 209:     
 210:     /** 
 211:      * Recalculates the start date/time and end date/time for this time period 
 212:      * relative to the supplied calendar (which incorporates a time zone).
 213:      * 
 214:      * @param calendar  the calendar (<code>null</code> not permitted).
 215:      * 
 216:      * @since 1.0.3
 217:      */
 218:     public void peg(Calendar calendar) {
 219:         this.firstMillisecond = getFirstMillisecond(calendar);
 220:         this.lastMillisecond = getLastMillisecond(calendar);
 221:     }
 222: 
 223:     /**
 224:      * Returns the month preceding this one.
 225:      *
 226:      * @return The month preceding this one.
 227:      */
 228:     public RegularTimePeriod previous() {
 229:         Month result;
 230:         if (this.month != MonthConstants.JANUARY) {
 231:             result = new Month(this.month - 1, this.year);
 232:         }
 233:         else {
 234:             if (this.year > 1900) {
 235:                 result = new Month(MonthConstants.DECEMBER, this.year - 1);
 236:             }
 237:             else {
 238:                 result = null;
 239:             }
 240:         }
 241:         return result;
 242:     }
 243: 
 244:     /**
 245:      * Returns the month following this one.
 246:      *
 247:      * @return The month following this one.
 248:      */
 249:     public RegularTimePeriod next() {
 250:         Month result;
 251:         if (this.month != MonthConstants.DECEMBER) {
 252:             result = new Month(this.month + 1, this.year);
 253:         }
 254:         else {
 255:             if (this.year < 9999) {
 256:                 result = new Month(MonthConstants.JANUARY, this.year + 1);
 257:             }
 258:             else {
 259:                 result = null;
 260:             }
 261:         }
 262:         return result;
 263:     }
 264: 
 265:     /**
 266:      * Returns a serial index number for the month.
 267:      *
 268:      * @return The serial index number.
 269:      */
 270:     public long getSerialIndex() {
 271:         return this.year * 12L + this.month;
 272:     }
 273: 
 274:     /**
 275:      * Returns a string representing the month (e.g. "January 2002").
 276:      * <P>
 277:      * To do: look at internationalisation.
 278:      *
 279:      * @return A string representing the month.
 280:      */
 281:     public String toString() {
 282:         return SerialDate.monthCodeToString(this.month) + " " + this.year;
 283:     }
 284: 
 285:     /**
 286:      * Tests the equality of this Month object to an arbitrary object.
 287:      * Returns true if the target is a Month instance representing the same
 288:      * month as this object.  In all other cases, returns false.
 289:      *
 290:      * @param obj  the object (<code>null</code> permitted).
 291:      *
 292:      * @return <code>true</code> if month and year of this and object are the 
 293:      *         same.
 294:      */
 295:     public boolean equals(Object obj) {
 296: 
 297:         if (obj != null) {
 298:             if (obj instanceof Month) {
 299:                 Month target = (Month) obj;
 300:                 return (this.month == target.getMonth() 
 301:                         && (this.year == target.getYearValue()));
 302:             }
 303:             else {
 304:                 return false;
 305:             }
 306:         }
 307:         else {
 308:             return false;
 309:         }
 310: 
 311:     }
 312: 
 313:     /**
 314:      * Returns a hash code for this object instance.  The approach described by
 315:      * Joshua Bloch in "Effective Java" has been used here:
 316:      * <p>
 317:      * <code>http://developer.java.sun.com/developer/Books/effectivejava
 318:      * /Chapter3.pdf</code>
 319:      * 
 320:      * @return A hash code.
 321:      */
 322:     public int hashCode() {
 323:         int result = 17;
 324:         result = 37 * result + this.month;
 325:         result = 37 * result + this.year;
 326:         return result;
 327:     }
 328: 
 329:     /**
 330:      * Returns an integer indicating the order of this Month object relative to
 331:      * the specified
 332:      * object: negative == before, zero == same, positive == after.
 333:      *
 334:      * @param o1  the object to compare.
 335:      *
 336:      * @return negative == before, zero == same, positive == after.
 337:      */
 338:     public int compareTo(Object o1) {
 339: 
 340:         int result;
 341: 
 342:         // CASE 1 : Comparing to another Month object
 343:         // --------------------------------------------
 344:         if (o1 instanceof Month) {
 345:             Month m = (Month) o1;
 346:             result = this.year - m.getYearValue();
 347:             if (result == 0) {
 348:                 result = this.month - m.getMonth();
 349:             }
 350:         }
 351: 
 352:         // CASE 2 : Comparing to another TimePeriod object
 353:         // -----------------------------------------------
 354:         else if (o1 instanceof RegularTimePeriod) {
 355:             // more difficult case - evaluate later...
 356:             result = 0;
 357:         }
 358: 
 359:         // CASE 3 : Comparing to a non-TimePeriod object
 360:         // ---------------------------------------------
 361:         else {
 362:             // consider time periods to be ordered after general objects
 363:             result = 1;
 364:         }
 365: 
 366:         return result;
 367: 
 368:     }
 369: 
 370:     /**
 371:      * Returns the first millisecond of the month, evaluated using the supplied
 372:      * calendar (which determines the time zone).
 373:      *
 374:      * @param calendar  the calendar (<code>null</code> not permitted).
 375:      *
 376:      * @return The first millisecond of the month.
 377:      *
 378:      * @throws NullPointerException if <code>calendar</code> is 
 379:      *     <code>null</code>.
 380:      */
 381:     public long getFirstMillisecond(Calendar calendar) {
 382:         calendar.set(this.year, this.month - 1, 1, 0, 0, 0);
 383:         calendar.set(Calendar.MILLISECOND, 0);
 384:         // in the following line, we'd rather call calendar.getTimeInMillis()
 385:         // to avoid object creation, but that isn't supported in Java 1.3.1
 386:         return calendar.getTime().getTime();
 387:     }
 388: 
 389:     /**
 390:      * Returns the last millisecond of the month, evaluated using the supplied
 391:      * calendar (which determines the time zone).
 392:      *
 393:      * @param calendar  the calendar (<code>null</code> not permitted).
 394:      *
 395:      * @return The last millisecond of the month.
 396:      *
 397:      * @throws NullPointerException if <code>calendar</code> is 
 398:      *     <code>null</code>.
 399:      */
 400:     public long getLastMillisecond(Calendar calendar) {
 401:         int eom = SerialDate.lastDayOfMonth(this.month, this.year);
 402:         calendar.set(this.year, this.month - 1, eom, 23, 59, 59);
 403:         calendar.set(Calendar.MILLISECOND, 999);
 404:         // in the following line, we'd rather call calendar.getTimeInMillis()
 405:         // to avoid object creation, but that isn't supported in Java 1.3.1
 406:         return calendar.getTime().getTime();
 407:     }
 408: 
 409:     /**
 410:      * Parses the string argument as a month.
 411:      * <P>
 412:      * This method is required to accept the format "YYYY-MM".  It will also
 413:      * accept "MM-YYYY". Anything else, at the moment, is a bonus.
 414:      *
 415:      * @param s  the string to parse.
 416:      *
 417:      * @return <code>null</code> if the string is not parseable, the month 
 418:      *         otherwise.
 419:      */
 420:     public static Month parseMonth(String s) {
 421: 
 422:         Month result = null;
 423:         if (s != null) {
 424: 
 425:             // trim whitespace from either end of the string
 426:             s = s.trim();
 427: 
 428:             int i = Month.findSeparator(s);
 429:             if (i != -1) {
 430:                 String s1 = s.substring(0, i).trim();
 431:                 String s2 = s.substring(i + 1, s.length()).trim();
 432: 
 433:                 Year year = Month.evaluateAsYear(s1);
 434:                 int month;
 435:                 if (year != null) {
 436:                     month = SerialDate.stringToMonthCode(s2);
 437:                     if (month == -1) {
 438:                         throw new TimePeriodFormatException(
 439:                             "Can't evaluate the month."
 440:                         );
 441:                     }
 442:                     result = new Month(month, year);
 443:                 }
 444:                 else {
 445:                     year = Month.evaluateAsYear(s2);
 446:                     if (year != null) {
 447:                         month = SerialDate.stringToMonthCode(s1);
 448:                         if (month == -1) {
 449:                             throw new TimePeriodFormatException(
 450:                                 "Can't evaluate the month."
 451:                             );
 452:                         }
 453:                         result = new Month(month, year);
 454:                     }
 455:                     else {
 456:                         throw new TimePeriodFormatException(
 457:                             "Can't evaluate the year."
 458:                         );
 459:                     }
 460:                 }
 461: 
 462:             }
 463:             else {
 464:                 throw new TimePeriodFormatException(
 465:                     "Could not find separator."
 466:                 );
 467:             }
 468: 
 469:         }
 470:         return result;
 471: 
 472:     }
 473: 
 474:     /**
 475:      * Finds the first occurrence of ' ', '-', ',' or '.'
 476:      *
 477:      * @param s  the string to parse.
 478:      * 
 479:      * @return <code>-1</code> if none of the characters where found, the
 480:      *      position of the first occurence otherwise.
 481:      */
 482:     private static int findSeparator(String s) {
 483: 
 484:         int result = s.indexOf('-');
 485:         if (result == -1) {
 486:             result = s.indexOf(',');
 487:         }
 488:         if (result == -1) {
 489:             result = s.indexOf(' ');
 490:         }
 491:         if (result == -1) {
 492:             result = s.indexOf('.');
 493:         }
 494:         return result;
 495:     }
 496: 
 497:     /**
 498:      * Creates a year from a string, or returns null (format exceptions 
 499:      * suppressed).
 500:      *
 501:      * @param s  the string to parse.
 502:      *
 503:      * @return <code>null</code> if the string is not parseable, the year 
 504:      *         otherwise.
 505:      */
 506:     private static Year evaluateAsYear(String s) {
 507: 
 508:         Year result = null;
 509:         try {
 510:             result = Year.parseYear(s);
 511:         }
 512:         catch (TimePeriodFormatException e) {
 513:             // suppress
 514:         }
 515:         return result;
 516: 
 517:     }
 518: 
 519: }