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