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-2005, 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-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: Month.java,v 1.7.2.2 2005/11/02 13:20:29 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 11-Oct-2001 : Version 1 (DG);
  40:  * 14-Nov-2001 : Added method to get year as primitive (DG);
  41:  *               Override for toString() method (DG);
  42:  * 18-Dec-2001 : Changed order of parameters in constructor (DG);
  43:  * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
  44:  * 29-Jan-2002 : Worked on the parseMonth() method (DG);
  45:  * 14-Feb-2002 : Fixed bugs in the Month constructors (DG);
  46:  * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 
  47:  *               evaluate with reference to a particular time zone (DG);
  48:  * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
  49:  * 10-Sep-2002 : Added getSerialIndex() method (DG);
  50:  * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  51:  * 10-Jan-2003 : Changed base class and method names (DG);
  52:  * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 
  53:  *               Serializable (DG);
  54:  * 21-Oct-2003 : Added hashCode() method (DG);
  55:  * 01-Nov-2005 : Fixed bug 1345383 (argument check in constructor) (DG);
  56:  *
  57:  */
  58: 
  59: package org.jfree.data.time;
  60: 
  61: import java.io.Serializable;
  62: import java.util.Calendar;
  63: import java.util.Date;
  64: import java.util.TimeZone;
  65: 
  66: import org.jfree.date.MonthConstants;
  67: import org.jfree.date.SerialDate;
  68: 
  69: /**
  70:  * Represents a single month.  This class is immutable, which is a requirement
  71:  * for all {@link RegularTimePeriod} subclasses.
  72:  */
  73: public class Month extends RegularTimePeriod implements Serializable {
  74: 
  75:     /** For serialization. */
  76:     private static final long serialVersionUID = -5090216912548722570L;
  77: 
  78:     /** The month (1-12). */
  79:     private int month;
  80: 
  81:     /** The year in which the month falls. */
  82:     private Year year;
  83: 
  84:     /**
  85:      * Constructs a new Month, based on the current system time.
  86:      */
  87:     public Month() {
  88:         this(new Date());
  89:     }
  90: 
  91:     /**
  92:      * Constructs a new month instance.
  93:      *
  94:      * @param month  the month (in the range 1 to 12).
  95:      * @param year  the year.
  96:      */
  97:     public Month(int month, int year) {
  98:         this(month, new Year(year));
  99:     }
 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: }