001 package edu.nrao.sss.measure; 002 003 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 004 005 import java.math.BigDecimal; 006 import java.math.RoundingMode; 007 import java.util.Arrays; 008 import java.util.Comparator; 009 import java.util.Date; 010 import java.util.EnumMap; 011 import java.util.EnumSet; 012 import java.util.Map; 013 import java.util.Set; 014 import java.util.SortedSet; 015 import java.util.TreeSet; 016 import java.util.regex.Pattern; 017 018 import javax.xml.bind.annotation.XmlAccessType; 019 import javax.xml.bind.annotation.XmlAccessorType; 020 import javax.xml.bind.annotation.XmlElement; 021 import javax.xml.bind.annotation.XmlType; 022 023 import edu.nrao.sss.math.MathUtil; 024 import edu.nrao.sss.util.StringUtil; 025 026 //TODO work on infinity. Make private static +/-INFINITY BigDecimal variables 027 // Whenever a value becomes infinite, use the private statics 028 029 /** 030 * A length of time without a defined starting or ending point. 031 * Contrast this with a {@link TimeInterval}, which is the span 032 * from one instant in time to another. 033 * <p> 034 * This duration currently works with only the following units: 035 * <ul> 036 * <li>{@code TimeUnits.NANOSECOND}</li> 037 * <li>{@code TimeUnits.MICROSECOND}</li> 038 * <li>{@code TimeUnits.MILLISECOND}</li> 039 * <li>{@code TimeUnits.SECOND}</li> 040 * <li>{@code TimeUnits.MINUTE}</li> 041 * <li>{@code TimeUnits.HOUR}</li> 042 * </ul> 043 * This set of legal units is provided by {@link #getLegalUnits()}.</p> 044 * <p> 045 * <b><u>Note About Accuracy</u></b><br/> 046 * This class originally used java's primitive <tt>double</tt> type 047 * for storage and calculation. Certain transformations, though, 048 * led to results that where not accurate enough for many purposes. 049 * Because of that, the internal references to <tt>double</tt> 050 * have been replaced with references to {@link BigDecimal}.</p> 051 * <p> 052 * <b>Version Info:</b> 053 * <table style="margin-left:2em"> 054 * <tr><td>$Revision: 1816 $</td> 055 * <tr><td>$Date: 2008-12-23 10:21:00 -0700 (Tue, 23 Dec 2008) $</td> 056 * <tr><td>$Author: dharland $</td> 057 * </table></p> 058 * 059 * @author David M. Harland 060 * @since 2006-03-30 061 */ 062 @XmlAccessorType(XmlAccessType.NONE) 063 @XmlType(propOrder={"xmlValue","units"}) 064 public class TimeDuration 065 implements Cloneable, Comparable<TimeDuration>, java.io.Serializable 066 { 067 private static final long serialVersionUID = 1L; 068 069 private static final char TEXT_SEPARATOR = ':'; 070 071 private static final BigDecimal DEFAULT_VALUE = BigDecimal.ZERO; 072 private static final TimeUnits DEFAULT_UNITS = TimeUnits.HOUR; 073 074 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 075 076 //Used by equals, hashCode, and compareTo methods 077 //private static final TimeUnits STD_UNITS = TimeUnits.MILLISECOND; 078 079 //Sets the legal time units for all methods 080 private static final EnumSet<TimeUnits> LEGAL_TIME_UNITS = 081 EnumSet.range(TimeUnits.HOUR, TimeUnits.NANOSECOND); 082 083 //Used by normalization methods 084 private static final Map<TimeUnits, Long> NORMALIZATION_FACTORS = 085 new EnumMap<TimeUnits, Long>(TimeUnits.class); 086 087 static 088 { 089 NORMALIZATION_FACTORS.put(TimeUnits.NANOSECOND, 1000000000L); 090 NORMALIZATION_FACTORS.put(TimeUnits.MICROSECOND, 1000000L); 091 NORMALIZATION_FACTORS.put(TimeUnits.MILLISECOND, 1000L); 092 NORMALIZATION_FACTORS.put(TimeUnits.SECOND, 60L); 093 NORMALIZATION_FACTORS.put(TimeUnits.MINUTE, 60L); 094 NORMALIZATION_FACTORS.put(TimeUnits.HOUR, Long.MAX_VALUE); //Not 24. 095 } 096 097 private BigDecimal value; 098 private TimeUnits units; 099 100 //=========================================================================== 101 // CONSTRUCTORS 102 //=========================================================================== 103 104 /** Creates a new duration of zero hours. */ 105 public TimeDuration() 106 { 107 set(DEFAULT_VALUE, DEFAULT_UNITS); 108 } 109 110 /** 111 * Creates a new duration of {@code hours} hours. 112 * See {@link #setValue(BigDecimal)} for information 113 * about valid parameter values and exceptions that might 114 * be thrown. 115 * 116 * @param hours the length of this duration in hours. 117 */ 118 public TimeDuration(BigDecimal hours) 119 { 120 set(hours, TimeUnits.HOUR); 121 } 122 123 /** 124 * Creates a new duration of {@code hours} hours. 125 * See {@link #setValue(String)} for information 126 * about valid parameter values and exceptions that might 127 * be thrown. 128 * 129 * @param hours the length of this duration in hours. 130 */ 131 public TimeDuration(String hours) 132 { 133 set(hours, TimeUnits.HOUR); 134 } 135 136 /** 137 * Creates a new duration of the given length. 138 * See {@link #set(BigDecimal, TimeUnits)} for information 139 * about valid parameter values and exceptions that might 140 * be thrown. 141 * 142 * @param value the length of this duration. 143 * 144 * @param units 145 * the units in which {@code value} is expressed. 146 * See the {@link TimeDuration class comments} for a list of legal values. 147 */ 148 public TimeDuration(BigDecimal value, TimeUnits units) 149 { 150 set(value, units); 151 } 152 153 /** 154 * Creates a new duration of the given length. 155 * See {@link #set(String, TimeUnits)} for information 156 * about valid parameter values and exceptions that might 157 * be thrown. 158 * 159 * @param value the length of this duration. 160 * 161 * @param units 162 * the units in which {@code time} is expressed. 163 * See the {@link TimeDuration class comments} for a list of legal values. 164 */ 165 public TimeDuration(String value, TimeUnits units) 166 { 167 set(value, units); 168 } 169 170 /** 171 * Resets this duration to its initial state. 172 * A reset duration has the same state as a new duration created 173 * with the no-argument constructor. 174 */ 175 public void reset() 176 { 177 set(DEFAULT_VALUE, DEFAULT_UNITS); 178 } 179 180 //=========================================================================== 181 // GETTING & SETTING THE PROPERTIES 182 //=========================================================================== 183 184 /** 185 * Returns the length of this duration. 186 * @return the length of this duration. 187 */ 188 public BigDecimal getValue() 189 { 190 return value; 191 } 192 193 /** 194 * Returns the units of this duration. 195 * @return the units of this duration. 196 */ 197 @XmlElement 198 public TimeUnits getUnits() 199 { 200 return units; 201 } 202 203 /** 204 * Sets the length and units of this duration. 205 * <p> 206 * See {@link #setValue(BigDecimal)} for more information on legal 207 * values for <tt>value</tt>.</p> 208 * 209 * @param value the length of this duration. 210 * @param units the units in which {@code value} is expressed. 211 */ 212 public final void set(BigDecimal value, TimeUnits units) 213 { 214 setValue(value); 215 setUnits(units); 216 } 217 218 /** 219 * Sets the length and units of this duration. 220 * <p> 221 * See {@link #setValue(String)} for more information on legal 222 * values for <tt>value</tt>.</p> 223 * 224 * @param value the length of this duration. 225 * @param units the units in which {@code time} is expressed. 226 */ 227 public final void set(String value, TimeUnits units) 228 { 229 setValue(value); 230 setUnits(units); 231 } 232 233 /** 234 * Sets the length of this duration to {@code newValue}. 235 * <p> 236 * Note that the <tt>units</tt> of this duration are unaffected by 237 * this method.</p> 238 * 239 * @param newValue 240 * the new magnitude for this duration. 241 * This value may not be negative or <i>null</i> but may be infinite. 242 * 243 * @throws NumberFormatException 244 * if {@code newValue} is <i>null</i> or negative. 245 */ 246 public final void setValue(BigDecimal newValue) 247 { 248 if (newValue == null || newValue.signum() < 0) 249 throw new NumberFormatException("newValue=" + newValue + 250 " is not a valid time of day. It must be non-null and non-negative."); 251 252 setAndRescale(newValue); 253 } 254 255 private void setAndRescale(BigDecimal newValue) 256 { 257 int precision = newValue.precision(); 258 259 if (precision < PRECISION) 260 { 261 int newScale = 262 (newValue.signum() == 0) ? 1 : PRECISION - precision + newValue.scale(); 263 264 value = newValue.setScale(newScale); 265 } 266 else if (precision > PRECISION) 267 { 268 value = newValue.round(MC_FINAL_CALC); 269 } 270 else 271 { 272 value = newValue; 273 } 274 } 275 276 /** 277 * Sets the length of this duration to {@code newValue}. 278 * <p> 279 * Note that the <tt>units</tt> of this duration are unaffected by 280 * this method.</p> 281 * 282 * @param newValue 283 * the new magnitude for this duration. 284 * This value may not be negative or <i>null</i> but may be infinite. 285 * The allowable representations of infinity are 286 * <tt>"infinity", "+infinity", </tt>and<tt> "-infinity"</tt>; 287 * these values are not case sensitive. 288 * 289 * @throws NumberFormatException 290 * if {@code newValue} is <i>null</i> or negative. 291 */ 292 public final void setValue(String newValue) 293 { 294 if (newValue == null) 295 throw new NumberFormatException("newValue may not be null."); 296 297 BigDecimal newBD; 298 299 newValue = newValue.trim().toLowerCase(); 300 301 if (newValue.equals("infinity") || 302 newValue.equals("+infinity") || newValue.equals("-infinity")) 303 { 304 newBD = MathUtil.getInfiniteValue(newValue.startsWith("-") ? -1 : +1); 305 } 306 else 307 { 308 newBD = new BigDecimal(newValue); 309 } 310 311 if (newBD.signum() < 0) 312 throw new NumberFormatException("newValue=" + newValue + 313 " is not a valid duration. It may not be negative."); 314 315 setAndRescale(newBD); 316 } 317 318 /** 319 * Sets the units of this duration to {@code newUnits}. 320 * <p> 321 * Note that the <tt>value</tt> of this duration is unaffected by 322 * this method. Contrast this with {@link #convertTo(TimeUnits)}.</p> 323 * 324 * @param newUnits 325 * the new units for this duration. 326 * See the {@link TimeDuration class comments} for a list of legal values. 327 * 328 * @throws IllegalArgumentException 329 * if the parameter is outside the described range. 330 */ 331 public final void setUnits(TimeUnits newUnits) 332 { 333 if (!LEGAL_TIME_UNITS.contains(newUnits)) 334 throw new IllegalArgumentException("Illegal newUnits: " + newUnits); 335 336 units = newUnits; 337 } 338 339 /** 340 * Sets the length of this duration. 341 * <p> 342 * While there are no restrictions on the values of the individual 343 * parameters, their combination must not result in a negative duration.</p> 344 * 345 * @throws IllegalArgumentException 346 * if the combination of parameters results in a negative duration. 347 */ 348 public final void set(long hours, int minutes, BigDecimal seconds) 349 { 350 BigDecimal msH = TimeUnits.HOUR.toUnits(TimeUnits.MILLISECOND) 351 .multiply(new BigDecimal(hours)); 352 353 BigDecimal msM = TimeUnits.MINUTE.toUnits(TimeUnits.MILLISECOND) 354 .multiply(new BigDecimal(minutes)); 355 356 BigDecimal msS = TimeUnits.SECOND.toUnits(TimeUnits.MILLISECOND) 357 .multiply(seconds); 358 359 BigDecimal milliseconds = msH.add(msM).add(msS); 360 361 if (milliseconds.signum() < 0) 362 throw new IllegalArgumentException("Bad combination of parameters: " + 363 hours +"hrs " + minutes + "min " + 364 seconds + "sec."); 365 366 setAndRescale(milliseconds); 367 units = TimeUnits.MILLISECOND; 368 } 369 370 /** 371 * Sets the length of this duration. 372 * <p> 373 * While there are no restrictions on the values of the individual 374 * parameters, their combination must not result in a negative duration.</p> 375 * 376 * @throws IllegalArgumentException 377 * if the combination of parameters results in a negative duration. 378 */ 379 public final void set(long hours, int minutes, String seconds) 380 { 381 set(hours, minutes, new BigDecimal(seconds)); 382 } 383 384 /** 385 * Sets this duration to be equal to {@code other}. 386 * <p> 387 * The code:<pre> 388 * TimeDuration td = new TimeDuration().set(otherDuration);</pre> 389 * gives the same result as:<pre> 390 * TimeDuration td = otherDuration.clone();</pre> 391 * This method is better suited to those situations where the target 392 * duration has already been created.</p> 393 * 394 * @param other the duration to which this duration will be made equal. 395 */ 396 public void set(TimeDuration other) 397 { 398 if (other == null) 399 throw new IllegalArgumentException("May not use NULL other in set(TimeDuration)."); 400 401 //Do not need to check for valid value/units 402 this.value = other.value; 403 this.units = other.units; 404 } 405 406 /** 407 * Sets the value and units of this duration based on {@code timeText}. 408 * See {@link #parse(String)} for the expected format of {@code timeText}. 409 * <p> 410 * If the parsing fails, this duration will be kept in its current 411 * state.</p> 412 * 413 * @param timeText 414 * a string that will be converted into a time duration. 415 * 416 * @throws IllegalArgumentException 417 * if {@code timeText} is not in the expected form. 418 * 419 * @since 2008-10-01 420 */ 421 public void set(String timeText) 422 { 423 if (timeText == null || timeText.equals("")) 424 { 425 this.reset(); 426 } 427 else 428 { 429 TimeUnits oldUnits = units; 430 BigDecimal oldValue = value; 431 432 try { 433 this.parseDuration(timeText); 434 } 435 catch (Exception ex) { 436 set(oldValue, oldUnits); 437 throw new IllegalArgumentException("Could not parse " + 438 timeText, ex); 439 } 440 } 441 } 442 443 //=========================================================================== 444 // HELPS FOR PERSISTENCE MECHANISMS 445 //=========================================================================== 446 447 //JAXB was having trouble with the overloaded setValue methods. 448 //These methods work around that trouble. 449 @XmlElement(name="value") 450 @SuppressWarnings("unused") 451 private BigDecimal getXmlValue() { return getValue().stripTrailingZeros(); } 452 @SuppressWarnings("unused") 453 private void setXmlValue(BigDecimal v) { setValue(v); } 454 455 //=========================================================================== 456 // DERIVED QUERIES 457 //=========================================================================== 458 459 /** 460 * Returns <i>true</i> if this duration is in its default state, 461 * no matter how it got there. 462 * <p> 463 * A duration is in its <i>default state</i> if both its value and 464 * its units are the same as those of a duration newly created via 465 * the {@link #TimeDuration() no-argument constructor}. 466 * A duration whose most recent update came via the 467 * {@link #reset() reset} method is also in its default state.</p> 468 * 469 * @return <i>true</i> if this duration is in its default state. 470 */ 471 public boolean isInDefaultState() 472 { 473 return value.equals(DEFAULT_VALUE) && 474 units.equals(DEFAULT_UNITS); 475 } 476 477 /** 478 * Returns <i>true</i> if this duration is infinite. 479 * @return <i>true</i> if this duration is infinite. 480 */ 481 public boolean isInfinite() 482 { 483 return MathUtil.doubleValueIsInfinite(value); 484 } 485 486 //=========================================================================== 487 // CONVERSION TO, AND EXPRESSION IN, OTHER UNITS 488 //=========================================================================== 489 490 /** 491 * Converts this duration to the new units. 492 * <p> 493 * After this method is complete this duration will have units of 494 * {@code units} and its <tt>value</tt> will have been converted 495 * accordingly.</p> 496 * 497 * @param newUnits the new units for this duration. 498 * If {@code newUnits} is <i>null</i> an 499 * {@code IllegalArgumentException} will be thrown. 500 * See the {@link TimeDuration class comments} for 501 * a list of legal values. 502 * 503 * @return this duration. The reason for this return type is to allow 504 * code of this nature: 505 * {@code BigDecimal hours = 506 * myDuration.convertTo(TimeUnits.HOUR).getValue();} 507 * 508 * @throws IllegalArgumentException if the parameter is outside the 509 * described range. 510 */ 511 public TimeDuration convertTo(TimeUnits newUnits) 512 { 513 if (newUnits == null) 514 throw new 515 IllegalArgumentException("NULL is not a valid value for newUnits."); 516 517 if (!newUnits.equals(units)) 518 { 519 set(toUnits(newUnits), newUnits); 520 } 521 522 return this; 523 } 524 525 /** 526 * Returns the length of this duration in {@code otherUnits}. 527 * <p> 528 * Note that this method does not alter the state of this duration. 529 * Contrast this with {@link #convertTo(TimeUnits)}.</p> 530 * <p> 531 * <b><u>Example:</u></b><br/> 532 * Let this duration be of length 2 hours 13 minutes 45.6789 seconds. 533 * The values returned by this method for the legal values of 534 * {@code unit} are (to 5 decimal places): 535 * <p><table style="margin-left:3em"> 536 * <tr><th><u>Units</u></th><th><u>Value</u></th></tr> 537 * <tr><td>MILLISECOND</td> <td align="right">8,025,678.90000</td></tr> 538 * <tr><td>SECOND</td> <td align="right">8,025.67890</td></tr> 539 * <tr><td>MINUTE</td> <td align="right">133.76132</td></tr> 540 * <tr><td>HOUR</td> <td align="right">2.22936</td></tr> 541 * </table></p> 542 * <p> 543 * Contrast this with {@link #toWholeUnits(TimeUnits)}.</p> 544 * <p> 545 * In addition to working with the legal time units described in the 546 * class comments, this method will also convert to: 547 * <ul> 548 * <li>{@link TimeUnits#DAY}</li> 549 * <li>{@link TimeUnits#YEAR}</li> 550 * </ul></p> 551 * 552 * @param otherUnits the units in which to express this duration's length. 553 * If {@code newUnits} is <i>null</i>, it will be treated as 554 * {@link TimeUnits#HOUR}. 555 * See the {@link TimeDuration class comments} for 556 * a list of legal values. 557 * 558 * @return this duration's value converted to {@code otherUnits}. 559 * 560 * @throws IllegalArgumentException if the parameter is outside the 561 * described range. 562 */ 563 public BigDecimal toUnits(TimeUnits otherUnits) 564 { 565 BigDecimal answer = value; 566 567 if (otherUnits == null) 568 otherUnits = TimeUnits.HOUR; 569 570 if ((!LEGAL_TIME_UNITS.contains(otherUnits)) && 571 !otherUnits.equals(TimeUnits.DAY) && 572 !otherUnits.equals(TimeUnits.YEAR)) 573 throw new IllegalArgumentException("Illegal otherUnits: " + otherUnits); 574 575 //No conversion for zero, infinite, or if no change of units 576 if (!otherUnits.equals(units) && 577 value.signum() != 0 && !isInfinite()) 578 { 579 answer = units.convertTo(otherUnits, value); 580 } 581 582 return answer; 583 } 584 585 /** 586 * Returns a set of {@link TimeUnits} that are legal for use with 587 * {@code TimeDuration} instances. 588 * @return a set of legal {@code TimeUnits}. 589 */ 590 public static Set<TimeUnits> getLegalUnits() 591 { 592 return LEGAL_TIME_UNITS.clone(); 593 } 594 595 /** 596 * Converts the value and units of this time duration so that the value 597 * is normal. By "normal" we mean that it falls in the natural range 598 * for its units. For example, the natural range for <tt>MINUTES</tt> 599 * is deemed to be [1.0-60.0). For units that are smaller than one 600 * second, the natural range is [1.0-1000.0). Even after normalization 601 * the numeric value may be outside the natural range of its units if 602 * there were no smaller or larger legal units available. 603 * 604 * @return this duration, after normalization. 605 */ 606 public TimeDuration normalize() 607 { 608 //In the Frequency class we use a better algorithm, but all the Freq'Units 609 //are 10^x, where x%3==0 and all the units are legal. 610 611 SortedSet<TimeUnits> validUnits = new TreeSet<TimeUnits>(LEGAL_TIME_UNITS); 612 613 TimeUnits newUnits = validUnits.last(); //in case we don't break from loop 614 BigDecimal seconds = toUnits(TimeUnits.SECOND); 615 616 for (TimeUnits tu : validUnits) 617 { 618 if (seconds.compareTo(tu.toSeconds()) >= 0) 619 { 620 newUnits = tu; 621 break; 622 } 623 } 624 625 return convertTo(newUnits); 626 } 627 628 /** 629 * Returns the length of this duration in a whole number of {@code units}. 630 * <p> 631 * <b><u>Example:</u></b><br/> 632 * Let this duration be of length 2 hours 13 minutes 45.6789 seconds. 633 * The values returned by this method for the legal values of 634 * {@code unit} are: 635 * <p><table style="margin-left:3em"> 636 * <tr><th><u>Units</u></th><th><u>Value</u></th></tr> 637 * <tr><td>MILLISECOND</td> <td align="right">8,025,678</td></tr> 638 * <tr><td>SECOND</td> <td align="right">8,025</td></tr> 639 * <tr><td>MINUTE</td> <td align="right">133</td></tr> 640 * <tr><td>HOUR</td> <td align="right">2</td></tr> 641 * </table></p> 642 * <p> 643 * Contrast this with {@link #toUnits(TimeUnits)}.</p> 644 * 645 * @param otherUnits the units of time in which the return value 646 * is expressed. 647 * See the {@link TimeDuration class comments} for 648 * a list of legal values. 649 * 650 * @return the length of this duration in a whole number of {@code units}. 651 * 652 * @throws IllegalArgumentException if the parameter is outside the 653 * described range. 654 */ 655 public long toWholeUnits(TimeUnits otherUnits) 656 { 657 return toUnits(otherUnits).longValue(); 658 } 659 660 /** 661 * For a duration that is thought of as HH:MM:SS.xxx, returns the part 662 * corresponding to {@code units}. 663 * <p> 664 * <b><u>Range of Returned Value:</u></b><br/> 665 * The value returned will be in its "natural" range. 666 * The table below gives the natural ranges for each unit. Note that since 667 * hours is the largest legal unit of time, it has no upper bound. 668 * <p><table style="margin-left:3em"> 669 * <tr><th><u>Units</u></th><th><u>Range</u></th></tr> 670 * <tr><td>MICROSECOND</td> <td>0 <= x < 1000</td></tr> 671 * <tr><td>MILLISECOND</td> <td>0 <= x < 1000</td></tr> 672 * <tr><td>SECOND</td> <td>0 <= x < 60</td></tr> 673 * <tr><td>MINUTE</td> <td>0 <= x < 60</td></tr> 674 * <tr><td>HOUR</td> <td>0 <= x</td></tr> 675 * </table></p> 676 * <p> 677 * <b><u>Example:</u></b><br/> 678 * Let this duration be of length 2 hours 13 minutes 45.6789 seconds. 679 * The values returned by this method for the legal values of 680 * {@code unit} are (to 5 decimal places): 681 * <p><table style="margin-left:3em"> 682 * <tr><th><u>Units</u></th><th><u>Value</u></th></tr> 683 * <tr><td>MILLISECOND</td> <td align="right">678.90000</td></tr> 684 * <tr><td>SECOND</td> <td align="right">45.67890</td></tr> 685 * <tr><td>MINUTE</td> <td align="right">13.76132</td></tr> 686 * <tr><td>HOUR</td> <td align="right">2.22936</td></tr> 687 * </table></p> 688 * 689 * @param units 690 * the units of time in which the return value is expressed. 691 * See the {@link TimeDuration class comments} for a list of legal values. 692 * 693 * @return 694 * the part of this duration corresponding to {@code units}. 695 * 696 * @throws IllegalArgumentException 697 * if the parameter is outside the described range. 698 * 699 * @see #getIntegralPart(TimeUnits) 700 */ 701 public BigDecimal getPart(TimeUnits units) 702 { 703 return toUnits(units).remainder( 704 new BigDecimal(NORMALIZATION_FACTORS.get(units))); 705 } 706 707 /** 708 * For a duration that is thought of as HH:MM:SS.xxx, returns the part 709 * corresponding to {@code units}, truncated to an integral value. 710 * <p> 711 * <b><u>Range of Returned Value:</u></b><br/> 712 * The value returned will be in its "natural" range. 713 * The table below gives the natural ranges for each unit. Note that since 714 * hours is the largest legal unit of time, it has no upper bound. 715 * <p><table style="margin-left:3em"> 716 * <tr><th><u>Units</u></th><th><u>Range</u></th></tr> 717 * <tr><td>MICROSECOND</td> <td>0 <= x < 1000</td></tr> 718 * <tr><td>MILLISECOND</td> <td>0 <= x < 1000</td></tr> 719 * <tr><td>SECOND</td> <td>0 <= x < 60</td></tr> 720 * <tr><td>MINUTE</td> <td>0 <= x < 60</td></tr> 721 * <tr><td>HOUR</td> <td>0 <= x</td></tr> 722 * </table></p> 723 * <p> 724 * <b><u>Example:</u></b><br/> 725 * Let this duration be of length 2 hours 13 minutes 45.6789 seconds. 726 * The values returned by this method for the legal values of 727 * {@code unit} are: 728 * <p><table style="margin-left:3em"> 729 * <tr><th><u>Units</u></th><th><u>Value</u></th></tr> 730 * <tr><td>MILLISECOND</td> <td align="right">678</td></tr> 731 * <tr><td>SECOND</td> <td align="right">45</td></tr> 732 * <tr><td>MINUTE</td> <td align="right">13</td></tr> 733 * <tr><td>HOUR</td> <td align="right">2</td></tr> 734 * </table></p> 735 * 736 * @param units 737 * the units of time in which the return value is expressed. 738 * See the {@link TimeDuration class comments} for a list of legal values. 739 * 740 * @return 741 * the part of this duration corresponding to {@code units}. 742 * 743 * @throws IllegalArgumentException 744 * if the parameter is outside the described range. 745 * 746 * @see #getPart(TimeUnits) 747 */ 748 public long getIntegralPart(TimeUnits units) 749 { 750 return toWholeUnits(units) % NORMALIZATION_FACTORS.get(units); 751 } 752 753 /** 754 * Returns a time interval that begins on {@code start} and lasts 755 * as long as this duration. 756 * 757 * @param start the starting time for the returned interval. 758 * 759 * @return an interval of time that begins on {@code start} and lasts 760 * as long as this duration. 761 */ 762 public TimeInterval toIntervalStartingOn(Date start) 763 { 764 Date end = 765 new Date(start.getTime() + toUnits(TimeUnits.MILLISECOND).longValue()); 766 767 return new TimeInterval(start, end); 768 } 769 770 /** 771 * Returns a time interval that ends on {@code end} and lasts 772 * as long as this duration. 773 * 774 * @param end the ending time for the returned interval. 775 * 776 * @return an interval of time that ends on {@code end} and lasts 777 * as long as this duration. 778 */ 779 public TimeInterval toIntervalEndingOn(Date end) 780 { 781 Date start = 782 new Date(end.getTime() - toUnits(TimeUnits.MILLISECOND).longValue()); 783 784 return new TimeInterval(start, end); 785 } 786 787 /** 788 * Returns a time interval that is centered on {@code center} and lasts 789 * as long as this duration. 790 * <p> 791 * If rounding prevents us from locating the endpoints the exact same 792 * distance from {@code center}, this method will have a bias toward 793 * creating an interval whose starting point is closer to 794 * {@code center} than its ending point.</p> 795 * 796 * @param center the center time for the returned interval. 797 * 798 * @return an interval of time that is centered on {@code center} and 799 * lasts as long as this duration. 800 */ 801 public TimeInterval toIntervalCenteredOn(Date center) 802 { 803 long totalLength = toUnits(TimeUnits.MILLISECOND).longValue(); 804 long firstHalfLength = totalLength / 2L; 805 long secondHalfLength = totalLength - firstHalfLength; 806 long centerTime = center.getTime(); 807 808 Date start = new Date(centerTime - firstHalfLength); 809 Date end = new Date(centerTime + secondHalfLength); 810 811 return new TimeInterval(start, end); 812 } 813 814 //=========================================================================== 815 // ARITHMETIC 816 //=========================================================================== 817 818 /** 819 * Adds the given time to this duration. 820 * 821 * @param other the duration to be added to this duration. 822 * 823 * @return this duration, after the addition. 824 */ 825 public TimeDuration add(TimeDuration other) 826 { 827 //TODO when we work on INFINITY, we need to consider other being infinite 828 // and the result being inf, too 829 if (!isInfinite()) 830 setAndRescale(value.add(other.toUnits(this.units))); 831 832 return this; 833 } 834 835 /** 836 * Subtracts {@code other} from this duration. If the subtraction would 837 * result in a negative interval, this interval is set to a length of zero. 838 * 839 * @param other the amount by which to reduce this duration. 840 */ 841 public TimeDuration subtract(TimeDuration other) 842 { 843 //TODO when we work on INFINITY, we need to consider other being infinite 844 // and the result being inf, too 845 if (!isInfinite()) 846 { 847 BigDecimal diff = value.subtract(other.toUnits(this.units)); 848 if (diff.signum() < 0) 849 diff = BigDecimal.ZERO; 850 setAndRescale(diff); 851 } 852 853 return this; 854 } 855 856 /** 857 * Multiplies this duration by {@code multiplier}. 858 * 859 * @param multiplier 860 * the number by which this duration should be multiplied. 861 * This value must not result in a duration that is 862 * negative. 863 * 864 * @return this duration, after the multiplication. 865 */ 866 public TimeDuration multiplyBy(String multiplier) 867 { 868 return multiplyBy(new BigDecimal(multiplier)); 869 } 870 871 /** 872 * Multiplies this duration by {@code multiplier}. 873 * 874 * @param multiplier 875 * the number by which this duration should be multiplied. 876 * This value must not result in a duration that is 877 * negative. 878 * 879 * @return this duration, after the multiplication. 880 */ 881 public TimeDuration multiplyBy(BigDecimal multiplier) 882 { 883 if (!isInfinite()) 884 { 885 BigDecimal result = value.multiply(multiplier); 886 887 if (result.signum() < 0) 888 throw new ArithmeticException( 889 "Result of operation may not be negative. This duration was not changed."); 890 891 setAndRescale(result); 892 } 893 894 return this; 895 } 896 897 //=========================================================================== 898 // AS TEXT 899 //=========================================================================== 900 901 /** Returns a text representation of this time duration. */ 902 public String toString() 903 { 904 return StringUtil.getInstance().formatNoScientificNotation(getValue()) + 905 getUnits().getSymbol(); 906 } 907 908 /** 909 * Returns a text representation of this duration. 910 * 911 * @param minFracDigits the minimum number of places after the decimal point. 912 * 913 * @param maxFracDigits the maximum number of places after the decimal point. 914 * 915 * @return a text representation of this frequency. 916 */ 917 public String toString(int minFracDigits, int maxFracDigits) 918 { 919 return StringUtil.getInstance().formatNoScientificNotation(value, 920 minFracDigits, 921 maxFracDigits) + 922 getUnits().getSymbol(); 923 } 924 925 /** 926 * Returns a text representation of this duration in 927 * hours, minutes, and seconds. The parts are separated 928 * by the colon (':') character. 929 */ 930 public String toStringHms() 931 { 932 return toStringHms(0, -1); 933 } 934 935 /** 936 * Returns a text representation of this duration in 937 * hours, minutes, and seconds. The parts are separated 938 * by the colon (':') character. 939 * 940 * @param minFracDigits 941 * the minimum number of places after the decimal point 942 * for the seconds field. 943 * 944 * @param maxFracDigits 945 * the maximum number of places after the decimal point 946 * for the seconds field. If this value is less than zero, 947 * no rounding or truncating will be performed. 948 */ 949 public String toStringHms(int minFracDigits, int maxFracDigits) 950 { 951 BigDecimal seconds = getPart(TimeUnits.SECOND); 952 long minutes = getIntegralPart(TimeUnits.MINUTE); 953 long hours = toWholeUnits(TimeUnits.HOUR); 954 955 //Rollover logic 956 //We have to worry about things like 59.999 being rounded to 60.0 957 if (maxFracDigits >= 0) 958 { 959 if (seconds.setScale(maxFracDigits, RoundingMode.HALF_UP).doubleValue() == 60.0) 960 { 961 seconds = BigDecimal.ZERO; 962 minutes++; 963 } 964 965 if (minutes == 60L) 966 { 967 minutes = 0L; 968 hours++; 969 } 970 } 971 972 //Formatting logic 973 StringBuilder buff = new StringBuilder(); 974 975 buff.append(hours).append(':'); 976 977 if (minutes < 10) 978 buff.append('0'); 979 buff.append(minutes).append(':'); 980 981 if (seconds.compareTo(BigDecimal.TEN) < 0) 982 buff.append('0'); 983 984 if (maxFracDigits >= 0) 985 buff.append(StringUtil.getInstance().formatNoScientificNotation( 986 seconds, minFracDigits, maxFracDigits)); 987 else //no rounding or truncation 988 buff.append(StringUtil.getInstance().formatNoScientificNotation(seconds)); 989 990 return buff.toString(); 991 } 992 993 /** 994 * Returns a string where the hours, minutes, and seconds are separated 995 * by the given string. 996 * 997 * @param separator the separator to use between the hours and minutes, 998 * and minutes and seconds, fields. 999 * 1000 * @return a text representation of this duration. 1001 */ 1002 public String toString(String separator) 1003 { 1004 return toString(separator, 0, -1); 1005 } 1006 1007 /** 1008 * Returns a string where the hours, minutes, and seconds are separated 1009 * by the given string. 1010 * 1011 * @param separator the separator to use between the hours and minutes, 1012 * and minutes and seconds, fields. 1013 * 1014 * @param minFracDigits the minimum number of places after the decimal point 1015 * for the seconds field. 1016 * 1017 * @param maxFracDigits the maximum number of places after the decimal point 1018 * for the seconds field. 1019 * 1020 * @return a text representation of this duration. 1021 */ 1022 public String toString(String separator, int minFracDigits, int maxFracDigits) 1023 { 1024 long hours = getIntegralPart(TimeUnits.HOUR); 1025 long minutes = getIntegralPart(TimeUnits.MINUTE); 1026 BigDecimal seconds = getPart(TimeUnits.SECOND); 1027 1028 StringBuilder buff = new StringBuilder(); 1029 1030 if (hours < 10) 1031 buff.append('0'); 1032 1033 buff.append(hours).append(separator); 1034 1035 if (minutes < 10) 1036 buff.append('0'); 1037 1038 buff.append(minutes).append(separator); 1039 1040 if (seconds.compareTo(BigDecimal.TEN) < 0) 1041 buff.append('0'); 1042 1043 if (maxFracDigits >= 0) 1044 buff.append(StringUtil.getInstance().formatNoScientificNotation( 1045 seconds, minFracDigits, maxFracDigits)); 1046 else 1047 buff.append(StringUtil.getInstance().formatNoScientificNotation(seconds)); 1048 1049 return buff.toString(); 1050 } 1051 1052 //=========================================================================== 1053 // PARSING 1054 //=========================================================================== 1055 1056 /** 1057 * Creates a time duration based on {@code timeText}. 1058 * <p> 1059 * The parsed text can be in many different forms. All of the forms 1060 * supported by {@link Longitude#parse(String)} are supported here. 1061 * Additionally, the form <tt>mm:ss.s</tt> (minutes and seconds) is supported. 1062 * A naked number will be treated as a number of seconds.</p> 1063 * 1064 * @param timeText 1065 * the text to be parsed and converted into a time duration. 1066 * If this value is <i>null</i> or <tt>""</tt> (the empty 1067 * string), a new duration of zero length is returned. 1068 * 1069 * @return a new time duration based on {@code timeText}. 1070 * 1071 * @throws IllegalArgumentException if {@code timeText} cannot be parsed. 1072 */ 1073 public static TimeDuration parse(String timeText) 1074 { 1075 return TimeDuration.parse(timeText, TimeOfDay.STANDARD_DAY_LENGTH); 1076 } 1077 1078 /** 1079 * Creates a new time duration by parsing {@code timeText}. 1080 * See {@link #parse(String)} for the expected format of {@code timeText}. 1081 * 1082 * @param timeText 1083 * the text to be parsed and converted into a time duration. 1084 * If this value is <i>null</i> or <tt>""</tt> (the empty 1085 * string), a new duration of zero length is returned. 1086 * 1087 * @param secondsInOneDay 1088 * the length of a day, in seconds. The {@link TimeOfDay} class has two 1089 * constants that express time in SI seconds, 1090 * {@link TimeOfDay#STANDARD_DAY_LENGTH} and 1091 * {@link TimeOfDay#SIDEREAL_DAY_LENGTH}, that my be used here. 1092 * 1093 * @return 1094 * a new time duration based on {@code timeText}. 1095 * 1096 * @throws IllegalArgumentException if {@code timeText} cannot be parsed. 1097 */ 1098 public static TimeDuration parse(String timeText, BigDecimal secondsInOneDay) 1099 { 1100 TimeDuration newDuration = new TimeDuration(secondsInOneDay); 1101 1102 //null & "" are permissible 1103 if (timeText != null && !timeText.equals("")) 1104 { 1105 try { 1106 newDuration.parseDuration(timeText); 1107 } 1108 catch (Exception ex) { 1109 throw new IllegalArgumentException("Could not parse " + timeText, ex); 1110 } 1111 } 1112 1113 return newDuration; 1114 } 1115 1116 /** 1117 * If parsing was successful, this duration's units & value will have been 1118 * valued. Otherwise an exception is thrown. 1119 */ 1120 private void parseDuration(String timeText) 1121 { 1122 //See if we're successful treating the text as a simple duration (value + units) 1123 try { 1124 parseSimpleTimeDuration(timeText); 1125 } 1126 //If not, try as complex form. 1127 //If that fails, we let the exception cascade upward. 1128 catch (Exception ex) { 1129 parseComplexTimeDuration(timeText); 1130 } 1131 } 1132 1133 /** 1134 * Used for hms and ":" forms. Eg, "12h 34m 56.789s" and "12:34:56.789". 1135 * Does the actual parsing. 1136 */ 1137 private void parseComplexTimeDuration(String timeText) 1138 { 1139 //The parsing mechanism of Longitude is very nearly what we want 1140 //here, so use it as a helper. 1141 try 1142 { 1143 Longitude helper = Longitude.parse(timeText, false); 1144 Number[] pieces = helper.toHms(); 1145 this.set(pieces[0].longValue(), 1146 pieces[1].intValue(), (BigDecimal)pieces[2]); 1147 } 1148 //We want to accept one form that Longitude does not: 1149 //minutes and seconds w/ colon (mm:ss.s) 1150 catch (Exception lonParseEx) 1151 { 1152 String[] pieces = timeText.split(Character.toString(TEXT_SEPARATOR),-1); 1153 if (pieces.length == 2) 1154 { 1155 int minutes = Integer.parseInt(pieces[0]); 1156 BigDecimal seconds = new BigDecimal(pieces[1]); 1157 this.set(0L, minutes, seconds); 1158 } 1159 else 1160 { 1161 throw new IllegalArgumentException("Could not parse " +timeText+ "."); 1162 } 1163 } 1164 } 1165 1166 private static final Pattern ANY_ALPHA = Pattern.compile(".*[a-zA-Z].*"); 1167 private static final Pattern ANY_WHITESPACE = Pattern.compile(".*\\s.*"); 1168 1169 /** 1170 * Used to see if string is form such as "123.45" or "98.765m". 1171 * If parsing was successful, this duration's units & value will have been 1172 * valued. Otherwise an exception is thrown. 1173 */ 1174 private void parseSimpleTimeDuration(String timeText) 1175 { 1176 timeText = timeText.trim(); 1177 1178 //If we have whitespace and all the other characters are digits, 1179 //then we're dealing with "hh mm ss.sss" or "mm ss.sss". This 1180 //method does not handle multipart text. 1181 //It is OK for us to have whitespace between the digits and 1182 //the units, though, as in "123.45 h". 1183 if (ANY_WHITESPACE.matcher(timeText).matches()) 1184 { 1185 if (!ANY_ALPHA.matcher(timeText).matches()) 1186 { 1187 throw new IllegalArgumentException("Cannot parse " + timeText); 1188 } 1189 } 1190 1191 //Quick exit if text represents infinity 1192 if (parseInfiniteDuration(timeText)) 1193 return; 1194 1195 //Eliminate whitespace 1196 timeText = timeText.replaceAll("\\s", ""); 1197 1198 //Assume we have a number followed (optionally) by a symbol 1199 units = null; 1200 1201 int unitsPos = -1; 1202 1203 //Sort units by length of symbol, longer symbols before shorter. 1204 //This helps w/ discovering which unit is contained in timeText. 1205 TimeUnits[] sortedUnits = TimeUnits.values(); 1206 Arrays.sort(sortedUnits, 1207 new Comparator<TimeUnits>() { 1208 public int compare(TimeUnits a, TimeUnits b) { 1209 return b.getSymbol().length() - a.getSymbol().length(); 1210 } 1211 }); 1212 1213 //Figure out what kind of units we have 1214 for (TimeUnits u : sortedUnits) 1215 { 1216 if (timeText.endsWith(u.getSymbol())) 1217 { 1218 units = u; 1219 unitsPos = timeText.lastIndexOf(u.getSymbol()); 1220 break; 1221 } 1222 } 1223 1224 //If unitsPos < 0, we either have no units or garbage. 1225 //The BigDecimal constructor will fail if it is garbage. 1226 //If we survive that parsing, we will assume default units. 1227 String numberString = 1228 (unitsPos < 0) ? timeText : timeText.substring(0, unitsPos); 1229 1230 setValue(new BigDecimal(numberString)); 1231 1232 //If we got this far, BigDecimal constructor was successful. 1233 //If the units are still null, use seconds. 1234 if (units == null) 1235 units = DEFAULT_UNITS; 1236 } 1237 1238 //TODO see if this can be generalized in EnumUtil, perhaps 1239 /** Returns <i>true</i> if parsed duration was infinite. */ 1240 private boolean parseInfiniteDuration(String timeText) 1241 { 1242 final String origText = timeText; //in case we need to throw exception 1243 1244 boolean isInfinite; 1245 1246 final String INF_TEXT = "infinity"; 1247 1248 char signChar = timeText.charAt(0); 1249 boolean negate = (signChar == '-'); 1250 boolean hasSignChar = negate || (signChar == '+'); 1251 1252 //Strip off "+" or "-" 1253 if (hasSignChar) 1254 timeText = timeText.substring(1); 1255 1256 int testLength = INF_TEXT.length(); 1257 int actualLength = timeText.length(); 1258 1259 //Might have "infinity" with no units 1260 if (actualLength == testLength) 1261 { 1262 isInfinite = timeText.equalsIgnoreCase(INF_TEXT); 1263 1264 if (isInfinite) 1265 set(MathUtil.getInfiniteValue(negate ? -1 : +1), DEFAULT_UNITS); 1266 } 1267 //Might have "infinity" followed by units 1268 else if (actualLength > testLength) 1269 { 1270 String testString = timeText.substring(0, testLength); 1271 1272 isInfinite = testString.equalsIgnoreCase(INF_TEXT); 1273 1274 if (isInfinite) 1275 { 1276 TimeUnits tu = 1277 TimeUnits.fromString(timeText.substring(testLength, actualLength)); 1278 1279 if (tu == null) 1280 throw new IllegalArgumentException("Could not parse '" + origText + 1281 "'. This looked like an infinite duration but units could not be determined."); 1282 1283 set(MathUtil.getInfiniteValue(negate ? -1 : +1), tu); 1284 } 1285 } 1286 //String too short to hold "infinity" 1287 else //actualLength < testLength 1288 { 1289 isInfinite = false; 1290 } 1291 1292 return isInfinite; 1293 } 1294 1295 //=========================================================================== 1296 // UTILITY METHODS 1297 //=========================================================================== 1298 1299 /** Returns a duration that is equal to this one. */ 1300 @Override 1301 public TimeDuration clone() 1302 { 1303 //Since this class has only primitive (& immutable) attributes, 1304 //the clone in Object is all we need. 1305 try 1306 { 1307 return (TimeDuration)super.clone(); 1308 } 1309 catch (CloneNotSupportedException ex) 1310 { 1311 //We'll never get here, but just in case... 1312 throw new RuntimeException(ex); 1313 } 1314 } 1315 1316 /** Returns <i>true</i> if {@code o} is equal to this duration. */ 1317 @Override 1318 public boolean equals(Object o) 1319 { 1320 //Quick exit if o is this 1321 if (o == this) 1322 return true; 1323 1324 //Quick exit if o is null 1325 if (o == null) 1326 return false; 1327 1328 //Quick exit if classes are different 1329 if (!o.getClass().equals(this.getClass())) 1330 return false; 1331 1332 TimeDuration other = (TimeDuration)o; 1333 1334 //Treat two infinite values of same sign as equal, 1335 //regardless of actual BigDecimal values 1336 if (isInfinite() && other.isInfinite()) 1337 return value.signum() == other.value.signum(); 1338 1339 //Ignore stored units; equality is based purely on magnitude in std units 1340 return compareTo(other) == 0; 1341 } 1342 1343 /** Returns a hash code value for this duration. */ 1344 @Override 1345 public int hashCode() 1346 { 1347 if (isInfinite()) 1348 return value.signum() > 0 ? "+infinity".hashCode() : "-infinity".hashCode(); 1349 1350 String crude = value.toPlainString() + units.getSymbol(); 1351 return crude.hashCode(); 1352 } 1353 1354 /** Compares this duration with the {@code otherDur} for order. */ 1355 public int compareTo(TimeDuration otherDur) 1356 { 1357 //Treat two infinite values of same sign as equal, 1358 //regardless of actual BigDecimal values 1359 if (isInfinite() && otherDur.isInfinite()) 1360 return value.signum() - otherDur.value.signum(); 1361 1362 //Avoid doing two unit conversions 1363 return value.compareTo(otherDur.toUnits(units)); 1364 } 1365 1366 //=========================================================================== 1367 // 1368 //=========================================================================== 1369 1370 /* 1371 public static void main(String[] args) 1372 { 1373 TimeDuration td = new TimeDuration(1.0, TimeUnits.HOUR); 1374 System.out.println(td.toUnits(TimeUnits.HOUR) + "hr = " + 1375 td.toUnits(TimeUnits.MINUTE) + "min = " + 1376 td.toUnits(TimeUnits.SECOND) + "sec = " + 1377 td.toUnits(TimeUnits.MILLISECOND) + "ms = " + 1378 td.toUnits(TimeUnits.MICROSECOND) + TimeUnits.MICROSECOND.getSymbol()); 1379 1380 td.set(12, 34, 48); 1381 System.out.println(td.toStringHms() + " = " + td.toUnits(TimeUnits.HOUR) + "hr"); 1382 1383 td.set(365 * 24, TimeUnits.HOUR); 1384 System.out.println(td.toStringHms() + " = " + td.toUnits(TimeUnits.HOUR) + "hr = " + 1385 td.toUnits(TimeUnits.DAY) +"day"); 1386 1387 TimeInterval ti = td.toIntervalEndingOn(new Date()); 1388 System.out.println(ti); 1389 1390 ti = td.toIntervalStartingOn(new Date()); 1391 System.out.println(ti); 1392 1393 TimeDuration td2 = new TimeDuration(); 1394 td.set(22,33,44); 1395 td2.set(33,44,55); 1396 System.out.println("td="+td.toStringHms()+", td2="+td2.toStringHms()); 1397 td.add(td2); 1398 System.out.println("td+td2="+td.toStringHms()); 1399 td.set(22,33,44); 1400 td2.subtract(td); 1401 System.out.println("td2-td="+td2.toStringHms()); 1402 }*/ 1403 1404 //This is here for quick & dirty testing 1405 /*public static void main(String args[]) 1406 { 1407 TimeDuration t1 = new TimeDuration(50.0, TimeUnits.HOUR); 1408 System.out.println("t1 = " + t1 + ", = " + t1.toStringHms() + ", = " + t1.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1409 t1.convertTo(TimeUnits.MINUTE); 1410 System.out.println("t1 = " + t1 + ", = " + t1.toStringHms() + ", = " + t1.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1411 t1.convertTo(TimeUnits.SECOND); 1412 System.out.println("t1 = " + t1 + ", = " + t1.toStringHms() + ", = " + t1.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1413 t1.convertTo(TimeUnits.MILLISECOND); 1414 System.out.println("t1 = " + t1 + ", = " + t1.toStringHms() + ", = " + t1.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1415 t1.convertTo(TimeUnits.HOUR); 1416 System.out.println("t1 = " + t1 + ", = " + t1.toStringHms() + ", = " + t1.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1417 1418 System.out.println(); 1419 1420 TimeDuration t2 = new TimeDuration(123456789.12345, TimeUnits.MILLISECOND); 1421 System.out.println("t2 = " + t2 + ", = " + t2.toStringHms() + ", = " + t2.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1422 t2.convertTo(TimeUnits.HOUR); 1423 System.out.println("t2 = " + t2 + ", = " + t2.toStringHms() + ", = " + t2.toUnits(TimeUnits.HOUR) + TimeUnits.HOUR.getSymbol()); 1424 t2.convertTo(TimeUnits.MINUTE); 1425 System.out.println("t2 = " + t2 + ", = " + t2.toStringHms() + ", = " + t2.toUnits(TimeUnits.MINUTE) + TimeUnits.MINUTE.getSymbol()); 1426 t2.convertTo(TimeUnits.SECOND); 1427 System.out.println("t2 = " + t2 + ", = " + t2.toStringHms() + ", = " + t2.toUnits(TimeUnits.SECOND) + TimeUnits.SECOND.getSymbol()); 1428 t2.convertTo(TimeUnits.MILLISECOND); 1429 System.out.println("t2 = " + t2 + ", = " + t2.toStringHms() + ", = " + t2.toUnits(TimeUnits.MILLISECOND) + TimeUnits.MILLISECOND.getSymbol()); 1430 }*/ 1431 /* 1432 public static void main(String args[]) 1433 { 1434 double seconds = 100000.0; 1435 1436 for (int i=1; i <= 14; i++) 1437 { 1438 TimeDuration td = new TimeDuration(seconds, TimeUnits.SECOND); 1439 td = td.normalize(); 1440 System.out.println("seconds = " + seconds + ", duration = " + td); 1441 seconds /= 10.0; 1442 } 1443 }*/ 1444 /* 1445 public static void main(String args[]) 1446 { 1447 //Deal w/ proper display when rounding HMS/DMS (ie, no 60 mins or secs) 1448 String[] text = 1449 { 1450 "0:00:59.95", "0:59:59.95", "99:59:59.95" 1451 }; 1452 1453 TimeDuration td; 1454 1455 for (int t=0; t < text.length; t++) 1456 { 1457 td = TimeDuration.parse(text[t]); 1458 System.out.print("text = " + text[t]); 1459 System.out.print(", ...Hms() = " + td.toStringHms()); 1460 System.out.print(", ...Hms(1,1) = " + td.toStringHms(1,1)); 1461 System.out.print(", toString() = " + td.toString()); 1462 System.out.println(); 1463 } 1464 } 1465 */ 1466 }