001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, 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 * 02-Mar-2009 : Added new constructor with Locale (DG); 062 * 063 */ 064 065 package org.jfree.data.time; 066 067 import java.io.Serializable; 068 import java.util.Calendar; 069 import java.util.Date; 070 import java.util.Locale; 071 import java.util.TimeZone; 072 073 /** 074 * Represents an hour in a specific day. This class is immutable, which is a 075 * requirement for all {@link RegularTimePeriod} subclasses. 076 */ 077 public class Hour extends RegularTimePeriod implements Serializable { 078 079 /** For serialization. */ 080 private static final long serialVersionUID = -835471579831937652L; 081 082 /** Useful constant for the first hour in the day. */ 083 public static final int FIRST_HOUR_IN_DAY = 0; 084 085 /** Useful constant for the last hour in the day. */ 086 public static final int LAST_HOUR_IN_DAY = 23; 087 088 /** The day. */ 089 private Day day; 090 091 /** The hour. */ 092 private byte hour; 093 094 /** The first millisecond. */ 095 private long firstMillisecond; 096 097 /** The last millisecond. */ 098 private long lastMillisecond; 099 100 /** 101 * Constructs a new Hour, based on the system date/time. 102 */ 103 public Hour() { 104 this(new Date()); 105 } 106 107 /** 108 * Constructs a new Hour. 109 * 110 * @param hour the hour (in the range 0 to 23). 111 * @param day the day (<code>null</code> not permitted). 112 */ 113 public Hour(int hour, Day day) { 114 if (day == null) { 115 throw new IllegalArgumentException("Null 'day' argument."); 116 } 117 this.hour = (byte) hour; 118 this.day = day; 119 peg(Calendar.getInstance()); 120 } 121 122 /** 123 * Creates a new hour. 124 * 125 * @param hour the hour (0-23). 126 * @param day the day (1-31). 127 * @param month the month (1-12). 128 * @param year the year (1900-9999). 129 */ 130 public Hour(int hour, int day, int month, int year) { 131 this(hour, new Day(day, month, year)); 132 } 133 134 /** 135 * Constructs a new instance, based on the supplied date/time and 136 * the default time zone. 137 * 138 * @param time the date-time (<code>null</code> not permitted). 139 * 140 * @see #Hour(Date, TimeZone) 141 */ 142 public Hour(Date time) { 143 // defer argument checking... 144 this(time, TimeZone.getDefault(), Locale.getDefault()); 145 } 146 147 /** 148 * Constructs a new instance, based on the supplied date/time evaluated 149 * in the specified time zone. 150 * 151 * @param time the date-time (<code>null</code> not permitted). 152 * @param zone the time zone (<code>null</code> not permitted). 153 * 154 * @deprecated As of 1.0.13, use the constructor that specifies the locale 155 * also. 156 */ 157 public Hour(Date time, TimeZone zone) { 158 this(time, zone, Locale.getDefault()); 159 } 160 161 /** 162 * Constructs a new instance, based on the supplied date/time evaluated 163 * in the specified time zone. 164 * 165 * @param time the date-time (<code>null</code> not permitted). 166 * @param zone the time zone (<code>null</code> not permitted). 167 * @param locale the locale (<code>null</code> not permitted). 168 * 169 * @since 1.0.13 170 */ 171 public Hour(Date time, TimeZone zone, Locale locale) { 172 if (time == null) { 173 throw new IllegalArgumentException("Null 'time' argument."); 174 } 175 if (zone == null) { 176 throw new IllegalArgumentException("Null 'zone' argument."); 177 } 178 if (locale == null) { 179 throw new IllegalArgumentException("Null 'locale' argument."); 180 } 181 Calendar calendar = Calendar.getInstance(zone, locale); 182 calendar.setTime(time); 183 this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY); 184 this.day = new Day(time, zone, locale); 185 peg(calendar); 186 } 187 188 /** 189 * Returns the hour. 190 * 191 * @return The hour (0 <= hour <= 23). 192 */ 193 public int getHour() { 194 return this.hour; 195 } 196 197 /** 198 * Returns the day in which this hour falls. 199 * 200 * @return The day. 201 */ 202 public Day getDay() { 203 return this.day; 204 } 205 206 /** 207 * Returns the year in which this hour falls. 208 * 209 * @return The year. 210 */ 211 public int getYear() { 212 return this.day.getYear(); 213 } 214 215 /** 216 * Returns the month in which this hour falls. 217 * 218 * @return The month. 219 */ 220 public int getMonth() { 221 return this.day.getMonth(); 222 } 223 224 /** 225 * Returns the day-of-the-month in which this hour falls. 226 * 227 * @return The day-of-the-month. 228 */ 229 public int getDayOfMonth() { 230 return this.day.getDayOfMonth(); 231 } 232 233 /** 234 * Returns the first millisecond of the hour. This will be determined 235 * relative to the time zone specified in the constructor, or in the 236 * calendar instance passed in the most recent call to the 237 * {@link #peg(Calendar)} method. 238 * 239 * @return The first millisecond of the hour. 240 * 241 * @see #getLastMillisecond() 242 */ 243 public long getFirstMillisecond() { 244 return this.firstMillisecond; 245 } 246 247 /** 248 * Returns the last millisecond of the hour. This will be 249 * determined relative to the time zone specified in the constructor, or 250 * in the calendar instance passed in the most recent call to the 251 * {@link #peg(Calendar)} method. 252 * 253 * @return The last millisecond of the hour. 254 * 255 * @see #getFirstMillisecond() 256 */ 257 public long getLastMillisecond() { 258 return this.lastMillisecond; 259 } 260 261 /** 262 * Recalculates the start date/time and end date/time for this time period 263 * relative to the supplied calendar (which incorporates a time zone). 264 * 265 * @param calendar the calendar (<code>null</code> not permitted). 266 * 267 * @since 1.0.3 268 */ 269 public void peg(Calendar calendar) { 270 this.firstMillisecond = getFirstMillisecond(calendar); 271 this.lastMillisecond = getLastMillisecond(calendar); 272 } 273 274 /** 275 * Returns the hour preceding this one. 276 * 277 * @return The hour preceding this one. 278 */ 279 public RegularTimePeriod previous() { 280 Hour result; 281 if (this.hour != FIRST_HOUR_IN_DAY) { 282 result = new Hour(this.hour - 1, this.day); 283 } 284 else { // we are at the first hour in the day... 285 Day prevDay = (Day) this.day.previous(); 286 if (prevDay != null) { 287 result = new Hour(LAST_HOUR_IN_DAY, prevDay); 288 } 289 else { 290 result = null; 291 } 292 } 293 return result; 294 } 295 296 /** 297 * Returns the hour following this one. 298 * 299 * @return The hour following this one. 300 */ 301 public RegularTimePeriod next() { 302 Hour result; 303 if (this.hour != LAST_HOUR_IN_DAY) { 304 result = new Hour(this.hour + 1, this.day); 305 } 306 else { // we are at the last hour in the day... 307 Day nextDay = (Day) this.day.next(); 308 if (nextDay != null) { 309 result = new Hour(FIRST_HOUR_IN_DAY, nextDay); 310 } 311 else { 312 result = null; 313 } 314 } 315 return result; 316 } 317 318 /** 319 * Returns a serial index number for the hour. 320 * 321 * @return The serial index number. 322 */ 323 public long getSerialIndex() { 324 return this.day.getSerialIndex() * 24L + this.hour; 325 } 326 327 /** 328 * Returns the first millisecond of the hour. 329 * 330 * @param calendar the calendar/timezone (<code>null</code> not permitted). 331 * 332 * @return The first millisecond. 333 * 334 * @throws NullPointerException if <code>calendar</code> is 335 * <code>null</code>. 336 */ 337 public long getFirstMillisecond(Calendar calendar) { 338 int year = this.day.getYear(); 339 int month = this.day.getMonth() - 1; 340 int dom = this.day.getDayOfMonth(); 341 calendar.set(year, month, dom, this.hour, 0, 0); 342 calendar.set(Calendar.MILLISECOND, 0); 343 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3 344 return calendar.getTime().getTime(); 345 } 346 347 /** 348 * Returns the last millisecond of the hour. 349 * 350 * @param calendar the calendar/timezone (<code>null</code> not permitted). 351 * 352 * @return The last millisecond. 353 * 354 * @throws NullPointerException if <code>calendar</code> is 355 * <code>null</code>. 356 */ 357 public long getLastMillisecond(Calendar calendar) { 358 int year = this.day.getYear(); 359 int month = this.day.getMonth() - 1; 360 int dom = this.day.getDayOfMonth(); 361 calendar.set(year, month, dom, this.hour, 59, 59); 362 calendar.set(Calendar.MILLISECOND, 999); 363 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3 364 return calendar.getTime().getTime(); 365 } 366 367 /** 368 * Tests the equality of this object against an arbitrary Object. 369 * <P> 370 * This method will return true ONLY if the object is an Hour object 371 * representing the same hour as this instance. 372 * 373 * @param obj the object to compare (<code>null</code> permitted). 374 * 375 * @return <code>true</code> if the hour and day value of the object 376 * is the same as this. 377 */ 378 public boolean equals(Object obj) { 379 if (obj == this) { 380 return true; 381 } 382 if (!(obj instanceof Hour)) { 383 return false; 384 } 385 Hour that = (Hour) obj; 386 if (this.hour != that.hour) { 387 return false; 388 } 389 if (!this.day.equals(that.day)) { 390 return false; 391 } 392 return true; 393 } 394 395 /** 396 * Returns a string representation of this instance, for debugging 397 * purposes. 398 * 399 * @return A string. 400 */ 401 public String toString() { 402 return "[" + this.hour + "," + getDayOfMonth() + "/" + getMonth() + "/" 403 + getYear() + "]"; 404 } 405 406 /** 407 * Returns a hash code for this object instance. The approach described by 408 * Joshua Bloch in "Effective Java" has been used here: 409 * <p> 410 * <code>http://developer.java.sun.com/developer/Books/effectivejava 411 * /Chapter3.pdf</code> 412 * 413 * @return A hash code. 414 */ 415 public int hashCode() { 416 int result = 17; 417 result = 37 * result + this.hour; 418 result = 37 * result + this.day.hashCode(); 419 return result; 420 } 421 422 /** 423 * Returns an integer indicating the order of this Hour object relative to 424 * the specified object: 425 * 426 * negative == before, zero == same, positive == after. 427 * 428 * @param o1 the object to compare. 429 * 430 * @return negative == before, zero == same, positive == after. 431 */ 432 public int compareTo(Object o1) { 433 int result; 434 435 // CASE 1 : Comparing to another Hour object 436 // ----------------------------------------- 437 if (o1 instanceof Hour) { 438 Hour h = (Hour) o1; 439 result = getDay().compareTo(h.getDay()); 440 if (result == 0) { 441 result = this.hour - h.getHour(); 442 } 443 } 444 445 // CASE 2 : Comparing to another TimePeriod object 446 // ----------------------------------------------- 447 else if (o1 instanceof RegularTimePeriod) { 448 // more difficult case - evaluate later... 449 result = 0; 450 } 451 452 // CASE 3 : Comparing to a non-TimePeriod object 453 // --------------------------------------------- 454 else { 455 // consider time periods to be ordered after general objects 456 result = 1; 457 } 458 459 return result; 460 } 461 462 /** 463 * Creates an Hour instance by parsing a string. The string is assumed to 464 * be in the format "YYYY-MM-DD HH", perhaps with leading or trailing 465 * whitespace. 466 * 467 * @param s the hour string to parse. 468 * 469 * @return <code>null</code> if the string is not parseable, the hour 470 * otherwise. 471 */ 472 public static Hour parseHour(String s) { 473 Hour result = null; 474 s = s.trim(); 475 476 String daystr = s.substring(0, Math.min(10, s.length())); 477 Day day = Day.parseDay(daystr); 478 if (day != null) { 479 String hourstr = s.substring( 480 Math.min(daystr.length() + 1, s.length()), s.length() 481 ); 482 hourstr = hourstr.trim(); 483 int hour = Integer.parseInt(hourstr); 484 // if the hour is 0 - 23 then create an hour 485 if ((hour >= FIRST_HOUR_IN_DAY) && (hour <= LAST_HOUR_IN_DAY)) { 486 result = new Hour(hour, day); 487 } 488 } 489 490 return result; 491 } 492 493 }