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 }