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