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     * Year.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: Year.java,v 1.9.2.2 2006/08/24 10:01:52 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 11-Oct-2001 : Version 1 (DG);
040     * 14-Nov-2001 : Override for toString() method (DG);
041     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
042     * 29-Jan-2002 : Worked on parseYear() method (DG);
043     * 14-Feb-2002 : Fixed bug in Year(Date) constructor (DG);
044     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 
045     *               evaluate with reference to a particular time zone (DG);
046     * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
047     * 10-Sep-2002 : Added getSerialIndex() method (DG);
048     * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
049     * 10-Jan-2003 : Changed base class and method names (DG);
050     * 05-Mar-2003 : Fixed bug in getFirstMillisecond() picked up in JUnit 
051     *               tests (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     * 
056     */
057    
058    package org.jfree.data.time;
059    
060    import java.io.Serializable;
061    import java.util.Calendar;
062    import java.util.Date;
063    import java.util.TimeZone;
064    
065    import org.jfree.date.MonthConstants;
066    import org.jfree.date.SerialDate;
067    
068    /**
069     * Represents a year in the range 1900 to 9999.  This class is immutable, which
070     * is a requirement for all {@link RegularTimePeriod} subclasses.
071     */
072    public class Year extends RegularTimePeriod implements Serializable {
073    
074        /** For serialization. */
075        private static final long serialVersionUID = -7659990929736074836L;
076        
077        /** The year. */
078        private int year;
079    
080        /**
081         * Creates a new <code>Year</code>, based on the current system date/time.
082         */
083        public Year() {
084            this(new Date());
085        }
086    
087        /**
088         * Creates a time period representing a single year.
089         *
090         * @param year  the year.
091         */
092        public Year(int year) {
093    
094            // check arguments...
095            if ((year < SerialDate.MINIMUM_YEAR_SUPPORTED)
096                || (year > SerialDate.MAXIMUM_YEAR_SUPPORTED)) {
097    
098                throw new IllegalArgumentException(
099                    "Year constructor: year (" + year + ") outside valid range.");
100            }
101    
102            // initialise...
103            this.year = year;
104    
105        }
106    
107        /**
108         * Creates a new <code>Year</code>, based on a particular instant in time, 
109         * using the default time zone.
110         *
111         * @param time  the time (<code>null</code> not permitted).
112         */
113        public Year(Date time) {
114            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
115        }
116    
117        /**
118         * Constructs a year, based on a particular instant in time and a time zone.
119         *
120         * @param time  the time.
121         * @param zone  the time zone.
122         */
123        public Year(Date time, TimeZone zone) {
124    
125            Calendar calendar = Calendar.getInstance(zone);
126            calendar.setTime(time);
127            this.year = calendar.get(Calendar.YEAR);
128    
129        }
130    
131        /**
132         * Returns the year.
133         *
134         * @return The year.
135         */
136        public int getYear() {
137            return this.year;
138        }
139    
140        /**
141         * Returns the year preceding this one.
142         *
143         * @return The year preceding this one (or <code>null</code> if the 
144         *         current year is 1900).
145         */
146        public RegularTimePeriod previous() {
147            if (this.year > SerialDate.MINIMUM_YEAR_SUPPORTED) {
148                return new Year(this.year - 1);
149            }
150            else {
151                return null;
152            }
153        }
154    
155        /**
156         * Returns the year following this one.
157         *
158         * @return The year following this one (or <code>null</code> if the current
159         *         year is 9999).
160         */
161        public RegularTimePeriod next() {
162            if (this.year < SerialDate.MAXIMUM_YEAR_SUPPORTED) {
163                return new Year(this.year + 1);
164            }
165            else {
166                return null;
167            }
168        }
169    
170        /**
171         * Returns a serial index number for the year.
172         * <P>
173         * The implementation simply returns the year number (e.g. 2002).
174         *
175         * @return The serial index number.
176         */
177        public long getSerialIndex() {
178            return this.year;
179        }
180    
181        /**
182         * Returns the first millisecond of the year, evaluated using the supplied
183         * calendar (which determines the time zone).
184         *
185         * @param calendar  the calendar.
186         *
187         * @return The first millisecond of the year.
188         */
189        public long getFirstMillisecond(Calendar calendar) {
190            Day jan1 = new Day(1, MonthConstants.JANUARY, this.year);
191            return jan1.getFirstMillisecond(calendar);
192        }
193    
194        /**
195         * Returns the last millisecond of the year, evaluated using the supplied
196         * calendar (which determines the time zone).
197         *
198         * @param calendar  the calendar.
199         *
200         * @return The last millisecond of the year.
201         */
202        public long getLastMillisecond(Calendar calendar) {
203            Day dec31 = new Day(31, MonthConstants.DECEMBER, this.year);
204            return dec31.getLastMillisecond(calendar);
205        }
206    
207        /**
208         * Tests the equality of this <code>Year</code> object to an arbitrary 
209         * object.  Returns <code>true</code> if the target is a <code>Year</code>
210         * instance representing the same year as this object.  In all other cases,
211         * returns <code>false</code>.
212         *
213         * @param object  the object.
214         *
215         * @return <code>true</code> if the year of this and the object are the 
216         *         same.
217         */
218        public boolean equals(Object object) {
219            if (object != null) {
220                if (object instanceof Year) {
221                    Year target = (Year) object;
222                    return (this.year == target.getYear());
223                }
224                else {
225                    return false;
226                }
227            }
228            else {
229                return false;
230            }
231        }
232        
233        /**
234         * Returns a hash code for this object instance.  The approach described by
235         * Joshua Bloch in "Effective Java" has been used here:
236         * <p>
237         * <code>http://developer.java.sun.com/developer/Books/effectivejava
238         *     /Chapter3.pdf</code>
239         * 
240         * @return A hash code.
241         */
242        public int hashCode() {
243            int result = 17;
244            int c = this.year;
245            result = 37 * result + c;
246            return result;
247        }
248    
249        /**
250         * Returns an integer indicating the order of this <code>Year</code> object
251         * relative to the specified object:
252         *
253         * negative == before, zero == same, positive == after.
254         *
255         * @param o1  the object to compare.
256         *
257         * @return negative == before, zero == same, positive == after.
258         */
259        public int compareTo(Object o1) {
260    
261            int result;
262    
263            // CASE 1 : Comparing to another Year object
264            // -----------------------------------------
265            if (o1 instanceof Year) {
266                Year y = (Year) o1;
267                result = this.year - y.getYear();
268            }
269    
270            // CASE 2 : Comparing to another TimePeriod object
271            // -----------------------------------------------
272            else if (o1 instanceof RegularTimePeriod) {
273                // more difficult case - evaluate later...
274                result = 0;
275            }
276    
277            // CASE 3 : Comparing to a non-TimePeriod object
278            // ---------------------------------------------
279            else {
280                // consider time periods to be ordered after general objects
281                result = 1;
282            }
283    
284            return result;
285    
286        }
287    
288        /**
289         * Returns a string representing the year..
290         *
291         * @return A string representing the year.
292         */
293        public String toString() {
294            return Integer.toString(this.year);
295        }
296    
297        /**
298         * Parses the string argument as a year.
299         * <P>
300         * The string format is YYYY.
301         *
302         * @param s  a string representing the year.
303         *
304         * @return <code>null</code> if the string is not parseable, the year 
305         *         otherwise.
306         */
307        public static Year parseYear(String s) {
308    
309            // parse the string...
310            int y;
311            try {
312                y = Integer.parseInt(s.trim());
313            }
314            catch (NumberFormatException e) {
315                throw new TimePeriodFormatException("Cannot parse string.");
316            }
317    
318            // create the year...
319            try {
320                return new Year(y);
321            }
322            catch (IllegalArgumentException e) {
323                throw new TimePeriodFormatException("Year outside valid range.");
324            }
325        }
326    
327    }