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