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