    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     * Hour.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     * 18-Dec-2001 : Changed order of parameters in constructor (DG);
    039     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
    040     * 14-Feb-2002 : Fixed bug in Hour(Date) constructor (DG);
    041     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to
    042     *               evaluate with reference to a particular time zone (DG);
    043     * 15-Mar-2002 : Changed API (DG);
    044     * 16-Apr-2002 : Fixed small time zone bug in constructor (DG);
    045     * 10-Sep-2002 : Added getSerialIndex() method (DG);
    046     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    047     * 10-Jan-2003 : Changed base class and method names (DG);
    048     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented
    049     *               Serializable (DG);
    050     * 21-Oct-2003 : Added hashCode() method, and new constructor for
    051     *               convenience (DG);
    052     * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
    053     * 04-Nov-2004 : Reverted change of 30-Sep-2004, because it won't work for
    054     *               JDK 1.3 (DG);
    055     * ------------- JFREECHART 1.0.x ---------------------------------------------
    056     * 05-Oct-2006 : Updated API docs (DG);
    057     * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
    058     * 04-Apr-2007 : In Hour(Date, TimeZone), peg milliseconds using specified
    059     *               time zone (DG);
    060     *
    061     */
    062
    063    package org.jfree.data.time;
    064
    065    import java.io.Serializable;
    066    import java.util.Calendar;
    067    import java.util.Date;
    068    import java.util.TimeZone;
    069
    070    /**
    071     * Represents an hour in a specific day.  This class is immutable, which is a
    072     * requirement for all {@link RegularTimePeriod} subclasses.
    073     */
    074    public class Hour extends RegularTimePeriod implements Serializable {
    075
    076        /** For serialization. */
    077        private static final long serialVersionUID = -835471579831937652L;
    078
    079        /** Useful constant for the first hour in the day. */
    080        public static final int FIRST_HOUR_IN_DAY = 0;
    081
    082        /** Useful constant for the last hour in the day. */
    083        public static final int LAST_HOUR_IN_DAY = 23;
    084
    085        /** The day. */
    086        private Day day;
    087
    088        /** The hour. */
    089        private byte hour;
    090
    091        /** The first millisecond. */
    092        private long firstMillisecond;
    093
    094        /** The last millisecond. */
    095        private long lastMillisecond;
    096
    097        /**
    098         * Constructs a new Hour, based on the system date/time.
    099         */
    100        public Hour() {
    101            this(new Date());
    102        }
    103
    104        /**
    105         * Constructs a new Hour.
    106         *
    107         * @param hour  the hour (in the range 0 to 23).
    108         * @param day  the day (<code>null</code> not permitted).
    109         */
    110        public Hour(int hour, Day day) {
    111            if (day == null) {
    112                throw new IllegalArgumentException("Null 'day' argument.");
    113            }
    114            this.hour = (byte) hour;
    115            this.day = day;
    116            peg(Calendar.getInstance());
    117        }
    118
    119        /**
    120         * Creates a new hour.
    121         *
    122         * @param hour  the hour (0-23).
    123         * @param day  the day (1-31).
    124         * @param month  the month (1-12).
    125         * @param year  the year (1900-9999).
    126         */
    127        public Hour(int hour, int day, int month, int year) {
    128            this(hour, new Day(day, month, year));
    129        }
    130
    131        /**
    132         * Constructs a new Hour, based on the supplied date/time.
    133         *
    134         * @param time  the date-time (<code>null</code> not permitted).
    135         */
    136        public Hour(Date time) {
    137            // defer argument checking...
    138            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
    139        }
    140
    141        /**
    142         * Constructs a new Hour, based on the supplied date/time evaluated in the
    143         * specified time zone.
    144         *
    145         * @param time  the date-time (<code>null</code> not permitted).
    146         * @param zone  the time zone (<code>null</code> not permitted).
    147         */
    148        public Hour(Date time, TimeZone zone) {
    149            if (time == null) {
    150                throw new IllegalArgumentException("Null 'time' argument.");
    151            }
    152            if (zone == null) {
    153                throw new IllegalArgumentException("Null 'zone' argument.");
    154            }
    155            Calendar calendar = Calendar.getInstance(zone);
    156            calendar.setTime(time);
    157            this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
    158            this.day = new Day(time, zone);
    159            peg(calendar);
    160        }
    161
    162        /**
    163         * Returns the hour.
    164         *
    165         * @return The hour (0 <= hour <= 23).
    166         */
    167        public int getHour() {
    168            return this.hour;
    169        }
    170
    171        /**
    172         * Returns the day in which this hour falls.
    173         *
    174         * @return The day.
    175         */
    176        public Day getDay() {
    177            return this.day;
    178        }
    179
    180        /**
    181         * Returns the year in which this hour falls.
    182         *
    183         * @return The year.
    184         */
    185        public int getYear() {
    186            return this.day.getYear();
    187        }
    188
    189        /**
    190         * Returns the month in which this hour falls.
    191         *
    192         * @return The month.
    193         */
    194        public int getMonth() {
    195            return this.day.getMonth();
    196        }
    197
    198        /**
    199         * Returns the day-of-the-month in which this hour falls.
    200         *
    201         * @return The day-of-the-month.
    202         */
    203        public int getDayOfMonth() {
    204            return this.day.getDayOfMonth();
    205        }
    206
    207        /**
    208         * Returns the first millisecond of the hour.  This will be determined
    209         * relative to the time zone specified in the constructor, or in the
    210         * calendar instance passed in the most recent call to the
    211         * {@link #peg(Calendar)} method.
    212         *
    213         * @return The first millisecond of the hour.
    214         *
    215         * @see #getLastMillisecond()
    216         */
    217        public long getFirstMillisecond() {
    218            return this.firstMillisecond;
    219        }
    220
    221        /**
    222         * Returns the last millisecond of the hour.  This will be
    223         * determined relative to the time zone specified in the constructor, or
    224         * in the calendar instance passed in the most recent call to the
    225         * {@link #peg(Calendar)} method.
    226         *
    227         * @return The last millisecond of the hour.
    228         *
    229         * @see #getFirstMillisecond()
    230         */
    231        public long getLastMillisecond() {
    232            return this.lastMillisecond;
    233        }
    234
    235        /**
    236         * Recalculates the start date/time and end date/time for this time period
    237         * relative to the supplied calendar (which incorporates a time zone).
    238         *
    239         * @param calendar  the calendar (<code>null</code> not permitted).
    240         *
    241         * @since 1.0.3
    242         */
    243        public void peg(Calendar calendar) {
    244            this.firstMillisecond = getFirstMillisecond(calendar);
    245            this.lastMillisecond = getLastMillisecond(calendar);
    246        }
    247
    248        /**
    249         * Returns the hour preceding this one.
    250         *
    251         * @return The hour preceding this one.
    252         */
    253        public RegularTimePeriod previous() {
    254
    255            Hour result;
    256            if (this.hour != FIRST_HOUR_IN_DAY) {
    257                result = new Hour(this.hour - 1, this.day);
    258            }
    259            else { // we are at the first hour in the day...
    260                Day prevDay = (Day) this.day.previous();
    261                if (prevDay != null) {
    262                    result = new Hour(LAST_HOUR_IN_DAY, prevDay);
    263                }
    264                else {
    265                    result = null;
    266                }
    267            }
    268            return result;
    269
    270        }
    271
    272        /**
    273         * Returns the hour following this one.
    274         *
    275         * @return The hour following this one.
    276         */
    277        public RegularTimePeriod next() {
    278
    279            Hour result;
    280            if (this.hour != LAST_HOUR_IN_DAY) {
    281                result = new Hour(this.hour + 1, this.day);
    282            }
    283            else { // we are at the last hour in the day...
    284                Day nextDay = (Day) this.day.next();
    285                if (nextDay != null) {
    286                    result = new Hour(FIRST_HOUR_IN_DAY, nextDay);
    287                }
    288                else {
    289                    result = null;
    290                }
    291            }
    292            return result;
    293
    294        }
    295
    296        /**
    297         * Returns a serial index number for the hour.
    298         *
    299         * @return The serial index number.
    300         */
    301        public long getSerialIndex() {
    302            return this.day.getSerialIndex() * 24L + this.hour;
    303        }
    304
    305        /**
    306         * Returns the first millisecond of the hour.
    307         *
    308         * @param calendar  the calendar/timezone (<code>null</code> not permitted).
    309         *
    310         * @return The first millisecond.
    311         *
    312         * @throws NullPointerException if <code>calendar</code> is
    313         *     <code>null</code>.
    314         */
    315        public long getFirstMillisecond(Calendar calendar) {
    316            int year = this.day.getYear();
    317            int month = this.day.getMonth() - 1;
    318            int dom = this.day.getDayOfMonth();
    319            calendar.set(year, month, dom, this.hour, 0, 0);
    320            calendar.set(Calendar.MILLISECOND, 0);
    321            //return calendar.getTimeInMillis();  // this won't work for JDK 1.3
    322            return calendar.getTime().getTime();
    323        }
    324
    325        /**
    326         * Returns the last millisecond of the hour.
    327         *
    328         * @param calendar  the calendar/timezone (<code>null</code> not permitted).
    329         *
    330         * @return The last millisecond.
    331         *
    332         * @throws NullPointerException if <code>calendar</code> is
    333         *     <code>null</code>.
    334         */
    335        public long getLastMillisecond(Calendar calendar) {
    336            int year = this.day.getYear();
    337            int month = this.day.getMonth() - 1;
    338            int dom = this.day.getDayOfMonth();
    339            calendar.set(year, month, dom, this.hour, 59, 59);
    340            calendar.set(Calendar.MILLISECOND, 999);
    341            //return calendar.getTimeInMillis();  // this won't work for JDK 1.3
    342            return calendar.getTime().getTime();
    343        }
    344
    345        /**
    346         * Tests the equality of this object against an arbitrary Object.
    347         * <P>
    348         * This method will return true ONLY if the object is an Hour object
    349         * representing the same hour as this instance.
    350         *
    351         * @param obj  the object to compare (<code>null</code> permitted).
    352         *
    353         * @return <code>true</code> if the hour and day value of the object
    354         *      is the same as this.
    355         */
    356        public boolean equals(Object obj) {
    357            if (obj == this) {
    358                return true;
    359            }
    360            if (!(obj instanceof Hour)) {
    361                return false;
    362            }
    363            Hour that = (Hour) obj;
    364            if (this.hour != that.hour) {
    365                return false;
    366            }
    367            if (!this.day.equals(that.day)) {
    368                return false;
    369            }
    370            return true;
    371        }
    372
    373        /**
    374         * Returns a hash code for this object instance.  The approach described by
    375         * Joshua Bloch in "Effective Java" has been used here:
    376         * <p>
    377         * <code>http://developer.java.sun.com/developer/Books/effectivejava
    378         * /Chapter3.pdf</code>
    379         *
    380         * @return A hash code.
    381         */
    382        public int hashCode() {
    383            int result = 17;
    384            result = 37 * result + this.hour;
    385            result = 37 * result + this.day.hashCode();
    386            return result;
    387        }
    388
    389        /**
    390         * Returns an integer indicating the order of this Hour object relative to
    391         * the specified object:
    392         *
    393         * negative == before, zero == same, positive == after.
    394         *
    395         * @param o1  the object to compare.
    396         *
    397         * @return negative == before, zero == same, positive == after.
    398         */
    399        public int compareTo(Object o1) {
    400
    401            int result;
    402
    403            // CASE 1 : Comparing to another Hour object
    404            // -----------------------------------------
    405            if (o1 instanceof Hour) {
    406                Hour h = (Hour) o1;
    407                result = getDay().compareTo(h.getDay());
    408                if (result == 0) {
    409                    result = this.hour - h.getHour();
    410                }
    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         * Creates an Hour instance by parsing a string.  The string is assumed to
    433         * be in the format "YYYY-MM-DD HH", perhaps with leading or trailing
    434         * whitespace.
    435         *
    436         * @param s  the hour string to parse.
    437         *
    438         * @return <code>null</code> if the string is not parseable, the hour
    439         *         otherwise.
    440         */
    441        public static Hour parseHour(String s) {
    442
    443            Hour result = null;
    444            s = s.trim();
    445
    446            String daystr = s.substring(0, Math.min(10, s.length()));
    447            Day day = Day.parseDay(daystr);
    448            if (day != null) {
    449                String hourstr = s.substring(
    450                    Math.min(daystr.length() + 1, s.length()), s.length()
    451                );
    452                hourstr = hourstr.trim();
    453                int hour = Integer.parseInt(hourstr);
    454                // if the hour is 0 - 23 then create an hour
    455                if ((hour >= FIRST_HOUR_IN_DAY) && (hour <= LAST_HOUR_IN_DAY)) {
    456                    result = new Hour(hour, day);
    457                }
    458            }
    459
    460            return result;
    461
    462        }
    463
    464    }