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