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