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