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