    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    }