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