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     * Day.java
029     * --------
030     * (C) Copyright 2001-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 11-Oct-2001 : Version 1 (DG);
038     * 15-Nov-2001 : Updated Javadoc comments (DG);
039     * 04-Dec-2001 : Added static method to parse a string into a Day object (DG);
040     * 19-Dec-2001 : Added new constructor as suggested by Paul English (DG);
041     * 29-Jan-2002 : Changed getDay() method to getSerialDate() (DG);
042     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 
043     *               evaluate with reference to a particular time zone (DG);
044     * 19-Mar-2002 : Changed the API for the TimePeriod classes (DG);
045     * 29-May-2002 : Fixed bug in equals method (DG);
046     * 24-Jun-2002 : Removed unnecessary imports (DG);
047     * 10-Sep-2002 : Added getSerialIndex() method (DG);
048     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
049     * 10-Jan-2003 : Changed base class and method names (DG);
050     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 
051     *               Serializable (DG);
052     * 21-Oct-2003 : Added hashCode() method (DG);
053     * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
054     * 04-Nov-2004 : Reverted change of 30-Sep-2004, because it won't work for 
055     *               JDK 1.3 (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     * 
060     */
061    
062    package org.jfree.data.time;
063    
064    import java.io.Serializable;
065    import java.text.DateFormat;
066    import java.text.ParseException;
067    import java.text.SimpleDateFormat;
068    import java.util.Calendar;
069    import java.util.Date;
070    import java.util.TimeZone;
071    
072    import org.jfree.date.SerialDate;
073    
074    /**
075     * Represents a single day in the range 1-Jan-1900 to 31-Dec-9999.  This class 
076     * is immutable, which is a requirement for all {@link RegularTimePeriod} 
077     * subclasses.
078     */
079    public class Day extends RegularTimePeriod implements Serializable {
080    
081        /** For serialization. */
082        private static final long serialVersionUID = -7082667380758962755L;
083        
084        /** A standard date formatter. */
085        protected static final DateFormat DATE_FORMAT 
086            = new SimpleDateFormat("yyyy-MM-dd");
087    
088        /** A date formatter for the default locale. */
089        protected static final DateFormat
090            DATE_FORMAT_SHORT = DateFormat.getDateInstance(DateFormat.SHORT);
091    
092        /** A date formatter for the default locale. */
093        protected static final DateFormat
094            DATE_FORMAT_MEDIUM = DateFormat.getDateInstance(DateFormat.MEDIUM);
095    
096        /** A date formatter for the default locale. */
097        protected static final DateFormat
098            DATE_FORMAT_LONG = DateFormat.getDateInstance(DateFormat.LONG);
099    
100        /** The day (uses SerialDate for convenience). */
101        private SerialDate serialDate;
102    
103        /** The first millisecond. */
104        private long firstMillisecond;
105        
106        /** The last millisecond. */
107        private long lastMillisecond;
108    
109        /**
110         * Creates a new instance, derived from the system date/time (and assuming 
111         * the default timezone).
112         */
113        public Day() {
114            this(new Date());
115        }
116    
117        /**
118         * Constructs a new one day time period.
119         *
120         * @param day  the day-of-the-month.
121         * @param month  the month (1 to 12).
122         * @param year  the year (1900 <= year <= 9999).
123         */
124        public Day(int day, int month, int year) {
125            this.serialDate = SerialDate.createInstance(day, month, year);
126            peg(Calendar.getInstance());
127        }
128    
129        /**
130         * Constructs a new one day time period.
131         *
132         * @param serialDate  the day (<code>null</code> not permitted).
133         */
134        public Day(SerialDate serialDate) {
135            if (serialDate == null) {
136                throw new IllegalArgumentException("Null 'serialDate' argument.");
137            }
138            this.serialDate = serialDate;
139            peg(Calendar.getInstance());
140        }
141    
142        /**
143         * Constructs a new instance, based on a particular date/time and the 
144         * default time zone.
145         *
146         * @param time  the time (<code>null</code> not permitted).
147         */
148        public Day(Date time) {
149            // defer argument checking...
150            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
151        }
152    
153        /**
154         * Constructs a new instance, based on a particular date/time and time zone.
155         *
156         * @param time  the date/time.
157         * @param zone  the time zone.
158         */
159        public Day(Date time, TimeZone zone) {
160            if (time == null) {
161                throw new IllegalArgumentException("Null 'time' argument.");
162            }
163            if (zone == null) {
164                throw new IllegalArgumentException("Null 'zone' argument.");
165            }
166            Calendar calendar = Calendar.getInstance(zone);
167            calendar.setTime(time);
168            int d = calendar.get(Calendar.DAY_OF_MONTH);
169            int m = calendar.get(Calendar.MONTH) + 1;
170            int y = calendar.get(Calendar.YEAR);
171            this.serialDate = SerialDate.createInstance(d, m, y);
172            peg(calendar);
173        }
174    
175        /**
176         * Returns the day as a {@link SerialDate}.  Note: the reference that is 
177         * returned should be an instance of an immutable {@link SerialDate} 
178         * (otherwise the caller could use the reference to alter the state of 
179         * this <code>Day</code> instance, and <code>Day</code> is supposed
180         * to be immutable).
181         *
182         * @return The day as a {@link SerialDate}.
183         */
184        public SerialDate getSerialDate() {
185            return this.serialDate;
186        }
187    
188        /**
189         * Returns the year.
190         *
191         * @return The year.
192         */
193        public int getYear() {
194            return this.serialDate.getYYYY();
195        }
196    
197        /**
198         * Returns the month.
199         *
200         * @return The month.
201         */
202        public int getMonth() {
203            return this.serialDate.getMonth();
204        }
205    
206        /**
207         * Returns the day of the month.
208         *
209         * @return The day of the month.
210         */
211        public int getDayOfMonth() {
212            return this.serialDate.getDayOfMonth();
213        }
214    
215        /**
216         * Returns the first millisecond of the day.  This will be determined 
217         * relative to the time zone specified in the constructor, or in the 
218         * calendar instance passed in the most recent call to the 
219         * {@link #peg(Calendar)} method.
220         *
221         * @return The first millisecond of the day.
222         * 
223         * @see #getLastMillisecond()
224         */
225        public long getFirstMillisecond() {
226            return this.firstMillisecond;
227        }
228    
229        /**
230         * Returns the last millisecond of the day.  This will be 
231         * determined relative to the time zone specified in the constructor, or
232         * in the calendar instance passed in the most recent call to the 
233         * {@link #peg(Calendar)} method.
234         *
235         * @return The last millisecond of the day.
236         * 
237         * @see #getFirstMillisecond()
238         */
239        public long getLastMillisecond() {
240            return this.lastMillisecond;
241        }
242        
243        /** 
244         * Recalculates the start date/time and end date/time for this time period 
245         * relative to the supplied calendar (which incorporates a time zone).
246         * 
247         * @param calendar  the calendar (<code>null</code> not permitted).
248         * 
249         * @since 1.0.3
250         */
251        public void peg(Calendar calendar) {
252            this.firstMillisecond = getFirstMillisecond(calendar);
253            this.lastMillisecond = getLastMillisecond(calendar);
254        }
255    
256        /**
257         * Returns the day preceding this one.
258         *
259         * @return The day preceding this one.
260         */
261        public RegularTimePeriod previous() {
262    
263            Day result;
264            int serial = this.serialDate.toSerial();
265            if (serial > SerialDate.SERIAL_LOWER_BOUND) {
266                SerialDate yesterday = SerialDate.createInstance(serial - 1);
267                return new Day(yesterday);
268            }
269            else {
270                result = null;
271            }
272            return result;
273    
274        }
275    
276        /**
277         * Returns the day following this one, or <code>null</code> if some limit 
278         * has been reached.
279         *
280         * @return The day following this one, or <code>null</code> if some limit 
281         *         has been reached.
282         */
283        public RegularTimePeriod next() {
284    
285            Day result;
286            int serial = this.serialDate.toSerial();
287            if (serial < SerialDate.SERIAL_UPPER_BOUND) {
288                SerialDate tomorrow = SerialDate.createInstance(serial + 1);
289                return new Day(tomorrow);
290            }
291            else {
292                result = null;
293            }
294            return result;
295    
296        }
297    
298        /**
299         * Returns a serial index number for the day.
300         *
301         * @return The serial index number.
302         */
303        public long getSerialIndex() {
304            return this.serialDate.toSerial();
305        }
306    
307        /**
308         * Returns the first millisecond of the day, evaluated using the supplied
309         * calendar (which determines the time zone).
310         *
311         * @param calendar  calendar to use (<code>null</code> not permitted).
312         *
313         * @return The start of the day as milliseconds since 01-01-1970.
314         *
315         * @throws NullPointerException if <code>calendar</code> is 
316         *     <code>null</code>.
317         */
318        public long getFirstMillisecond(Calendar calendar) {
319            int year = this.serialDate.getYYYY();
320            int month = this.serialDate.getMonth();
321            int day = this.serialDate.getDayOfMonth();
322            calendar.clear();
323            calendar.set(year, month - 1, day, 0, 0, 0);
324            calendar.set(Calendar.MILLISECOND, 0);
325            //return calendar.getTimeInMillis();  // this won't work for JDK 1.3
326            return calendar.getTime().getTime();
327        }
328    
329        /**
330         * Returns the last millisecond of the day, evaluated using the supplied
331         * calendar (which determines the time zone).
332         *
333         * @param calendar  calendar to use (<code>null</code> not permitted).
334         *
335         * @return The end of the day as milliseconds since 01-01-1970.
336         *
337         * @throws NullPointerException if <code>calendar</code> is 
338         *     <code>null</code>.
339         */
340        public long getLastMillisecond(Calendar calendar) {
341            int year = this.serialDate.getYYYY();
342            int month = this.serialDate.getMonth();
343            int day = this.serialDate.getDayOfMonth();
344            calendar.clear();
345            calendar.set(year, month - 1, day, 23, 59, 59);
346            calendar.set(Calendar.MILLISECOND, 999);
347            //return calendar.getTimeInMillis();  // this won't work for JDK 1.3
348            return calendar.getTime().getTime();
349        }
350    
351        /**
352         * Tests the equality of this Day object to an arbitrary object.  Returns
353         * true if the target is a Day instance or a SerialDate instance
354         * representing the same day as this object. In all other cases,
355         * returns false.
356         *
357         * @param obj  the object (<code>null</code> permitted).
358         *
359         * @return A flag indicating whether or not an object is equal to this day.
360         */
361        public boolean equals(Object obj) {
362            
363            if (obj == this) {
364                return true;
365            }
366            if (!(obj instanceof Day)) {
367                return false;
368            }
369            Day that = (Day) obj;
370            if (!this.serialDate.equals(that.getSerialDate())) {
371                return false;
372            }
373            return true;
374            
375        }
376    
377        /**
378         * Returns a hash code for this object instance.  The approach described by
379         * Joshua Bloch in "Effective Java" has been used here:
380         * <p>
381         * <code>http://developer.java.sun.com/developer/Books/effectivejava
382         * /Chapter3.pdf</code>
383         * 
384         * @return A hash code.
385         */
386        public int hashCode() {
387            return this.serialDate.hashCode();
388        }
389    
390        /**
391         * Returns an integer indicating the order of this Day object relative to
392         * the specified object:
393         *
394         * negative == before, zero == same, positive == after.
395         *
396         * @param o1  the object to compare.
397         *
398         * @return negative == before, zero == same, positive == after.
399         */
400        public int compareTo(Object o1) {
401    
402            int result;
403    
404            // CASE 1 : Comparing to another Day object
405            // ----------------------------------------
406            if (o1 instanceof Day) {
407                Day d = (Day) o1;
408                result = -d.getSerialDate().compare(this.serialDate);
409            }
410    
411            // CASE 2 : Comparing to another TimePeriod object
412            // -----------------------------------------------
413            else if (o1 instanceof RegularTimePeriod) {
414                // more difficult case - evaluate later...
415                result = 0;
416            }
417    
418            // CASE 3 : Comparing to a non-TimePeriod object
419            // ---------------------------------------------
420            else {
421                // consider time periods to be ordered after general objects
422                result = 1;
423            }
424    
425            return result;
426    
427        }
428    
429        /**
430         * Returns a string representing the day.
431         *
432         * @return A string representing the day.
433         */
434        public String toString() {
435            return this.serialDate.toString();
436        }
437    
438        /**
439         * Parses the string argument as a day.
440         * <P>
441         * This method is required to recognise YYYY-MM-DD as a valid format.
442         * Anything else, for now, is a bonus.
443         *
444         * @param s  the date string to parse.
445         *
446         * @return <code>null</code> if the string does not contain any parseable
447         *      string, the day otherwise.
448         */
449        public static Day parseDay(String s) {
450    
451            try {
452                return new Day (Day.DATE_FORMAT.parse(s));
453            }
454            catch (ParseException e1) {
455                try {
456                    return new Day (Day.DATE_FORMAT_SHORT.parse(s));
457                }
458                catch (ParseException e2) {
459                  // ignore
460                }
461            }
462            return null;
463    
464        }
465    
466    }