001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, 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-2007, by Object Refinery Limited and Contributors.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): Chris Boek;
034: *
035: * $Id: Month.java,v 1.7.2.6 2007/04/04 10:58:19 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: * ------------- JFREECHART 1.0.x ---------------------------------------------
057: * 05-Oct-2006 : Updated API docs (DG);
058: * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
059: * 04-Apr-2007 : Fixed bug in Month(Date, TimeZone) constructor (CB);
060: *
061: */
062:
063: package org.jfree.data.time;
064:
065: import java.io.Serializable;
066: import java.util.Calendar;
067: import java.util.Date;
068: import java.util.TimeZone;
069:
070: import org.jfree.date.MonthConstants;
071: import org.jfree.date.SerialDate;
072:
073: /**
074: * Represents a single month. This class is immutable, which is a requirement
075: * for all {@link RegularTimePeriod} subclasses.
076: */
077: public class Month extends RegularTimePeriod implements Serializable {
078:
079: /** For serialization. */
080: private static final long serialVersionUID = -5090216912548722570L;
081:
082: /** The month (1-12). */
083: private int month;
084:
085: /** The year in which the month falls. */
086: private int year;
087:
088: /** The first millisecond. */
089: private long firstMillisecond;
090:
091: /** The last millisecond. */
092: private long lastMillisecond;
093:
094: /**
095: * Constructs a new Month, based on the current system time.
096: */
097: public Month() {
098: this (new Date());
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, int year) {
108: if ((month < 1) || (month > 12)) {
109: throw new IllegalArgumentException(
110: "Month outside valid range.");
111: }
112: this .month = month;
113: this .year = year;
114: peg(Calendar.getInstance());
115: }
116:
117: /**
118: * Constructs a new month instance.
119: *
120: * @param month the month (in the range 1 to 12).
121: * @param year the year.
122: */
123: public Month(int month, Year year) {
124: if ((month < 1) || (month > 12)) {
125: throw new IllegalArgumentException(
126: "Month outside valid range.");
127: }
128: this .month = month;
129: this .year = year.getYear();
130: peg(Calendar.getInstance());
131: }
132:
133: /**
134: * Constructs a new <code>Month</code> instance, based on a date/time and
135: * the default time zone.
136: *
137: * @param time the date/time.
138: */
139: public Month(Date time) {
140: this (time, RegularTimePeriod.DEFAULT_TIME_ZONE);
141: }
142:
143: /**
144: * Constructs a new <code>Month</code> instance, based on a date/time and
145: * a time zone. The first and last millisecond values are initially
146: * pegged to the given time zone also.
147: *
148: * @param time the date/time.
149: * @param zone the time zone (<code>null</code> not permitted).
150: */
151: public Month(Date time, TimeZone zone) {
152: Calendar calendar = Calendar.getInstance(zone);
153: calendar.setTime(time);
154: this .month = calendar.get(Calendar.MONTH) + 1;
155: this .year = calendar.get(Calendar.YEAR);
156: peg(calendar);
157: }
158:
159: /**
160: * Returns the year in which the month falls.
161: *
162: * @return The year in which the month falls (as a Year object).
163: */
164: public Year getYear() {
165: return new Year(this .year);
166: }
167:
168: /**
169: * Returns the year in which the month falls.
170: *
171: * @return The year in which the month falls (as an int).
172: */
173: public int getYearValue() {
174: return this .year;
175: }
176:
177: /**
178: * Returns the month. Note that 1=JAN, 2=FEB, ...
179: *
180: * @return The month.
181: */
182: public int getMonth() {
183: return this .month;
184: }
185:
186: /**
187: * Returns the first millisecond of the month. This will be determined
188: * relative to the time zone specified in the constructor, or in the
189: * calendar instance passed in the most recent call to the
190: * {@link #peg(Calendar)} method.
191: *
192: * @return The first millisecond of the month.
193: *
194: * @see #getLastMillisecond()
195: */
196: public long getFirstMillisecond() {
197: return this .firstMillisecond;
198: }
199:
200: /**
201: * Returns the last millisecond of the month. This will be
202: * determined relative to the time zone specified in the constructor, or
203: * in the calendar instance passed in the most recent call to the
204: * {@link #peg(Calendar)} method.
205: *
206: * @return The last millisecond of the month.
207: *
208: * @see #getFirstMillisecond()
209: */
210: public long getLastMillisecond() {
211: return this .lastMillisecond;
212: }
213:
214: /**
215: * Recalculates the start date/time and end date/time for this time period
216: * relative to the supplied calendar (which incorporates a time zone).
217: *
218: * @param calendar the calendar (<code>null</code> not permitted).
219: *
220: * @since 1.0.3
221: */
222: public void peg(Calendar calendar) {
223: this .firstMillisecond = getFirstMillisecond(calendar);
224: this .lastMillisecond = getLastMillisecond(calendar);
225: }
226:
227: /**
228: * Returns the month preceding this one.
229: *
230: * @return The month preceding this one.
231: */
232: public RegularTimePeriod previous() {
233: Month result;
234: if (this .month != MonthConstants.JANUARY) {
235: result = new Month(this .month - 1, this .year);
236: } else {
237: if (this .year > 1900) {
238: result = new Month(MonthConstants.DECEMBER,
239: this .year - 1);
240: } else {
241: result = null;
242: }
243: }
244: return result;
245: }
246:
247: /**
248: * Returns the month following this one.
249: *
250: * @return The month following this one.
251: */
252: public RegularTimePeriod next() {
253: Month result;
254: if (this .month != MonthConstants.DECEMBER) {
255: result = new Month(this .month + 1, this .year);
256: } else {
257: if (this .year < 9999) {
258: result = new Month(MonthConstants.JANUARY,
259: this .year + 1);
260: } else {
261: result = null;
262: }
263: }
264: return result;
265: }
266:
267: /**
268: * Returns a serial index number for the month.
269: *
270: * @return The serial index number.
271: */
272: public long getSerialIndex() {
273: return this .year * 12L + this .month;
274: }
275:
276: /**
277: * Returns a string representing the month (e.g. "January 2002").
278: * <P>
279: * To do: look at internationalisation.
280: *
281: * @return A string representing the month.
282: */
283: public String toString() {
284: return SerialDate.monthCodeToString(this .month) + " "
285: + this .year;
286: }
287:
288: /**
289: * Tests the equality of this Month object to an arbitrary object.
290: * Returns true if the target is a Month instance representing the same
291: * month as this object. In all other cases, returns false.
292: *
293: * @param obj the object (<code>null</code> permitted).
294: *
295: * @return <code>true</code> if month and year of this and object are the
296: * same.
297: */
298: public boolean equals(Object obj) {
299:
300: if (obj != null) {
301: if (obj instanceof Month) {
302: Month target = (Month) obj;
303: return (this .month == target.getMonth() && (this .year == target
304: .getYearValue()));
305: } else {
306: return false;
307: }
308: } else {
309: return false;
310: }
311:
312: }
313:
314: /**
315: * Returns a hash code for this object instance. The approach described by
316: * Joshua Bloch in "Effective Java" has been used here:
317: * <p>
318: * <code>http://developer.java.sun.com/developer/Books/effectivejava
319: * /Chapter3.pdf</code>
320: *
321: * @return A hash code.
322: */
323: public int hashCode() {
324: int result = 17;
325: result = 37 * result + this .month;
326: result = 37 * result + this .year;
327: return result;
328: }
329:
330: /**
331: * Returns an integer indicating the order of this Month object relative to
332: * the specified
333: * object: negative == before, zero == same, positive == after.
334: *
335: * @param o1 the object to compare.
336: *
337: * @return negative == before, zero == same, positive == after.
338: */
339: public int compareTo(Object o1) {
340:
341: int result;
342:
343: // CASE 1 : Comparing to another Month object
344: // --------------------------------------------
345: if (o1 instanceof Month) {
346: Month m = (Month) o1;
347: result = this .year - m.getYearValue();
348: if (result == 0) {
349: result = this .month - m.getMonth();
350: }
351: }
352:
353: // CASE 2 : Comparing to another TimePeriod object
354: // -----------------------------------------------
355: else if (o1 instanceof RegularTimePeriod) {
356: // more difficult case - evaluate later...
357: result = 0;
358: }
359:
360: // CASE 3 : Comparing to a non-TimePeriod object
361: // ---------------------------------------------
362: else {
363: // consider time periods to be ordered after general objects
364: result = 1;
365: }
366:
367: return result;
368:
369: }
370:
371: /**
372: * Returns the first millisecond of the month, evaluated using the supplied
373: * calendar (which determines the time zone).
374: *
375: * @param calendar the calendar (<code>null</code> not permitted).
376: *
377: * @return The first millisecond of the month.
378: *
379: * @throws NullPointerException if <code>calendar</code> is
380: * <code>null</code>.
381: */
382: public long getFirstMillisecond(Calendar calendar) {
383: calendar.set(this .year, this .month - 1, 1, 0, 0, 0);
384: calendar.set(Calendar.MILLISECOND, 0);
385: // in the following line, we'd rather call calendar.getTimeInMillis()
386: // to avoid object creation, but that isn't supported in Java 1.3.1
387: return calendar.getTime().getTime();
388: }
389:
390: /**
391: * Returns the last millisecond of the month, evaluated using the supplied
392: * calendar (which determines the time zone).
393: *
394: * @param calendar the calendar (<code>null</code> not permitted).
395: *
396: * @return The last millisecond of the month.
397: *
398: * @throws NullPointerException if <code>calendar</code> is
399: * <code>null</code>.
400: */
401: public long getLastMillisecond(Calendar calendar) {
402: int eom = SerialDate.lastDayOfMonth(this .month, this .year);
403: calendar.set(this .year, this .month - 1, eom, 23, 59, 59);
404: calendar.set(Calendar.MILLISECOND, 999);
405: // in the following line, we'd rather call calendar.getTimeInMillis()
406: // to avoid object creation, but that isn't supported in Java 1.3.1
407: return calendar.getTime().getTime();
408: }
409:
410: /**
411: * Parses the string argument as a month.
412: * <P>
413: * This method is required to accept the format "YYYY-MM". It will also
414: * accept "MM-YYYY". Anything else, at the moment, is a bonus.
415: *
416: * @param s the string to parse.
417: *
418: * @return <code>null</code> if the string is not parseable, the month
419: * otherwise.
420: */
421: public static Month parseMonth(String s) {
422:
423: Month result = null;
424: if (s != null) {
425:
426: // trim whitespace from either end of the string
427: s = s.trim();
428:
429: int i = Month.findSeparator(s);
430: if (i != -1) {
431: String s1 = s.substring(0, i).trim();
432: String s2 = s.substring(i + 1, s.length()).trim();
433:
434: Year year = Month.evaluateAsYear(s1);
435: int month;
436: if (year != null) {
437: month = SerialDate.stringToMonthCode(s2);
438: if (month == -1) {
439: throw new TimePeriodFormatException(
440: "Can't evaluate the month.");
441: }
442: result = new Month(month, year);
443: } else {
444: year = Month.evaluateAsYear(s2);
445: if (year != null) {
446: month = SerialDate.stringToMonthCode(s1);
447: if (month == -1) {
448: throw new TimePeriodFormatException(
449: "Can't evaluate the month.");
450: }
451: result = new Month(month, year);
452: } else {
453: throw new TimePeriodFormatException(
454: "Can't evaluate the year.");
455: }
456: }
457:
458: } else {
459: throw new TimePeriodFormatException(
460: "Could not find separator.");
461: }
462:
463: }
464: return result;
465:
466: }
467:
468: /**
469: * Finds the first occurrence of ' ', '-', ',' or '.'
470: *
471: * @param s the string to parse.
472: *
473: * @return <code>-1</code> if none of the characters where found, the
474: * position of the first occurence otherwise.
475: */
476: private static int findSeparator(String s) {
477:
478: int result = s.indexOf('-');
479: if (result == -1) {
480: result = s.indexOf(',');
481: }
482: if (result == -1) {
483: result = s.indexOf(' ');
484: }
485: if (result == -1) {
486: result = s.indexOf('.');
487: }
488: return result;
489: }
490:
491: /**
492: * Creates a year from a string, or returns null (format exceptions
493: * suppressed).
494: *
495: * @param s the string to parse.
496: *
497: * @return <code>null</code> if the string is not parseable, the year
498: * otherwise.
499: */
500: private static Year evaluateAsYear(String s) {
501:
502: Year result = null;
503: try {
504: result = Year.parseYear(s);
505: } catch (TimePeriodFormatException e) {
506: // suppress
507: }
508: return result;
509:
510: }
511:
512: }
|