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