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