001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * -----------------------
028 * RelativeDateFormat.java
029 * -----------------------
030 * (C) Copyright 2006-2014, 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 * 02-Jul-2013 : Use ParamChecks, and fix NB warnings (DG);
045 *
046 */
047
048package org.jfree.chart.util;
049
050import java.text.DateFormat;
051import java.text.DecimalFormat;
052import java.text.FieldPosition;
053import java.text.NumberFormat;
054import java.text.ParsePosition;
055import java.util.Calendar;
056import java.util.Date;
057import java.util.GregorianCalendar;
058
059/**
060 * A formatter that formats dates to show the elapsed time relative to some
061 * base date.
062 *
063 * @since 1.0.3
064 */
065public class RelativeDateFormat extends DateFormat {
066
067    /** The base milliseconds for the elapsed time calculation. */
068    private long baseMillis;
069
070    /**
071     * A flag that controls whether or not a zero day count is displayed.
072     */
073    private boolean showZeroDays;
074
075    /**
076     * A flag that controls whether or not a zero hour count is displayed.
077     *
078     * @since 1.0.10
079     */
080    private boolean showZeroHours;
081
082    /**
083     * A formatter for the day count (most likely not critical until the
084     * day count exceeds 999).
085     */
086    private NumberFormat dayFormatter;
087
088    /**
089     * A prefix prepended to the start of the format if the relative date is
090     * positive.
091     *
092     * @since 1.0.10
093     */
094    private String positivePrefix;
095
096    /**
097     * A string appended after the day count.
098     */
099    private String daySuffix;
100
101    /**
102     * A formatter for the hours.
103     *
104     * @since 1.0.11
105     */
106    private NumberFormat hourFormatter;
107
108    /**
109     * A string appended after the hours.
110     */
111    private String hourSuffix;
112
113    /**
114     * A formatter for the minutes.
115     *
116     * @since 1.0.11
117     */
118    private NumberFormat minuteFormatter;
119
120    /**
121     * A string appended after the minutes.
122     */
123    private String minuteSuffix;
124
125    /**
126     * A formatter for the seconds (and milliseconds).
127     */
128    private NumberFormat secondFormatter;
129
130    /**
131     * A string appended after the seconds.
132     */
133    private String secondSuffix;
134
135    /**
136     * A constant for the number of milliseconds in one hour.
137     */
138    private static final long MILLISECONDS_IN_ONE_HOUR = 60 * 60 * 1000L;
139
140    /**
141     * A constant for the number of milliseconds in one day.
142     */
143    private static final long MILLISECONDS_IN_ONE_DAY 
144            = 24 * MILLISECONDS_IN_ONE_HOUR;
145
146    /**
147     * Creates a new instance with base milliseconds set to zero.
148     */
149    public RelativeDateFormat() {
150        this(0L);
151    }
152
153    /**
154     * Creates a new instance.
155     *
156     * @param time  the date/time (<code>null</code> not permitted).
157     */
158    public RelativeDateFormat(Date time) {
159        this(time.getTime());
160    }
161
162    /**
163     * Creates a new instance.
164     *
165     * @param baseMillis  the time zone (<code>null</code> not permitted).
166     */
167    public RelativeDateFormat(long baseMillis) {
168        super();
169        this.baseMillis = baseMillis;
170        this.showZeroDays = false;
171        this.showZeroHours = true;
172        this.positivePrefix = "";
173        this.dayFormatter = NumberFormat.getNumberInstance();
174        this.daySuffix = "d";
175        this.hourFormatter = NumberFormat.getNumberInstance();
176        this.hourSuffix = "h";
177        this.minuteFormatter = NumberFormat.getNumberInstance();
178        this.minuteSuffix = "m";
179        this.secondFormatter = NumberFormat.getNumberInstance();
180        this.secondFormatter.setMaximumFractionDigits(3);
181        this.secondFormatter.setMinimumFractionDigits(3);
182        this.secondSuffix = "s";
183
184        // we don't use the calendar or numberFormat fields, but equals(Object)
185        // is failing without them being non-null
186        this.calendar = new GregorianCalendar();
187        this.numberFormat = new DecimalFormat("0");
188    }
189
190    /**
191     * Returns the base date/time used to calculate the elapsed time for
192     * display.
193     *
194     * @return The base date/time in milliseconds since 1-Jan-1970.
195     *
196     * @see #setBaseMillis(long)
197     */
198    public long getBaseMillis() {
199        return this.baseMillis;
200    }
201
202    /**
203     * Sets the base date/time used to calculate the elapsed time for display.
204     * This should be specified in milliseconds using the same encoding as
205     * <code>java.util.Date</code>.
206     *
207     * @param baseMillis  the base date/time in milliseconds.
208     *
209     * @see #getBaseMillis()
210     */
211    public void setBaseMillis(long baseMillis) {
212        this.baseMillis = baseMillis;
213    }
214
215    /**
216     * Returns the flag that controls whether or not zero day counts are
217     * shown in the formatted output.
218     *
219     * @return The flag.
220     *
221     * @see #setShowZeroDays(boolean)
222     */
223    public boolean getShowZeroDays() {
224        return this.showZeroDays;
225    }
226
227    /**
228     * Sets the flag that controls whether or not zero day counts are shown
229     * in the formatted output.
230     *
231     * @param show  the flag.
232     *
233     * @see #getShowZeroDays()
234     */
235    public void setShowZeroDays(boolean show) {
236        this.showZeroDays = show;
237    }
238
239    /**
240     * Returns the flag that controls whether or not zero hour counts are
241     * shown in the formatted output.
242     *
243     * @return The flag.
244     *
245     * @see #setShowZeroHours(boolean)
246     *
247     * @since 1.0.10
248     */
249    public boolean getShowZeroHours() {
250        return this.showZeroHours;
251    }
252
253    /**
254     * Sets the flag that controls whether or not zero hour counts are shown
255     * in the formatted output.
256     *
257     * @param show  the flag.
258     *
259     * @see #getShowZeroHours()
260     *
261     * @since 1.0.10
262     */
263    public void setShowZeroHours(boolean show) {
264        this.showZeroHours = show;
265    }
266
267    /**
268     * Returns the string that is prepended to the format if the relative time
269     * is positive.
270     *
271     * @return The string (never <code>null</code>).
272     *
273     * @see #setPositivePrefix(String)
274     *
275     * @since 1.0.10
276     */
277    public String getPositivePrefix() {
278        return this.positivePrefix;
279    }
280
281    /**
282     * Sets the string that is prepended to the format if the relative time is
283     * positive.
284     *
285     * @param prefix  the prefix (<code>null</code> not permitted).
286     *
287     * @see #getPositivePrefix()
288     *
289     * @since 1.0.10
290     */
291    public void setPositivePrefix(String prefix) {
292        ParamChecks.nullNotPermitted(prefix, "prefix");
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        ParamChecks.nullNotPermitted(formatter, "formatter");
305        this.dayFormatter = formatter;
306    }
307
308    /**
309     * Returns the string that is appended to the day count.
310     *
311     * @return The string.
312     *
313     * @see #setDaySuffix(String)
314     */
315    public String getDaySuffix() {
316        return this.daySuffix;
317    }
318
319    /**
320     * Sets the string that is appended to the day count.
321     *
322     * @param suffix  the suffix (<code>null</code> not permitted).
323     *
324     * @see #getDaySuffix()
325     */
326    public void setDaySuffix(String suffix) {
327        ParamChecks.nullNotPermitted(suffix, "suffix");
328        this.daySuffix = suffix;
329    }
330
331    /**
332     * Sets the formatter for the hours.
333     *
334     * @param formatter  the formatter (<code>null</code> not permitted).
335     *
336     * @since 1.0.11
337     */
338    public void setHourFormatter(NumberFormat formatter) {
339        ParamChecks.nullNotPermitted(formatter, "formatter");
340        this.hourFormatter = formatter;
341    }
342
343    /**
344     * Returns the string that is appended to the hour count.
345     *
346     * @return The string.
347     *
348     * @see #setHourSuffix(String)
349     */
350    public String getHourSuffix() {
351        return this.hourSuffix;
352    }
353
354    /**
355     * Sets the string that is appended to the hour count.
356     *
357     * @param suffix  the suffix (<code>null</code> not permitted).
358     *
359     * @see #getHourSuffix()
360     */
361    public void setHourSuffix(String suffix) {
362        ParamChecks.nullNotPermitted(suffix, "suffix");
363        this.hourSuffix = suffix;
364    }
365
366    /**
367     * Sets the formatter for the minutes.
368     *
369     * @param formatter  the formatter (<code>null</code> not permitted).
370     *
371     * @since 1.0.11
372     */
373    public void setMinuteFormatter(NumberFormat formatter) {
374        ParamChecks.nullNotPermitted(formatter, "formatter");
375        this.minuteFormatter = formatter;
376    }
377
378    /**
379     * Returns the string that is appended to the minute count.
380     *
381     * @return The string.
382     *
383     * @see #setMinuteSuffix(String)
384     */
385    public String getMinuteSuffix() {
386        return this.minuteSuffix;
387    }
388
389    /**
390     * Sets the string that is appended to the minute count.
391     *
392     * @param suffix  the suffix (<code>null</code> not permitted).
393     *
394     * @see #getMinuteSuffix()
395     */
396    public void setMinuteSuffix(String suffix) {
397        ParamChecks.nullNotPermitted(suffix, "suffix");
398        this.minuteSuffix = suffix;
399    }
400
401    /**
402     * Returns the string that is appended to the second count.
403     *
404     * @return The string.
405     *
406     * @see #setSecondSuffix(String)
407     */
408    public String getSecondSuffix() {
409        return this.secondSuffix;
410    }
411
412    /**
413     * Sets the string that is appended to the second count.
414     *
415     * @param suffix  the suffix (<code>null</code> not permitted).
416     *
417     * @see #getSecondSuffix()
418     */
419    public void setSecondSuffix(String suffix) {
420        ParamChecks.nullNotPermitted(suffix, "suffix");
421        this.secondSuffix = suffix;
422    }
423
424    /**
425     * Sets the formatter for the seconds and milliseconds.
426     *
427     * @param formatter  the formatter (<code>null</code> not permitted).
428     */
429    public void setSecondFormatter(NumberFormat formatter) {
430        ParamChecks.nullNotPermitted(formatter, "formatter");
431        this.secondFormatter = formatter;
432    }
433
434    /**
435     * Formats the given date as the amount of elapsed time (relative to the
436     * base date specified in the constructor).
437     *
438     * @param date  the date.
439     * @param toAppendTo  the string buffer.
440     * @param fieldPosition  the field position.
441     *
442     * @return The formatted date.
443     */
444    @Override
445    public StringBuffer format(Date date, StringBuffer toAppendTo,
446                               FieldPosition fieldPosition) {
447        long currentMillis = date.getTime();
448        long elapsed = currentMillis - this.baseMillis;
449        String signPrefix;
450        if (elapsed < 0) {
451            elapsed *= -1L;
452            signPrefix = "-";
453        }
454        else {
455            signPrefix = this.positivePrefix;
456        }
457
458        long days = elapsed / MILLISECONDS_IN_ONE_DAY;
459        elapsed = elapsed - (days * MILLISECONDS_IN_ONE_DAY);
460        long hours = elapsed / MILLISECONDS_IN_ONE_HOUR;
461        elapsed = elapsed - (hours * MILLISECONDS_IN_ONE_HOUR);
462        long minutes = elapsed / 60000L;
463        elapsed = elapsed - (minutes * 60000L);
464        double seconds = elapsed / 1000.0;
465
466        toAppendTo.append(signPrefix);
467        if (days != 0 || this.showZeroDays) {
468            toAppendTo.append(this.dayFormatter.format(days))
469                    .append(getDaySuffix());
470        }
471        if (hours != 0 || this.showZeroHours) {
472            toAppendTo.append(this.hourFormatter.format(hours))
473                    .append(getHourSuffix());
474        }
475        toAppendTo.append(this.minuteFormatter.format(minutes))
476                .append(getMinuteSuffix());
477        toAppendTo.append(this.secondFormatter.format(seconds))
478                .append(getSecondSuffix());
479        return toAppendTo;
480    }
481
482    /**
483     * Parses the given string (not implemented).
484     *
485     * @param source  the date string.
486     * @param pos  the parse position.
487     *
488     * @return <code>null</code>, as this method has not been implemented.
489     */
490    @Override
491    public Date parse(String source, ParsePosition pos) {
492        return null;
493    }
494
495    /**
496     * Tests this formatter for equality with an arbitrary object.
497     *
498     * @param obj  the object (<code>null</code> permitted).
499     *
500     * @return A boolean.
501     */
502    @Override
503    public boolean equals(Object obj) {
504        if (obj == this) {
505            return true;
506        }
507        if (!(obj instanceof RelativeDateFormat)) {
508            return false;
509        }
510        if (!super.equals(obj)) {
511            return false;
512        }
513        RelativeDateFormat that = (RelativeDateFormat) obj;
514        if (this.baseMillis != that.baseMillis) {
515            return false;
516        }
517        if (this.showZeroDays != that.showZeroDays) {
518            return false;
519        }
520        if (this.showZeroHours != that.showZeroHours) {
521            return false;
522        }
523        if (!this.positivePrefix.equals(that.positivePrefix)) {
524            return false;
525        }
526        if (!this.daySuffix.equals(that.daySuffix)) {
527            return false;
528        }
529        if (!this.hourSuffix.equals(that.hourSuffix)) {
530            return false;
531        }
532        if (!this.minuteSuffix.equals(that.minuteSuffix)) {
533            return false;
534        }
535        if (!this.secondSuffix.equals(that.secondSuffix)) {
536            return false;
537        }
538        if (!this.dayFormatter.equals(that.dayFormatter)) {
539            return false;
540        }
541        if (!this.hourFormatter.equals(that.hourFormatter)) {
542            return false;
543        }
544        if (!this.minuteFormatter.equals(that.minuteFormatter)) {
545            return false;
546        }
547        if (!this.secondFormatter.equals(that.secondFormatter)) {
548            return false;
549        }
550        return true;
551    }
552
553    /**
554     * Returns a hash code for this instance.
555     *
556     * @return A hash code.
557     */
558    @Override
559    public int hashCode() {
560        int result = 193;
561        result = 37 * result
562                + (int) (this.baseMillis ^ (this.baseMillis >>> 32));
563        result = 37 * result + this.positivePrefix.hashCode();
564        result = 37 * result + this.daySuffix.hashCode();
565        result = 37 * result + this.hourSuffix.hashCode();
566        result = 37 * result + this.minuteSuffix.hashCode();
567        result = 37 * result + this.secondSuffix.hashCode();
568        result = 37 * result + this.secondFormatter.hashCode();
569        return result;
570    }
571
572    /**
573     * Returns a clone of this instance.
574     *
575     * @return A clone.
576     */
577    @Override
578    public Object clone() {
579        RelativeDateFormat clone = (RelativeDateFormat) super.clone();
580        clone.dayFormatter = (NumberFormat) this.dayFormatter.clone();
581        clone.secondFormatter = (NumberFormat) this.secondFormatter.clone();
582        return clone;
583    }
584
585}