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 }