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