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 * RelativeDateFormat.java 029 * ----------------------- 030 * (C) Copyright 2006-2022, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Michael Siemer; 034 * 035 */ 036 037package org.jfree.chart.text.format; 038 039import java.text.DateFormat; 040import java.text.DecimalFormat; 041import java.text.FieldPosition; 042import java.text.NumberFormat; 043import java.text.ParsePosition; 044import java.util.Date; 045import java.util.GregorianCalendar; 046import org.jfree.chart.internal.Args; 047 048/** 049 * A formatter that formats dates to show the elapsed time relative to some 050 * base date. 051 */ 052public class RelativeDateFormat extends DateFormat { 053 054 /** The base milliseconds for the elapsed time calculation. */ 055 private long baseMillis; 056 057 /** 058 * A flag that controls whether or not a zero day count is displayed. 059 */ 060 private boolean showZeroDays; 061 062 /** 063 * A flag that controls whether or not a zero hour count is displayed. 064 */ 065 private boolean showZeroHours; 066 067 /** 068 * A formatter for the day count (most likely not critical until the 069 * day count exceeds 999). 070 */ 071 private NumberFormat dayFormatter; 072 073 /** 074 * A prefix prepended to the start of the format if the relative date is 075 * positive. 076 */ 077 private String positivePrefix; 078 079 /** 080 * A string appended after the day count. 081 */ 082 private String daySuffix; 083 084 /** 085 * A formatter for the hours. 086 */ 087 private NumberFormat hourFormatter; 088 089 /** 090 * A string appended after the hours. 091 */ 092 private String hourSuffix; 093 094 /** 095 * A formatter for the minutes. 096 */ 097 private NumberFormat minuteFormatter; 098 099 /** 100 * A string appended after the minutes. 101 */ 102 private String minuteSuffix; 103 104 /** 105 * A formatter for the seconds (and milliseconds). 106 */ 107 private NumberFormat secondFormatter; 108 109 /** 110 * A string appended after the seconds. 111 */ 112 private String secondSuffix; 113 114 /** 115 * A constant for the number of milliseconds in one hour. 116 */ 117 private static final long MILLISECONDS_IN_ONE_HOUR = 60 * 60 * 1000L; 118 119 /** 120 * A constant for the number of milliseconds in one day. 121 */ 122 private static final long MILLISECONDS_IN_ONE_DAY 123 = 24 * MILLISECONDS_IN_ONE_HOUR; 124 125 /** 126 * Creates a new instance with base milliseconds set to zero. 127 */ 128 public RelativeDateFormat() { 129 this(0L); 130 } 131 132 /** 133 * Creates a new instance. 134 * 135 * @param time the date/time ({@code null} not permitted). 136 */ 137 public RelativeDateFormat(Date time) { 138 this(time.getTime()); 139 } 140 141 /** 142 * Creates a new instance. 143 * 144 * @param baseMillis the time zone ({@code null} not permitted). 145 */ 146 public RelativeDateFormat(long baseMillis) { 147 super(); 148 this.baseMillis = baseMillis; 149 this.showZeroDays = false; 150 this.showZeroHours = true; 151 this.positivePrefix = ""; 152 this.dayFormatter = NumberFormat.getNumberInstance(); 153 this.daySuffix = "d"; 154 this.hourFormatter = NumberFormat.getNumberInstance(); 155 this.hourSuffix = "h"; 156 this.minuteFormatter = NumberFormat.getNumberInstance(); 157 this.minuteSuffix = "m"; 158 this.secondFormatter = NumberFormat.getNumberInstance(); 159 this.secondFormatter.setMaximumFractionDigits(3); 160 this.secondFormatter.setMinimumFractionDigits(3); 161 this.secondSuffix = "s"; 162 163 // we don't use the calendar or numberFormat fields, but equals(Object) 164 // is failing without them being non-null 165 this.calendar = new GregorianCalendar(); 166 this.numberFormat = new DecimalFormat("0"); 167 } 168 169 /** 170 * Returns the base date/time used to calculate the elapsed time for 171 * display. 172 * 173 * @return The base date/time in milliseconds since 1-Jan-1970. 174 * 175 * @see #setBaseMillis(long) 176 */ 177 public long getBaseMillis() { 178 return this.baseMillis; 179 } 180 181 /** 182 * Sets the base date/time used to calculate the elapsed time for display. 183 * This should be specified in milliseconds using the same encoding as 184 * {@code java.util.Date}. 185 * 186 * @param baseMillis the base date/time in milliseconds. 187 * 188 * @see #getBaseMillis() 189 */ 190 public void setBaseMillis(long baseMillis) { 191 this.baseMillis = baseMillis; 192 } 193 194 /** 195 * Returns the flag that controls whether or not zero day counts are 196 * shown in the formatted output. 197 * 198 * @return The flag. 199 * 200 * @see #setShowZeroDays(boolean) 201 */ 202 public boolean getShowZeroDays() { 203 return this.showZeroDays; 204 } 205 206 /** 207 * Sets the flag that controls whether or not zero day counts are shown 208 * in the formatted output. 209 * 210 * @param show the flag. 211 * 212 * @see #getShowZeroDays() 213 */ 214 public void setShowZeroDays(boolean show) { 215 this.showZeroDays = show; 216 } 217 218 /** 219 * Returns the flag that controls whether or not zero hour counts are 220 * shown in the formatted output. 221 * 222 * @return The flag. 223 * 224 * @see #setShowZeroHours(boolean) 225 */ 226 public boolean getShowZeroHours() { 227 return this.showZeroHours; 228 } 229 230 /** 231 * Sets the flag that controls whether or not zero hour counts are shown 232 * in the formatted output. 233 * 234 * @param show the flag. 235 * 236 * @see #getShowZeroHours() 237 */ 238 public void setShowZeroHours(boolean show) { 239 this.showZeroHours = show; 240 } 241 242 /** 243 * Returns the string that is prepended to the format if the relative time 244 * is positive. 245 * 246 * @return The string (never {@code null}). 247 * 248 * @see #setPositivePrefix(String) 249 */ 250 public String getPositivePrefix() { 251 return this.positivePrefix; 252 } 253 254 /** 255 * Sets the string that is prepended to the format if the relative time is 256 * positive. 257 * 258 * @param prefix the prefix ({@code null} not permitted). 259 * 260 * @see #getPositivePrefix() 261 */ 262 public void setPositivePrefix(String prefix) { 263 Args.nullNotPermitted(prefix, "prefix"); 264 this.positivePrefix = prefix; 265 } 266 267 /** 268 * Sets the formatter for the days. 269 * 270 * @param formatter the formatter ({@code null} not permitted). 271 */ 272 public void setDayFormatter(NumberFormat formatter) { 273 Args.nullNotPermitted(formatter, "formatter"); 274 this.dayFormatter = formatter; 275 } 276 277 /** 278 * Returns the string that is appended to the day count. 279 * 280 * @return The string. 281 * 282 * @see #setDaySuffix(String) 283 */ 284 public String getDaySuffix() { 285 return this.daySuffix; 286 } 287 288 /** 289 * Sets the string that is appended to the day count. 290 * 291 * @param suffix the suffix ({@code null} not permitted). 292 * 293 * @see #getDaySuffix() 294 */ 295 public void setDaySuffix(String suffix) { 296 Args.nullNotPermitted(suffix, "suffix"); 297 this.daySuffix = suffix; 298 } 299 300 /** 301 * Sets the formatter for the hours. 302 * 303 * @param formatter the formatter ({@code null} not permitted). 304 */ 305 public void setHourFormatter(NumberFormat formatter) { 306 Args.nullNotPermitted(formatter, "formatter"); 307 this.hourFormatter = formatter; 308 } 309 310 /** 311 * Returns the string that is appended to the hour count. 312 * 313 * @return The string. 314 * 315 * @see #setHourSuffix(String) 316 */ 317 public String getHourSuffix() { 318 return this.hourSuffix; 319 } 320 321 /** 322 * Sets the string that is appended to the hour count. 323 * 324 * @param suffix the suffix ({@code null} not permitted). 325 * 326 * @see #getHourSuffix() 327 */ 328 public void setHourSuffix(String suffix) { 329 Args.nullNotPermitted(suffix, "suffix"); 330 this.hourSuffix = suffix; 331 } 332 333 /** 334 * Sets the formatter for the minutes. 335 * 336 * @param formatter the formatter ({@code null} not permitted). 337 */ 338 public void setMinuteFormatter(NumberFormat formatter) { 339 Args.nullNotPermitted(formatter, "formatter"); 340 this.minuteFormatter = formatter; 341 } 342 343 /** 344 * Returns the string that is appended to the minute count. 345 * 346 * @return The string. 347 * 348 * @see #setMinuteSuffix(String) 349 */ 350 public String getMinuteSuffix() { 351 return this.minuteSuffix; 352 } 353 354 /** 355 * Sets the string that is appended to the minute count. 356 * 357 * @param suffix the suffix ({@code null} not permitted). 358 * 359 * @see #getMinuteSuffix() 360 */ 361 public void setMinuteSuffix(String suffix) { 362 Args.nullNotPermitted(suffix, "suffix"); 363 this.minuteSuffix = suffix; 364 } 365 366 /** 367 * Returns the string that is appended to the second count. 368 * 369 * @return The string. 370 * 371 * @see #setSecondSuffix(String) 372 */ 373 public String getSecondSuffix() { 374 return this.secondSuffix; 375 } 376 377 /** 378 * Sets the string that is appended to the second count. 379 * 380 * @param suffix the suffix ({@code null} not permitted). 381 * 382 * @see #getSecondSuffix() 383 */ 384 public void setSecondSuffix(String suffix) { 385 Args.nullNotPermitted(suffix, "suffix"); 386 this.secondSuffix = suffix; 387 } 388 389 /** 390 * Sets the formatter for the seconds and milliseconds. 391 * 392 * @param formatter the formatter ({@code null} not permitted). 393 */ 394 public void setSecondFormatter(NumberFormat formatter) { 395 Args.nullNotPermitted(formatter, "formatter"); 396 this.secondFormatter = formatter; 397 } 398 399 /** 400 * Formats the given date as the amount of elapsed time (relative to the 401 * base date specified in the constructor). 402 * 403 * @param date the date. 404 * @param toAppendTo the string buffer. 405 * @param fieldPosition the field position. 406 * 407 * @return The formatted date. 408 */ 409 @Override 410 public StringBuffer format(Date date, StringBuffer toAppendTo, 411 FieldPosition fieldPosition) { 412 long currentMillis = date.getTime(); 413 long elapsed = currentMillis - this.baseMillis; 414 String signPrefix; 415 if (elapsed < 0) { 416 elapsed *= -1L; 417 signPrefix = "-"; 418 } 419 else { 420 signPrefix = this.positivePrefix; 421 } 422 423 long days = elapsed / MILLISECONDS_IN_ONE_DAY; 424 elapsed = elapsed - (days * MILLISECONDS_IN_ONE_DAY); 425 long hours = elapsed / MILLISECONDS_IN_ONE_HOUR; 426 elapsed = elapsed - (hours * MILLISECONDS_IN_ONE_HOUR); 427 long minutes = elapsed / 60000L; 428 elapsed = elapsed - (minutes * 60000L); 429 double seconds = elapsed / 1000.0; 430 431 toAppendTo.append(signPrefix); 432 if (days != 0 || this.showZeroDays) { 433 toAppendTo.append(this.dayFormatter.format(days)) 434 .append(getDaySuffix()); 435 } 436 if (hours != 0 || this.showZeroHours) { 437 toAppendTo.append(this.hourFormatter.format(hours)) 438 .append(getHourSuffix()); 439 } 440 toAppendTo.append(this.minuteFormatter.format(minutes)) 441 .append(getMinuteSuffix()); 442 toAppendTo.append(this.secondFormatter.format(seconds)) 443 .append(getSecondSuffix()); 444 return toAppendTo; 445 } 446 447 /** 448 * Parses the given string (not implemented). 449 * 450 * @param source the date string. 451 * @param pos the parse position. 452 * 453 * @return {@code null}, as this method has not been implemented. 454 */ 455 @Override 456 public Date parse(String source, ParsePosition pos) { 457 return null; 458 } 459 460 /** 461 * Tests this formatter for equality with an arbitrary object. 462 * 463 * @param obj the object ({@code null} permitted). 464 * 465 * @return A boolean. 466 */ 467 @Override 468 public boolean equals(Object obj) { 469 if (obj == this) { 470 return true; 471 } 472 if (!(obj instanceof RelativeDateFormat)) { 473 return false; 474 } 475 if (!super.equals(obj)) { 476 return false; 477 } 478 RelativeDateFormat that = (RelativeDateFormat) obj; 479 if (this.baseMillis != that.baseMillis) { 480 return false; 481 } 482 if (this.showZeroDays != that.showZeroDays) { 483 return false; 484 } 485 if (this.showZeroHours != that.showZeroHours) { 486 return false; 487 } 488 if (!this.positivePrefix.equals(that.positivePrefix)) { 489 return false; 490 } 491 if (!this.daySuffix.equals(that.daySuffix)) { 492 return false; 493 } 494 if (!this.hourSuffix.equals(that.hourSuffix)) { 495 return false; 496 } 497 if (!this.minuteSuffix.equals(that.minuteSuffix)) { 498 return false; 499 } 500 if (!this.secondSuffix.equals(that.secondSuffix)) { 501 return false; 502 } 503 if (!this.dayFormatter.equals(that.dayFormatter)) { 504 return false; 505 } 506 if (!this.hourFormatter.equals(that.hourFormatter)) { 507 return false; 508 } 509 if (!this.minuteFormatter.equals(that.minuteFormatter)) { 510 return false; 511 } 512 if (!this.secondFormatter.equals(that.secondFormatter)) { 513 return false; 514 } 515 return true; 516 } 517 518 /** 519 * Returns a hash code for this instance. 520 * 521 * @return A hash code. 522 */ 523 @Override 524 public int hashCode() { 525 int result = 193; 526 result = 37 * result 527 + (int) (this.baseMillis ^ (this.baseMillis >>> 32)); 528 result = 37 * result + this.positivePrefix.hashCode(); 529 result = 37 * result + this.daySuffix.hashCode(); 530 result = 37 * result + this.hourSuffix.hashCode(); 531 result = 37 * result + this.minuteSuffix.hashCode(); 532 result = 37 * result + this.secondSuffix.hashCode(); 533 result = 37 * result + this.secondFormatter.hashCode(); 534 return result; 535 } 536 537 /** 538 * Returns a clone of this instance. 539 * 540 * @return A clone. 541 */ 542 @Override 543 public Object clone() { 544 RelativeDateFormat clone = (RelativeDateFormat) super.clone(); 545 clone.dayFormatter = (NumberFormat) this.dayFormatter.clone(); 546 clone.secondFormatter = (NumberFormat) this.secondFormatter.clone(); 547 return clone; 548 } 549 550}