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 }