Source for org.jfree.chart.util.RelativeDateFormat

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it
  10:  * under the terms of the GNU Lesser General Public License as published by
  11:  * the Free Software Foundation; either version 2.1 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
  22:  * USA.
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25:  * in the United States and other countries.]
  26:  *
  27:  * -----------------------
  28:  * RelativeDateFormat.java
  29:  * -----------------------
  30:  * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Michael Siemer;
  34:  *
  35:  * Changes:
  36:  * --------
  37:  * 01-Nov-2006 : Version 1 (DG);
  38:  * 23-Nov-2006 : Added argument checks, updated equals(), added clone() and
  39:  *               hashCode() (DG);
  40:  * 15-Feb-2008 : Applied patch 1873328 by Michael Siemer, with minor
  41:  *               modifications (DG);
  42:  *
  43:  */
  44: 
  45: package org.jfree.chart.util;
  46: 
  47: import java.text.DateFormat;
  48: import java.text.DecimalFormat;
  49: import java.text.FieldPosition;
  50: import java.text.NumberFormat;
  51: import java.text.ParsePosition;
  52: import java.util.Calendar;
  53: import java.util.Date;
  54: import java.util.GregorianCalendar;
  55: 
  56: /**
  57:  * A formatter that formats dates to show the elapsed time relative to some
  58:  * base date.
  59:  *
  60:  * @since 1.0.3
  61:  */
  62: public class RelativeDateFormat extends DateFormat {
  63: 
  64:     /** The base milliseconds for the elapsed time calculation. */
  65:     private long baseMillis;
  66: 
  67:     /**
  68:      * A flag that controls whether or not a zero day count is displayed.
  69:      */
  70:     private boolean showZeroDays;
  71: 
  72:     /**
  73:      * A flag that controls whether or not a zero hour count is displayed.
  74:      *
  75:      * @since 1.0.10
  76:      */
  77:     private boolean showZeroHours;
  78: 
  79:     /**
  80:      * A formatter for the day count (most likely not critical until the
  81:      * day count exceeds 999).
  82:      */
  83:     private NumberFormat dayFormatter;
  84: 
  85:     /**
  86:      * A prefix prepended to the start of the format if the relative date is
  87:      * positive.
  88:      *
  89:      * @since 1.0.10
  90:      */
  91:     private String positivePrefix;
  92: 
  93:     /**
  94:      * A string appended after the day count.
  95:      */
  96:     private String daySuffix;
  97: 
  98:     /**
  99:      * 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: }