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 }