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