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