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    }