    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    }