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    }