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
063package org.jfree.data.time;
064
065import java.io.Serializable;
066import java.util.Calendar;
067import java.util.Date;
068import 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 */
074public 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}