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