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