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