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