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