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