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 }