content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2012, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jfreechart/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   * Other names may be trademarks of their respective owners.]
   *
   * -----------------------
   * RelativeDateFormat.java
   * -----------------------
   * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors.
   *
   * Original Author:  David Gilbert (for Object Refinery Limited);
   * Contributor(s):   Michael Siemer;
   *
   * Changes:
   * --------
   * 01-Nov-2006 : Version 1 (DG);
   * 23-Nov-2006 : Added argument checks, updated equals(), added clone() and
   *               hashCode() (DG);
   * 15-Feb-2008 : Applied patch 1873328 by Michael Siemer, with minor
   *               modifications (DG);
   * 01-Sep-2008 : Added new fields for hour and minute formatting, based on
   *               patch 2033092 (DG);
   *
   */
  
  package org.jfree.chart.util;
  
  import java.text.DateFormat;
  import java.text.DecimalFormat;
  import java.text.FieldPosition;
  import java.text.NumberFormat;
  import java.text.ParsePosition;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.GregorianCalendar;
  
  /**
   * A formatter that formats dates to show the elapsed time relative to some
   * base date.
   *
   * @since 1.0.3
   */
  public class RelativeDateFormat extends DateFormat {
  
      /** The base milliseconds for the elapsed time calculation. */
      private long baseMillis;
  
      /**
       * A flag that controls whether or not a zero day count is displayed.
       */
      private boolean showZeroDays;
  
      /**
       * A flag that controls whether or not a zero hour count is displayed.
       *
       * @since 1.0.10
       */
      private boolean showZeroHours;
  
      /**
       * A formatter for the day count (most likely not critical until the
       * day count exceeds 999).
       */
      private NumberFormat dayFormatter;
  
      /**
       * A prefix prepended to the start of the format if the relative date is
       * positive.
       *
       * @since 1.0.10
       */
      private String positivePrefix;
  
      /**
       * A string appended after the day count.
       */
      private String daySuffix;
  
      /**
       * A formatter for the hours.
       *
       * @since 1.0.11
       */
      private NumberFormat hourFormatter;
  
      /**
       * A string appended after the hours.
       */
      private String hourSuffix;
  
      /**
       * A formatter for the minutes.
       *
       * @since 1.0.11
       */
      private NumberFormat minuteFormatter;
  
      /**
       * A string appended after the minutes.
       */
      private String minuteSuffix;
  
      /**
       * A formatter for the seconds (and milliseconds).
       */
      private NumberFormat secondFormatter;
  
      /**
       * A string appended after the seconds.
       */
      private String secondSuffix;
  
      /**
       * A constant for the number of milliseconds in one hour.
       */
      private static long MILLISECONDS_IN_ONE_HOUR = 60 * 60 * 1000L;
  
      /**
       * A constant for the number of milliseconds in one day.
       */
      private static long MILLISECONDS_IN_ONE_DAY = 24 * MILLISECONDS_IN_ONE_HOUR;
  
      /**
       * Creates a new instance with base milliseconds set to zero.
       */
      public RelativeDateFormat() {
          this(0L);
      }
  
      /**
       * Creates a new instance.
       *
       * @param time  the date/time (<code>null</code> not permitted).
       */
      public RelativeDateFormat(Date time) {
          this(time.getTime());
      }
  
      /**
       * Creates a new instance.
       *
       * @param baseMillis  the time zone (<code>null</code> not permitted).
       */
      public RelativeDateFormat(long baseMillis) {
          super();
          this.baseMillis = baseMillis;
          this.showZeroDays = false;
          this.showZeroHours = true;
          this.positivePrefix = "";
          this.dayFormatter = NumberFormat.getNumberInstance();
          this.daySuffix = "d";
          this.hourFormatter = NumberFormat.getNumberInstance();
          this.hourSuffix = "h";
          this.minuteFormatter = NumberFormat.getNumberInstance();
          this.minuteSuffix = "m";
          this.secondFormatter = NumberFormat.getNumberInstance();
          this.secondFormatter.setMaximumFractionDigits(3);
          this.secondFormatter.setMinimumFractionDigits(3);
          this.secondSuffix = "s";
  
          // we don't use the calendar or numberFormat fields, but equals(Object)
          // is failing without them being non-null
          this.calendar = new GregorianCalendar();
          this.numberFormat = new DecimalFormat("0");
      }
  
      /**
       * Returns the base date/time used to calculate the elapsed time for
       * display.
       *
       * @return The base date/time in milliseconds since 1-Jan-1970.
       *
       * @see #setBaseMillis(long)
       */
      public long getBaseMillis() {
          return this.baseMillis;
      }
  
      /**
       * Sets the base date/time used to calculate the elapsed time for display.
       * This should be specified in milliseconds using the same encoding as
       * <code>java.util.Date</code>.
       *
       * @param baseMillis  the base date/time in milliseconds.
       *
       * @see #getBaseMillis()
       */
      public void setBaseMillis(long baseMillis) {
          this.baseMillis = baseMillis;
      }
  
      /**
       * Returns the flag that controls whether or not zero day counts are
       * shown in the formatted output.
       *
       * @return The flag.
       *
       * @see #setShowZeroDays(boolean)
       */
      public boolean getShowZeroDays() {
          return this.showZeroDays;
      }
  
      /**
       * Sets the flag that controls whether or not zero day counts are shown
       * in the formatted output.
       *
       * @param show  the flag.
       *
       * @see #getShowZeroDays()
       */
      public void setShowZeroDays(boolean show) {
          this.showZeroDays = show;
      }
  
      /**
       * Returns the flag that controls whether or not zero hour counts are
       * shown in the formatted output.
       *
       * @return The flag.
       *
       * @see #setShowZeroHours(boolean)
       *
       * @since 1.0.10
       */
      public boolean getShowZeroHours() {
          return this.showZeroHours;
      }
  
      /**
       * Sets the flag that controls whether or not zero hour counts are shown
       * in the formatted output.
       *
       * @param show  the flag.
       *
       * @see #getShowZeroHours()
       *
       * @since 1.0.10
       */
      public void setShowZeroHours(boolean show) {
          this.showZeroHours = show;
      }
  
      /**
       * Returns the string that is prepended to the format if the relative time
       * is positive.
       *
       * @return The string (never <code>null</code>).
       *
       * @see #setPositivePrefix(String)
       *
       * @since 1.0.10
       */
      public String getPositivePrefix() {
          return this.positivePrefix;
      }
  
      /**
       * Sets the string that is prepended to the format if the relative time is
       * positive.
       *
       * @param prefix  the prefix (<code>null</code> not permitted).
       *
       * @see #getPositivePrefix()
       *
       * @since 1.0.10
       */
      public void setPositivePrefix(String prefix) {
          if (prefix == null) {
              throw new IllegalArgumentException("Null 'prefix' argument.");
          }
          this.positivePrefix = prefix;
      }
  
      /**
       * Sets the formatter for the days.
       *
       * @param formatter  the formatter (<code>null</code> not permitted).
       *
       * @since 1.0.11
       */
      public void setDayFormatter(NumberFormat formatter) {
          if (formatter == null) {
              throw new IllegalArgumentException("Null 'formatter' argument.");
          }
          this.dayFormatter = formatter;
      }
  
      /**
       * Returns the string that is appended to the day count.
       *
       * @return The string.
       *
       * @see #setDaySuffix(String)
       */
      public String getDaySuffix() {
          return this.daySuffix;
      }
  
      /**
       * Sets the string that is appended to the day count.
       *
       * @param suffix  the suffix (<code>null</code> not permitted).
       *
       * @see #getDaySuffix()
       */
      public void setDaySuffix(String suffix) {
          if (suffix == null) {
              throw new IllegalArgumentException("Null 'suffix' argument.");
          }
          this.daySuffix = suffix;
      }
  
      /**
       * Sets the formatter for the hours.
       *
       * @param formatter  the formatter (<code>null</code> not permitted).
       *
       * @since 1.0.11
       */
      public void setHourFormatter(NumberFormat formatter) {
          if (formatter == null) {
              throw new IllegalArgumentException("Null 'formatter' argument.");
          }
          this.hourFormatter = formatter;
      }
  
      /**
       * Returns the string that is appended to the hour count.
       *
       * @return The string.
       *
       * @see #setHourSuffix(String)
       */
      public String getHourSuffix() {
          return this.hourSuffix;
      }
  
      /**
       * Sets the string that is appended to the hour count.
       *
       * @param suffix  the suffix (<code>null</code> not permitted).
       *
       * @see #getHourSuffix()
       */
      public void setHourSuffix(String suffix) {
          if (suffix == null) {
              throw new IllegalArgumentException("Null 'suffix' argument.");
          }
          this.hourSuffix = suffix;
      }
  
      /**
       * Sets the formatter for the minutes.
       *
       * @param formatter  the formatter (<code>null</code> not permitted).
       *
       * @since 1.0.11
       */
      public void setMinuteFormatter(NumberFormat formatter) {
          if (formatter == null) {
              throw new IllegalArgumentException("Null 'formatter' argument.");
          }
          this.minuteFormatter = formatter;
      }
  
      /**
       * Returns the string that is appended to the minute count.
       *
       * @return The string.
       *
       * @see #setMinuteSuffix(String)
       */
      public String getMinuteSuffix() {
          return this.minuteSuffix;
      }
  
      /**
       * Sets the string that is appended to the minute count.
       *
       * @param suffix  the suffix (<code>null</code> not permitted).
       *
       * @see #getMinuteSuffix()
       */
      public void setMinuteSuffix(String suffix) {
          if (suffix == null) {
              throw new IllegalArgumentException("Null 'suffix' argument.");
          }
          this.minuteSuffix = suffix;
      }
  
      /**
       * Returns the string that is appended to the second count.
       *
       * @return The string.
       *
       * @see #setSecondSuffix(String)
       */
      public String getSecondSuffix() {
          return this.secondSuffix;
      }
  
      /**
       * Sets the string that is appended to the second count.
       *
       * @param suffix  the suffix (<code>null</code> not permitted).
       *
       * @see #getSecondSuffix()
       */
      public void setSecondSuffix(String suffix) {
          if (suffix == null) {
              throw new IllegalArgumentException("Null 'suffix' argument.");
          }
          this.secondSuffix = suffix;
      }
  
      /**
       * Sets the formatter for the seconds and milliseconds.
       *
       * @param formatter  the formatter (<code>null</code> not permitted).
       */
      public void setSecondFormatter(NumberFormat formatter) {
          if (formatter == null) {
              throw new IllegalArgumentException("Null 'formatter' argument.");
          }
          this.secondFormatter = formatter;
      }
  
      /**
       * Formats the given date as the amount of elapsed time (relative to the
       * base date specified in the constructor).
       *
       * @param date  the date.
       * @param toAppendTo  the string buffer.
       * @param fieldPosition  the field position.
       *
       * @return The formatted date.
       */
      @Override
      public StringBuffer format(Date date, StringBuffer toAppendTo,
                                 FieldPosition fieldPosition) {
          long currentMillis = date.getTime();
          long elapsed = currentMillis - this.baseMillis;
          String signPrefix;
          if (elapsed < 0) {
              elapsed *= -1L;
              signPrefix = "-";
          }
          else {
              signPrefix = this.positivePrefix;
          }
  
          long days = elapsed / MILLISECONDS_IN_ONE_DAY;
          elapsed = elapsed - (days * MILLISECONDS_IN_ONE_DAY);
          long hours = elapsed / MILLISECONDS_IN_ONE_HOUR;
          elapsed = elapsed - (hours * MILLISECONDS_IN_ONE_HOUR);
          long minutes = elapsed / 60000L;
          elapsed = elapsed - (minutes * 60000L);
          double seconds = elapsed / 1000.0;
  
          toAppendTo.append(signPrefix);
          if (days != 0 || this.showZeroDays) {
              toAppendTo.append(this.dayFormatter.format(days) + getDaySuffix());
          }
          if (hours != 0 || this.showZeroHours) {
              toAppendTo.append(this.hourFormatter.format(hours)
                      + getHourSuffix());
          }
          toAppendTo.append(this.minuteFormatter.format(minutes)
                  + getMinuteSuffix());
          toAppendTo.append(this.secondFormatter.format(seconds)
                  + getSecondSuffix());
          return toAppendTo;
      }
  
      /**
       * Parses the given string (not implemented).
       *
       * @param source  the date string.
       * @param pos  the parse position.
       *
       * @return <code>null</code>, as this method has not been implemented.
       */
      @Override
      public Date parse(String source, ParsePosition pos) {
          return null;
      }
  
      /**
       * Tests this formatter for equality with an arbitrary object.
       *
       * @param obj  the object (<code>null</code> permitted).
       *
       * @return A boolean.
       */
      @Override
      public boolean equals(Object obj) {
          if (obj == this) {
              return true;
          }
          if (!(obj instanceof RelativeDateFormat)) {
              return false;
          }
          if (!super.equals(obj)) {
              return false;
          }
          RelativeDateFormat that = (RelativeDateFormat) obj;
          if (this.baseMillis != that.baseMillis) {
              return false;
          }
          if (this.showZeroDays != that.showZeroDays) {
              return false;
          }
          if (this.showZeroHours != that.showZeroHours) {
              return false;
          }
          if (!this.positivePrefix.equals(that.positivePrefix)) {
              return false;
          }
          if (!this.daySuffix.equals(that.daySuffix)) {
              return false;
          }
          if (!this.hourSuffix.equals(that.hourSuffix)) {
              return false;
          }
          if (!this.minuteSuffix.equals(that.minuteSuffix)) {
              return false;
          }
          if (!this.secondSuffix.equals(that.secondSuffix)) {
              return false;
          }
          if (!this.dayFormatter.equals(that.dayFormatter)) {
              return false;
          }
          if (!this.hourFormatter.equals(that.hourFormatter)) {
              return false;
          }
          if (!this.minuteFormatter.equals(that.minuteFormatter)) {
              return false;
          }
          if (!this.secondFormatter.equals(that.secondFormatter)) {
              return false;
          }
          return true;
      }
  
      /**
       * Returns a hash code for this instance.
       *
       * @return A hash code.
       */
      @Override
      public int hashCode() {
          int result = 193;
          result = 37 * result
                  + (int) (this.baseMillis ^ (this.baseMillis >>> 32));
          result = 37 * result + this.positivePrefix.hashCode();
          result = 37 * result + this.daySuffix.hashCode();
          result = 37 * result + this.hourSuffix.hashCode();
          result = 37 * result + this.minuteSuffix.hashCode();
          result = 37 * result + this.secondSuffix.hashCode();
          result = 37 * result + this.secondFormatter.hashCode();
          return result;
      }
  
      /**
       * Returns a clone of this instance.
       *
       * @return A clone.
       */
      @Override
      public Object clone() {
          RelativeDateFormat clone = (RelativeDateFormat) super.clone();
          clone.dayFormatter = (NumberFormat) this.dayFormatter.clone();
          clone.secondFormatter = (NumberFormat) this.secondFormatter.clone();
          return clone;
      }
  
      /**
       * Some test code.
       *
       * @param args  ignored.
       */
      public static void main(String[] args) {
          GregorianCalendar c0 = new GregorianCalendar(2006, 10, 1, 0, 0, 0);
          GregorianCalendar c1 = new GregorianCalendar(2006, 10, 1, 11, 37, 43);
          c1.set(Calendar.MILLISECOND, 123);
  
          System.out.println("Default: ");
          RelativeDateFormat rdf = new RelativeDateFormat(c0.getTimeInMillis());
          System.out.println(rdf.format(c1.getTime()));
          System.out.println();
  
          System.out.println("Hide milliseconds: ");
          rdf.setSecondFormatter(new DecimalFormat("0"));
          System.out.println(rdf.format(c1.getTime()));
          System.out.println();
  
          System.out.println("Show zero day output: ");
          rdf.setShowZeroDays(true);
          System.out.println(rdf.format(c1.getTime()));
          System.out.println();
  
          System.out.println("Alternative suffixes: ");
          rdf.setShowZeroDays(false);
          rdf.setDaySuffix(":");
          rdf.setHourSuffix(":");
          rdf.setMinuteSuffix(":");
          rdf.setSecondSuffix("");
          System.out.println(rdf.format(c1.getTime()));
          System.out.println();
      }
  }
  