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
062package org.jfree.data.time;
063
064import java.io.Serializable;
065import java.text.DateFormat;
066import java.text.ParseException;
067import java.text.SimpleDateFormat;
068import java.util.Calendar;
069import java.util.Date;
070import java.util.TimeZone;
071
072import 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 */
079public 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}