1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 /* 6 * Copyright 2005 The Apache Software Foundation. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xerces.internal.jaxp.datatype; 22 23 import java.io.IOException; 24 import java.io.ObjectStreamException; 25 import java.io.Serializable; 26 import java.math.BigDecimal; 27 import java.math.BigInteger; 28 import java.util.Calendar; 29 import java.util.Date; 30 import java.util.GregorianCalendar; 31 import java.util.TimeZone; 32 33 import javax.xml.datatype.DatatypeConstants; 34 import javax.xml.datatype.Duration; 35 import javax.xml.datatype.XMLGregorianCalendar; 36 37 import com.sun.org.apache.xerces.internal.util.DatatypeMessageFormatter; 38 39 /** 40 * <p>Immutable representation of a time span as defined in 41 * the W3C XML Schema 1.0 specification.</p> 42 * 43 * <p>A Duration object represents a period of Gregorian time, 44 * which consists of six fields (years, months, days, hours, 45 * minutes, and seconds) plus a sign (+/-) field.</p> 46 * 47 * <p>The first five fields have non-negative (>=0) integers or null 48 * (which represents that the field is not set), 49 * and the seconds field has a non-negative decimal or null. 50 * A negative sign indicates a negative duration.</p> 51 * 52 * <p>This class provides a number of methods that make it easy 53 * to use for the duration datatype of XML Schema 1.0 with 54 * the errata.</p> 55 * 56 * <h2>Order relationship</h2> 57 * <p>Duration objects only have partial order, where two values A and B 58 * maybe either:</p> 59 * <ol> 60 * <li>A<B (A is shorter than B) 61 * <li>A>B (A is longer than B) 62 * <li>A==B (A and B are of the same duration) 63 * <li>A<>B (Comparison between A and B is indeterminate) 64 * </ol> 65 * <p>For example, 30 days cannot be meaningfully compared to one month. 66 * The {@link #compare(Duration)} method implements this 67 * relationship.</p> 68 * 69 * <p>See the {@link #isLongerThan(Duration)} method for details about 70 * the order relationship among {@link Duration} objects.</p> 71 * 72 * 73 * 74 * <h2>Operations over Duration</h2> 75 * <p>This class provides a set of basic arithmetic operations, such 76 * as addition, subtraction and multiplication. 77 * Because durations don't have total order, an operation could 78 * fail for some combinations of operations. For example, you cannot 79 * subtract 15 days from 1 month. See the javadoc of those methods 80 * for detailed conditions where this could happen.</p> 81 * 82 * <p>Also, division of a duration by a number is not provided because 83 * the {@link Duration} class can only deal with finite precision 84 * decimal numbers. For example, one cannot represent 1 sec divided by 3.</p> 85 * 86 * <p>However, you could substitute a division by 3 with multiplying 87 * by numbers such as 0.3 or 0.333.</p> 88 * 89 * 90 * 91 * <h2>Range of allowed values</h2> 92 * <p> 93 * Because some operations of {@link Duration} rely on {@link Calendar} 94 * even though {@link Duration} can hold very large or very small values, 95 * some of the methods may not work correctly on such {@link Duration}s. 96 * The impacted methods document their dependency on {@link Calendar}. 97 * 98 * 99 * @author <a href="mailto:Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 100 * @author <a href="mailto:Joseph.Fialli@Sun.com">Joseph Fialli</a> 101 102 * @see XMLGregorianCalendar#add(Duration) 103 */ 104 class DurationImpl 105 extends Duration 106 implements Serializable { 107 108 /** 109 * <p>Number of Fields.</p> 110 */ 111 private static final int FIELD_NUM = 6; 112 113 /** 114 * <p>Internal array of value Fields.</p> 115 */ 116 private static final DatatypeConstants.Field[] FIELDS = new DatatypeConstants.Field[]{ 117 DatatypeConstants.YEARS, 118 DatatypeConstants.MONTHS, 119 DatatypeConstants.DAYS, 120 DatatypeConstants.HOURS, 121 DatatypeConstants.MINUTES, 122 DatatypeConstants.SECONDS 123 }; 124 125 /** 126 * <p>Internal array of value Field ids.</p> 127 */ 128 private static final int[] FIELD_IDS = { 129 DatatypeConstants.YEARS.getId(), 130 DatatypeConstants.MONTHS.getId(), 131 DatatypeConstants.DAYS.getId(), 132 DatatypeConstants.HOURS.getId(), 133 DatatypeConstants.MINUTES.getId(), 134 DatatypeConstants.SECONDS.getId() 135 }; 136 137 /** 138 * TimeZone for GMT. 139 */ 140 private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 141 142 /** 143 * <p>BigDecimal value of 0.</p> 144 */ 145 private static final BigDecimal ZERO = BigDecimal.valueOf((long) 0); 146 147 /** 148 * <p>Indicates the sign. -1, 0 or 1 if the duration is negative, 149 * zero, or positive.</p> 150 */ 151 protected int signum; 152 153 /** 154 * <p>Years of this <code>Duration</code>.</p> 155 */ 156 /** 157 * These were final since Duration is immutable. But new subclasses need 158 * to be able to set after conversion. It won't break the immutable nature 159 * of them since there's no other way to set new values to them 160 */ 161 protected BigInteger years; 162 163 /** 164 * <p>Months of this <code>Duration</code>.</p> 165 */ 166 protected BigInteger months; 167 168 /** 169 * <p>Days of this <code>Duration</code>.</p> 170 */ 171 protected BigInteger days; 172 173 /** 174 * <p>Hours of this <code>Duration</code>.</p> 175 */ 176 protected BigInteger hours; 177 178 /** 179 * <p>Minutes of this <code>Duration</code>.</p> 180 */ 181 protected BigInteger minutes; 182 183 /** 184 * <p>Seconds of this <code>Duration</code>.</p> 185 */ 186 protected BigDecimal seconds; 187 188 /** 189 * Returns the sign of this duration in -1,0, or 1. 190 * 191 * @return 192 * -1 if this duration is negative, 0 if the duration is zero, 193 * and 1 if the duration is postive. 194 */ 195 public int getSign() { 196 197 return signum; 198 } 199 200 /** 201 * TODO: Javadoc 202 * @param isPositive Sign. 203 * 204 * @return 1 if positive, else -1. 205 */ 206 protected int calcSignum(boolean isPositive) { 207 if ((years == null || years.signum() == 0) 208 && (months == null || months.signum() == 0) 209 && (days == null || days.signum() == 0) 210 && (hours == null || hours.signum() == 0) 211 && (minutes == null || minutes.signum() == 0) 212 && (seconds == null || seconds.signum() == 0)) { 213 return 0; 214 } 215 216 if (isPositive) { 217 return 1; 218 } else { 219 return -1; 220 } 221 222 } 223 224 /** 225 * <p>Constructs a new Duration object by specifying each field individually.</p> 226 * 227 * <p>All the parameters are optional as long as at least one field is present. 228 * If specified, parameters have to be zero or positive.</p> 229 * 230 * @param isPositive Set to <code>false</code> to create a negative duration. When the length 231 * of the duration is zero, this parameter will be ignored. 232 * @param years of this <code>Duration</code> 233 * @param months of this <code>Duration</code> 234 * @param days of this <code>Duration</code> 235 * @param hours of this <code>Duration</code> 236 * @param minutes of this <code>Duration</code> 237 * @param seconds of this <code>Duration</code> 238 * 239 * @throws IllegalArgumentException 240 * If years, months, days, hours, minutes and 241 * seconds parameters are all <code>null</code>. Or if any 242 * of those parameters are negative. 243 */ 244 protected DurationImpl( 245 boolean isPositive, 246 BigInteger years, 247 BigInteger months, 248 BigInteger days, 249 BigInteger hours, 250 BigInteger minutes, 251 BigDecimal seconds) { 252 253 this.years = years; 254 this.months = months; 255 this.days = days; 256 this.hours = hours; 257 this.minutes = minutes; 258 this.seconds = seconds; 259 260 this.signum = calcSignum(isPositive); 261 262 // sanity check 263 if (years == null 264 && months == null 265 && days == null 266 && hours == null 267 && minutes == null 268 && seconds == null) { 269 throw new IllegalArgumentException( 270 //"all the fields are null" 271 DatatypeMessageFormatter.formatMessage(null, "AllFieldsNull", null) 272 ); 273 } 274 testNonNegative(years, DatatypeConstants.YEARS); 275 testNonNegative(months, DatatypeConstants.MONTHS); 276 testNonNegative(days, DatatypeConstants.DAYS); 277 testNonNegative(hours, DatatypeConstants.HOURS); 278 testNonNegative(minutes, DatatypeConstants.MINUTES); 279 testNonNegative(seconds, DatatypeConstants.SECONDS); 280 } 281 282 /** 283 * <p>Makes sure that the given number is non-negative. If it is not, 284 * throw {@link IllegalArgumentException}.</p> 285 * 286 * @param n Number to test. 287 * @param f Field to test. 288 */ 289 protected static void testNonNegative(BigInteger n, DatatypeConstants.Field f) { 290 if (n != null && n.signum() < 0) { 291 throw new IllegalArgumentException( 292 DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) 293 ); 294 } 295 } 296 297 /** 298 * <p>Makes sure that the given number is non-negative. If it is not, 299 * throw {@link IllegalArgumentException}.</p> 300 * 301 * @param n Number to test. 302 * @param f Field to test. 303 */ 304 protected static void testNonNegative(BigDecimal n, DatatypeConstants.Field f) { 305 if (n != null && n.signum() < 0) { 306 307 throw new IllegalArgumentException( 308 DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) 309 ); 310 } 311 } 312 313 /** 314 * <p>Constructs a new Duration object by specifying each field 315 * individually.</p> 316 * 317 * <p>This method is functionally equivalent to 318 * invoking another constructor by wrapping 319 * all non-zero parameters into {@link BigInteger} and {@link BigDecimal}. 320 * Zero value of int parameter is equivalent of null value of 321 * the corresponding field.</p> 322 * 323 * @see #DurationImpl(boolean, BigInteger, BigInteger, BigInteger, BigInteger, 324 * BigInteger, BigDecimal) 325 */ 326 protected DurationImpl( 327 final boolean isPositive, 328 final int years, 329 final int months, 330 final int days, 331 final int hours, 332 final int minutes, 333 final int seconds) { 334 this( 335 isPositive, 336 wrap(years), 337 wrap(months), 338 wrap(days), 339 wrap(hours), 340 wrap(minutes), 341 seconds != DatatypeConstants.FIELD_UNDEFINED ? new BigDecimal(String.valueOf(seconds)) : null); 342 } 343 344 /** 345 * TODO: Javadoc 346 * 347 * @param i int to convert to BigInteger. 348 * 349 * @return BigInteger representation of int. 350 */ 351 protected static BigInteger wrap(final int i) { 352 353 // field may not be set 354 if (i == DatatypeConstants.FIELD_UNDEFINED) { 355 return null; 356 } 357 358 // int -> BigInteger 359 return new BigInteger(String.valueOf(i)); 360 } 361 362 /** 363 * <p>Constructs a new Duration object by specifying the duration 364 * in milliseconds.</p> 365 * 366 * @param durationInMilliSeconds 367 * The length of the duration in milliseconds. 368 */ 369 protected DurationImpl(final long durationInMilliSeconds) { 370 371 long l = durationInMilliSeconds; 372 373 if (l > 0) { 374 signum = 1; 375 } else if (l < 0) { 376 signum = -1; 377 if (l == 0x8000000000000000L) { 378 // negating 0x8000000000000000L causes an overflow 379 l++; 380 } 381 l *= -1; 382 } else { 383 signum = 0; 384 } 385 386 // let GregorianCalendar do the heavy lifting 387 GregorianCalendar gregorianCalendar = new GregorianCalendar(GMT); 388 389 // duration is the offset from the Epoch 390 gregorianCalendar.setTimeInMillis(l); 391 392 // now find out how much each field has changed 393 long int2long = 0L; 394 395 // years 396 int2long = gregorianCalendar.get(Calendar.YEAR) - 1970; 397 this.years = BigInteger.valueOf(int2long); 398 399 // months 400 int2long = gregorianCalendar.get(Calendar.MONTH); 401 this.months = BigInteger.valueOf(int2long); 402 403 // days 404 int2long = gregorianCalendar.get(Calendar.DAY_OF_MONTH) - 1; 405 this.days = BigInteger.valueOf(int2long); 406 407 // hours 408 int2long = gregorianCalendar.get(Calendar.HOUR_OF_DAY); 409 this.hours = BigInteger.valueOf(int2long); 410 411 // minutes 412 int2long = gregorianCalendar.get(Calendar.MINUTE); 413 this.minutes = BigInteger.valueOf(int2long); 414 415 // seconds & milliseconds 416 int2long = (gregorianCalendar.get(Calendar.SECOND) * 1000) 417 + gregorianCalendar.get(Calendar.MILLISECOND); 418 this.seconds = BigDecimal.valueOf(int2long, 3); 419 } 420 421 /** 422 * Constructs a new Duration object by 423 * parsing its string representation 424 * "PnYnMnDTnHnMnS" as defined in XML Schema 1.0 section 3.2.6.1. 425 * 426 * <p> 427 * The string representation may not have any leading 428 * and trailing whitespaces. 429 * 430 * <p> 431 * For example, this method parses strings like 432 * "P1D" (1 day), "-PT100S" (-100 sec.), "P1DT12H" (1 days and 12 hours). 433 * 434 * <p> 435 * The parsing is done field by field so that 436 * the following holds for any lexically correct string x: 437 * <pre> 438 * new Duration(x).toString().equals(x) 439 * </pre> 440 * 441 * Returns a non-null valid duration object that holds the value 442 * indicated by the lexicalRepresentation parameter. 443 * 444 * @param lexicalRepresentation 445 * Lexical representation of a duration. 446 * @throws IllegalArgumentException 447 * If the given string does not conform to the aforementioned 448 * specification. 449 * @throws NullPointerException 450 * If the given string is null. 451 */ 452 protected DurationImpl(String lexicalRepresentation) 453 throws IllegalArgumentException { 454 // only if I could use the JDK1.4 regular expression .... 455 456 final String s = lexicalRepresentation; 457 boolean positive; 458 int[] idx = new int[1]; 459 int length = s.length(); 460 boolean timeRequired = false; 461 462 if (lexicalRepresentation == null) { 463 throw new NullPointerException(); 464 } 465 466 idx[0] = 0; 467 if (length != idx[0] && s.charAt(idx[0]) == '-') { 468 idx[0]++; 469 positive = false; 470 } else { 471 positive = true; 472 } 473 474 if (length != idx[0] && s.charAt(idx[0]++) != 'P') { 475 throw new IllegalArgumentException(s); //,idx[0]-1); 476 } 477 478 479 // phase 1: chop the string into chunks 480 // (where a chunk is '<number><a symbol>' 481 //-------------------------------------- 482 int dateLen = 0; 483 String[] dateParts = new String[3]; 484 int[] datePartsIndex = new int[3]; 485 while (length != idx[0] 486 && isDigit(s.charAt(idx[0])) 487 && dateLen < 3) { 488 datePartsIndex[dateLen] = idx[0]; 489 dateParts[dateLen++] = parsePiece(s, idx); 490 } 491 492 if (length != idx[0]) { 493 if (s.charAt(idx[0]++) == 'T') { 494 timeRequired = true; 495 } else { 496 throw new IllegalArgumentException(s); // ,idx[0]-1); 497 } 498 } 499 500 int timeLen = 0; 501 String[] timeParts = new String[3]; 502 int[] timePartsIndex = new int[3]; 503 while (length != idx[0] 504 && isDigitOrPeriod(s.charAt(idx[0])) 505 && timeLen < 3) { 506 timePartsIndex[timeLen] = idx[0]; 507 timeParts[timeLen++] = parsePiece(s, idx); 508 } 509 510 if (timeRequired && timeLen == 0) { 511 throw new IllegalArgumentException(s); // ,idx[0]); 512 } 513 514 if (length != idx[0]) { 515 throw new IllegalArgumentException(s); // ,idx[0]); 516 } 517 if (dateLen == 0 && timeLen == 0) { 518 throw new IllegalArgumentException(s); // ,idx[0]); 519 } 520 521 // phase 2: check the ordering of chunks 522 //-------------------------------------- 523 organizeParts(s, dateParts, datePartsIndex, dateLen, "YMD"); 524 organizeParts(s, timeParts, timePartsIndex, timeLen, "HMS"); 525 526 // parse into numbers 527 years = parseBigInteger(s, dateParts[0], datePartsIndex[0]); 528 months = parseBigInteger(s, dateParts[1], datePartsIndex[1]); 529 days = parseBigInteger(s, dateParts[2], datePartsIndex[2]); 530 hours = parseBigInteger(s, timeParts[0], timePartsIndex[0]); 531 minutes = parseBigInteger(s, timeParts[1], timePartsIndex[1]); 532 seconds = parseBigDecimal(s, timeParts[2], timePartsIndex[2]); 533 signum = calcSignum(positive); 534 } 535 536 537 /** 538 * TODO: Javadoc 539 * 540 * @param ch char to test. 541 * 542 * @return true if ch is a digit, else false. 543 */ 544 private static boolean isDigit(char ch) { 545 return '0' <= ch && ch <= '9'; 546 } 547 548 /** 549 * TODO: Javadoc 550 * 551 * @param ch to test. 552 * 553 * @return true if ch is a digit or a period, else false. 554 */ 555 private static boolean isDigitOrPeriod(char ch) { 556 return isDigit(ch) || ch == '.'; 557 } 558 559 /** 560 * TODO: Javadoc 561 * 562 * @param whole String to parse. 563 * @param idx TODO: ??? 564 * 565 * @return Result of parsing. 566 * 567 * @throws IllegalArgumentException If whole cannot be parsed. 568 */ 569 private static String parsePiece(String whole, int[] idx) 570 throws IllegalArgumentException { 571 int start = idx[0]; 572 while (idx[0] < whole.length() 573 && isDigitOrPeriod(whole.charAt(idx[0]))) { 574 idx[0]++; 575 } 576 if (idx[0] == whole.length()) { 577 throw new IllegalArgumentException(whole); // ,idx[0]); 578 } 579 580 idx[0]++; 581 582 return whole.substring(start, idx[0]); 583 } 584 585 /** 586 * TODO: Javadoc. 587 * 588 * @param whole TODO: ??? 589 * @param parts TODO: ??? 590 * @param partsIndex TODO: ??? 591 * @param len TODO: ??? 592 * @param tokens TODO: ??? 593 * 594 * @throws IllegalArgumentException TODO: ??? 595 */ 596 private static void organizeParts( 597 String whole, 598 String[] parts, 599 int[] partsIndex, 600 int len, 601 String tokens) 602 throws IllegalArgumentException { 603 604 int idx = tokens.length(); 605 for (int i = len - 1; i >= 0; i--) { 606 int nidx = 607 tokens.lastIndexOf( 608 parts[i].charAt(parts[i].length() - 1), 609 idx - 1); 610 if (nidx == -1) { 611 throw new IllegalArgumentException(whole); 612 // ,partsIndex[i]+parts[i].length()-1); 613 } 614 615 for (int j = nidx + 1; j < idx; j++) { 616 parts[j] = null; 617 } 618 idx = nidx; 619 parts[idx] = parts[i]; 620 partsIndex[idx] = partsIndex[i]; 621 } 622 for (idx--; idx >= 0; idx--) { 623 parts[idx] = null; 624 } 625 } 626 627 /** 628 * TODO: Javadoc 629 * 630 * @param whole TODO: ???. 631 * @param part TODO: ???. 632 * @param index TODO: ???. 633 * 634 * @return TODO: ???. 635 * 636 * @throws IllegalArgumentException TODO: ???. 637 */ 638 private static BigInteger parseBigInteger( 639 String whole, 640 String part, 641 int index) 642 throws IllegalArgumentException { 643 if (part == null) { 644 return null; 645 } 646 part = part.substring(0, part.length() - 1); 647 // try { 648 return new BigInteger(part); 649 // } catch( NumberFormatException e ) { 650 // throw new ParseException( whole, index ); 651 // } 652 } 653 654 /** 655 * TODO: Javadoc. 656 * 657 * @param whole TODO: ???. 658 * @param part TODO: ???. 659 * @param index TODO: ???. 660 * 661 * @return TODO: ???. 662 * 663 * @throws IllegalArgumentException TODO: ???. 664 */ 665 private static BigDecimal parseBigDecimal( 666 String whole, 667 String part, 668 int index) 669 throws IllegalArgumentException { 670 if (part == null) { 671 return null; 672 } 673 part = part.substring(0, part.length() - 1); 674 // NumberFormatException is IllegalArgumentException 675 // try { 676 return new BigDecimal(part); 677 // } catch( NumberFormatException e ) { 678 // throw new ParseException( whole, index ); 679 // } 680 } 681 682 /** 683 * <p>Four constants defined for the comparison of durations.</p> 684 */ 685 private static final XMLGregorianCalendar[] TEST_POINTS = new XMLGregorianCalendar[] { 686 XMLGregorianCalendarImpl.parse("1696-09-01T00:00:00Z"), 687 XMLGregorianCalendarImpl.parse("1697-02-01T00:00:00Z"), 688 XMLGregorianCalendarImpl.parse("1903-03-01T00:00:00Z"), 689 XMLGregorianCalendarImpl.parse("1903-07-01T00:00:00Z") 690 }; 691 692 /** 693 * <p>Partial order relation comparison with this <code>Duration</code> instance.</p> 694 * 695 * <p>Comparison result must be in accordance with 696 * <a href="http://www.w3.org/TR/xmlschema-2/#duration-order">W3C XML Schema 1.0 Part 2, Section 3.2.7.6.2, 697 * <i>Order relation on duration</i></a>.</p> 698 * 699 * <p>Return:</p> 700 * <ul> 701 * <li>{@link DatatypeConstants#LESSER} if this <code>Duration</code> is shorter than <code>duration</code> parameter</li> 702 * <li>{@link DatatypeConstants#EQUAL} if this <code>Duration</code> is equal to <code>duration</code> parameter</li> 703 * <li>{@link DatatypeConstants#GREATER} if this <code>Duration</code> is longer than <code>duration</code> parameter</li> 704 * <li>{@link DatatypeConstants#INDETERMINATE} if a conclusive partial order relation cannot be determined</li> 705 * </ul> 706 * 707 * @param duration to compare 708 * 709 * @return the relationship between <code>this</code> <code>Duration</code>and <code>duration</code> parameter as 710 * {@link DatatypeConstants#LESSER}, {@link DatatypeConstants#EQUAL}, {@link DatatypeConstants#GREATER} 711 * or {@link DatatypeConstants#INDETERMINATE}. 712 * 713 * @throws UnsupportedOperationException If the underlying implementation 714 * cannot reasonably process the request, e.g. W3C XML Schema allows for 715 * arbitrarily large/small/precise values, the request may be beyond the 716 * implementations capability. 717 * @throws NullPointerException if <code>duration</code> is <code>null</code>. 718 * 719 * @see #isShorterThan(Duration) 720 * @see #isLongerThan(Duration) 721 */ 722 public int compare(Duration rhs) { 723 724 BigInteger maxintAsBigInteger = BigInteger.valueOf((long) Integer.MAX_VALUE); 725 BigInteger minintAsBigInteger = BigInteger.valueOf((long) Integer.MIN_VALUE); 726 727 // check for fields that are too large in this Duration 728 if (years != null && years.compareTo(maxintAsBigInteger) == 1) { 729 throw new UnsupportedOperationException( 730 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 731 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), years.toString()}) 732 //this.getClass().getName() + "#compare(Duration duration)" 733 //+ " years too large to be supported by this implementation " 734 //+ years.toString() 735 ); 736 } 737 if (months != null && months.compareTo(maxintAsBigInteger) == 1) { 738 throw new UnsupportedOperationException( 739 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 740 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MONTHS.toString(), months.toString()}) 741 742 //this.getClass().getName() + "#compare(Duration duration)" 743 //+ " months too large to be supported by this implementation " 744 //+ months.toString() 745 ); 746 } 747 if (days != null && days.compareTo(maxintAsBigInteger) == 1) { 748 throw new UnsupportedOperationException( 749 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 750 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.DAYS.toString(), days.toString()}) 751 752 //this.getClass().getName() + "#compare(Duration duration)" 753 //+ " days too large to be supported by this implementation " 754 //+ days.toString() 755 ); 756 } 757 if (hours != null && hours.compareTo(maxintAsBigInteger) == 1) { 758 throw new UnsupportedOperationException( 759 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 760 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.HOURS.toString(), hours.toString()}) 761 762 //this.getClass().getName() + "#compare(Duration duration)" 763 //+ " hours too large to be supported by this implementation " 764 //+ hours.toString() 765 ); 766 } 767 if (minutes != null && minutes.compareTo(maxintAsBigInteger) == 1) { 768 throw new UnsupportedOperationException( 769 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 770 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MINUTES.toString(), minutes.toString()}) 771 772 //this.getClass().getName() + "#compare(Duration duration)" 773 //+ " minutes too large to be supported by this implementation " 774 //+ minutes.toString() 775 ); 776 } 777 if (seconds != null && seconds.toBigInteger().compareTo(maxintAsBigInteger) == 1) { 778 throw new UnsupportedOperationException( 779 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 780 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.SECONDS.toString(), seconds.toString()}) 781 782 //this.getClass().getName() + "#compare(Duration duration)" 783 //+ " seconds too large to be supported by this implementation " 784 //+ seconds.toString() 785 ); 786 } 787 788 // check for fields that are too large in rhs Duration 789 BigInteger rhsYears = (BigInteger) rhs.getField(DatatypeConstants.YEARS); 790 if (rhsYears != null && rhsYears.compareTo(maxintAsBigInteger) == 1) { 791 throw new UnsupportedOperationException( 792 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 793 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), rhsYears.toString()}) 794 795 //this.getClass().getName() + "#compare(Duration duration)" 796 //+ " years too large to be supported by this implementation " 797 //+ rhsYears.toString() 798 ); 799 } 800 BigInteger rhsMonths = (BigInteger) rhs.getField(DatatypeConstants.MONTHS); 801 if (rhsMonths != null && rhsMonths.compareTo(maxintAsBigInteger) == 1) { 802 throw new UnsupportedOperationException( 803 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 804 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MONTHS.toString(), rhsMonths.toString()}) 805 806 //this.getClass().getName() + "#compare(Duration duration)" 807 //+ " months too large to be supported by this implementation " 808 //+ rhsMonths.toString() 809 ); 810 } 811 BigInteger rhsDays = (BigInteger) rhs.getField(DatatypeConstants.DAYS); 812 if (rhsDays != null && rhsDays.compareTo(maxintAsBigInteger) == 1) { 813 throw new UnsupportedOperationException( 814 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 815 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.DAYS.toString(), rhsDays.toString()}) 816 817 //this.getClass().getName() + "#compare(Duration duration)" 818 //+ " days too large to be supported by this implementation " 819 //+ rhsDays.toString() 820 ); 821 } 822 BigInteger rhsHours = (BigInteger) rhs.getField(DatatypeConstants.HOURS); 823 if (rhsHours != null && rhsHours.compareTo(maxintAsBigInteger) == 1) { 824 throw new UnsupportedOperationException( 825 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 826 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.HOURS.toString(), rhsHours.toString()}) 827 828 //this.getClass().getName() + "#compare(Duration duration)" 829 //+ " hours too large to be supported by this implementation " 830 //+ rhsHours.toString() 831 ); 832 } 833 BigInteger rhsMinutes = (BigInteger) rhs.getField(DatatypeConstants.MINUTES); 834 if (rhsMinutes != null && rhsMinutes.compareTo(maxintAsBigInteger) == 1) { 835 throw new UnsupportedOperationException( 836 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 837 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MINUTES.toString(), rhsMinutes.toString()}) 838 839 //this.getClass().getName() + "#compare(Duration duration)" 840 //+ " minutes too large to be supported by this implementation " 841 //+ rhsMinutes.toString() 842 ); 843 } 844 BigDecimal rhsSecondsAsBigDecimal = (BigDecimal) rhs.getField(DatatypeConstants.SECONDS); 845 BigInteger rhsSeconds = null; 846 if ( rhsSecondsAsBigDecimal != null ) { 847 rhsSeconds = rhsSecondsAsBigDecimal.toBigInteger(); 848 } 849 if (rhsSeconds != null && rhsSeconds.compareTo(maxintAsBigInteger) == 1) { 850 throw new UnsupportedOperationException( 851 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 852 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.SECONDS.toString(), rhsSeconds.toString()}) 853 854 //this.getClass().getName() + "#compare(Duration duration)" 855 //+ " seconds too large to be supported by this implementation " 856 //+ rhsSeconds.toString() 857 ); 858 } 859 860 // turn this Duration into a GregorianCalendar 861 GregorianCalendar lhsCalendar = new GregorianCalendar( 862 1970, 863 1, 864 1, 865 0, 866 0, 867 0); 868 lhsCalendar.add(GregorianCalendar.YEAR, getYears() * getSign()); 869 lhsCalendar.add(GregorianCalendar.MONTH, getMonths() * getSign()); 870 lhsCalendar.add(GregorianCalendar.DAY_OF_YEAR, getDays() * getSign()); 871 lhsCalendar.add(GregorianCalendar.HOUR_OF_DAY, getHours() * getSign()); 872 lhsCalendar.add(GregorianCalendar.MINUTE, getMinutes() * getSign()); 873 lhsCalendar.add(GregorianCalendar.SECOND, getSeconds() * getSign()); 874 875 // turn compare Duration into a GregorianCalendar 876 GregorianCalendar rhsCalendar = new GregorianCalendar( 877 1970, 878 1, 879 1, 880 0, 881 0, 882 0); 883 rhsCalendar.add(GregorianCalendar.YEAR, rhs.getYears() * rhs.getSign()); 884 rhsCalendar.add(GregorianCalendar.MONTH, rhs.getMonths() * rhs.getSign()); 885 rhsCalendar.add(GregorianCalendar.DAY_OF_YEAR, rhs.getDays() * rhs.getSign()); 886 rhsCalendar.add(GregorianCalendar.HOUR_OF_DAY, rhs.getHours() * rhs.getSign()); 887 rhsCalendar.add(GregorianCalendar.MINUTE, rhs.getMinutes() * rhs.getSign()); 888 rhsCalendar.add(GregorianCalendar.SECOND, rhs.getSeconds() * rhs.getSign()); 889 890 891 if (lhsCalendar.equals(rhsCalendar)) { 892 return DatatypeConstants.EQUAL; 893 } 894 895 return compareDates(this, rhs); 896 } 897 898 /** 899 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration") 900 * 901 * @param duration1 Unnormalized duration 902 * @param duration2 Unnormalized duration 903 * @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate. 904 * EQUAL if the order relation between date1 and date2 is EQUAL. 905 * If the strict parameter is true, return LESS_THAN if date1 is less than date2 and 906 * return GREATER_THAN if date1 is greater than date2. 907 * If the strict parameter is false, return LESS_THAN if date1 is less than OR equal to date2 and 908 * return GREATER_THAN if date1 is greater than OR equal to date2 909 */ 910 private int compareDates(Duration duration1, Duration duration2) { 911 912 int resultA = DatatypeConstants.INDETERMINATE; 913 int resultB = DatatypeConstants.INDETERMINATE; 914 915 XMLGregorianCalendar tempA = (XMLGregorianCalendar)TEST_POINTS[0].clone(); 916 XMLGregorianCalendar tempB = (XMLGregorianCalendar)TEST_POINTS[0].clone(); 917 918 //long comparison algorithm is required 919 tempA.add(duration1); 920 tempB.add(duration2); 921 resultA = tempA.compare(tempB); 922 if ( resultA == DatatypeConstants.INDETERMINATE ) { 923 return DatatypeConstants.INDETERMINATE; 924 } 925 926 tempA = (XMLGregorianCalendar)TEST_POINTS[1].clone(); 927 tempB = (XMLGregorianCalendar)TEST_POINTS[1].clone(); 928 929 tempA.add(duration1); 930 tempB.add(duration2); 931 resultB = tempA.compare(tempB); 932 resultA = compareResults(resultA, resultB); 933 if (resultA == DatatypeConstants.INDETERMINATE) { 934 return DatatypeConstants.INDETERMINATE; 935 } 936 937 tempA = (XMLGregorianCalendar)TEST_POINTS[2].clone(); 938 tempB = (XMLGregorianCalendar)TEST_POINTS[2].clone(); 939 940 tempA.add(duration1); 941 tempB.add(duration2); 942 resultB = tempA.compare(tempB); 943 resultA = compareResults(resultA, resultB); 944 if (resultA == DatatypeConstants.INDETERMINATE) { 945 return DatatypeConstants.INDETERMINATE; 946 } 947 948 tempA = (XMLGregorianCalendar)TEST_POINTS[3].clone(); 949 tempB = (XMLGregorianCalendar)TEST_POINTS[3].clone(); 950 951 tempA.add(duration1); 952 tempB.add(duration2); 953 resultB = tempA.compare(tempB); 954 resultA = compareResults(resultA, resultB); 955 956 return resultA; 957 } 958 959 private int compareResults(int resultA, int resultB){ 960 961 if ( resultB == DatatypeConstants.INDETERMINATE ) { 962 return DatatypeConstants.INDETERMINATE; 963 } 964 else if ( resultA!=resultB) { 965 return DatatypeConstants.INDETERMINATE; 966 } 967 return resultA; 968 } 969 970 /** 971 * Returns a hash code consistent with the definition of the equals method. 972 * 973 * @see Object#hashCode() 974 */ 975 public int hashCode() { 976 // component wise hash is not correct because 1day = 24hours 977 Calendar cal = TEST_POINTS[0].toGregorianCalendar(); 978 this.addTo(cal); 979 return (int) getCalendarTimeInMillis(cal); 980 } 981 982 /** 983 * Returns a string representation of this duration object. 984 * 985 * <p> 986 * The result is formatter according to the XML Schema 1.0 987 * spec and can be always parsed back later into the 988 * equivalent duration object by 989 * the {@link #DurationImpl(String)} constructor. 990 * 991 * <p> 992 * Formally, the following holds for any {@link Duration} 993 * object x. 994 * <pre> 995 * new Duration(x.toString()).equals(x) 996 * </pre> 997 * 998 * @return 999 * Always return a non-null valid String object. 1000 */ 1001 public String toString() { 1002 StringBuffer buf = new StringBuffer(); 1003 if (signum < 0) { 1004 buf.append('-'); 1005 } 1006 buf.append('P'); 1007 1008 if (years != null) { 1009 buf.append(years + "Y"); 1010 } 1011 if (months != null) { 1012 buf.append(months + "M"); 1013 } 1014 if (days != null) { 1015 buf.append(days + "D"); 1016 } 1017 1018 if (hours != null || minutes != null || seconds != null) { 1019 buf.append('T'); 1020 if (hours != null) { 1021 buf.append(hours + "H"); 1022 } 1023 if (minutes != null) { 1024 buf.append(minutes + "M"); 1025 } 1026 if (seconds != null) { 1027 buf.append(toString(seconds) + "S"); 1028 } 1029 } 1030 1031 return buf.toString(); 1032 } 1033 1034 /** 1035 * <p>Turns {@link BigDecimal} to a string representation.</p> 1036 * 1037 * <p>Due to a behavior change in the {@link BigDecimal#toString()} 1038 * method in JDK1.5, this had to be implemented here.</p> 1039 * 1040 * @param bd <code>BigDecimal</code> to format as a <code>String</code> 1041 * 1042 * @return <code>String</code> representation of <code>BigDecimal</code> 1043 */ 1044 private String toString(BigDecimal bd) { 1045 String intString = bd.unscaledValue().toString(); 1046 int scale = bd.scale(); 1047 1048 if (scale == 0) { 1049 return intString; 1050 } 1051 1052 /* Insert decimal point */ 1053 StringBuffer buf; 1054 int insertionPoint = intString.length() - scale; 1055 if (insertionPoint == 0) { /* Point goes right before intVal */ 1056 return "0." + intString; 1057 } else if (insertionPoint > 0) { /* Point goes inside intVal */ 1058 buf = new StringBuffer(intString); 1059 buf.insert(insertionPoint, '.'); 1060 } else { /* We must insert zeros between point and intVal */ 1061 buf = new StringBuffer(3 - insertionPoint + intString.length()); 1062 buf.append("0."); 1063 for (int i = 0; i < -insertionPoint; i++) { 1064 buf.append('0'); 1065 } 1066 buf.append(intString); 1067 } 1068 return buf.toString(); 1069 } 1070 1071 /** 1072 * Checks if a field is set. 1073 * 1074 * A field of a duration object may or may not be present. 1075 * This method can be used to test if a field is present. 1076 * 1077 * @param field 1078 * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, 1079 * MINUTES, or SECONDS.) 1080 * @return 1081 * true if the field is present. false if not. 1082 * 1083 * @throws NullPointerException 1084 * If the field parameter is null. 1085 */ 1086 public boolean isSet(DatatypeConstants.Field field) { 1087 1088 if (field == null) { 1089 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)" ; 1090 throw new NullPointerException( 1091 //"cannot be called with field == null" 1092 DatatypeMessageFormatter.formatMessage(null, "FieldCannotBeNull", new Object[]{methodName}) 1093 ); 1094 } 1095 1096 if (field == DatatypeConstants.YEARS) { 1097 return years != null; 1098 } 1099 1100 if (field == DatatypeConstants.MONTHS) { 1101 return months != null; 1102 } 1103 1104 if (field == DatatypeConstants.DAYS) { 1105 return days != null; 1106 } 1107 1108 if (field == DatatypeConstants.HOURS) { 1109 return hours != null; 1110 } 1111 1112 if (field == DatatypeConstants.MINUTES) { 1113 return minutes != null; 1114 } 1115 1116 if (field == DatatypeConstants.SECONDS) { 1117 return seconds != null; 1118 } 1119 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)"; 1120 1121 throw new IllegalArgumentException( 1122 DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) 1123 ); 1124 1125 } 1126 1127 /** 1128 * Gets the value of a field. 1129 * 1130 * Fields of a duration object may contain arbitrary large value. 1131 * Therefore this method is designed to return a {@link Number} object. 1132 * 1133 * In case of YEARS, MONTHS, DAYS, HOURS, and MINUTES, the returned 1134 * number will be a non-negative integer. In case of seconds, 1135 * the returned number may be a non-negative decimal value. 1136 * 1137 * @param field 1138 * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, 1139 * MINUTES, or SECONDS.) 1140 * @return 1141 * If the specified field is present, this method returns 1142 * a non-null non-negative {@link Number} object that 1143 * represents its value. If it is not present, return null. 1144 * For YEARS, MONTHS, DAYS, HOURS, and MINUTES, this method 1145 * returns a {@link BigInteger} object. For SECONDS, this 1146 * method returns a {@link BigDecimal}. 1147 * 1148 * @throws NullPointerException 1149 * If the field parameter is null. 1150 */ 1151 public Number getField(DatatypeConstants.Field field) { 1152 1153 if (field == null) { 1154 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field) " ; 1155 1156 throw new NullPointerException( 1157 DatatypeMessageFormatter.formatMessage(null,"FieldCannotBeNull", new Object[]{methodName}) 1158 ); 1159 } 1160 1161 if (field == DatatypeConstants.YEARS) { 1162 return years; 1163 } 1164 1165 if (field == DatatypeConstants.MONTHS) { 1166 return months; 1167 } 1168 1169 if (field == DatatypeConstants.DAYS) { 1170 return days; 1171 } 1172 1173 if (field == DatatypeConstants.HOURS) { 1174 return hours; 1175 } 1176 1177 if (field == DatatypeConstants.MINUTES) { 1178 return minutes; 1179 } 1180 1181 if (field == DatatypeConstants.SECONDS) { 1182 return seconds; 1183 } 1184 /** 1185 throw new IllegalArgumentException( 1186 "javax.xml.datatype.Duration" 1187 + "#(getSet(DatatypeConstants.Field field) called with an unknown field: " 1188 + field.toString() 1189 ); 1190 */ 1191 String methodName = "javax.xml.datatype.Duration" + "#(getSet(DatatypeConstants.Field field)"; 1192 1193 throw new IllegalArgumentException( 1194 DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) 1195 ); 1196 1197 } 1198 1199 /** 1200 * Obtains the value of the YEARS field as an integer value, 1201 * or 0 if not present. 1202 * 1203 * <p> 1204 * This method is a convenience method around the 1205 * {@link #getField(DatatypeConstants.Field)} method. 1206 * 1207 * <p> 1208 * Note that since this method returns <tt>int</tt>, this 1209 * method will return an incorrect value for {@link Duration}s 1210 * with the year field that goes beyond the range of <tt>int</tt>. 1211 * Use <code>getField(YEARS)</code> to avoid possible loss of precision.</p> 1212 * 1213 * @return 1214 * If the YEARS field is present, return 1215 * its value as an integer by using the {@link Number#intValue()} 1216 * method. If the YEARS field is not present, return 0. 1217 */ 1218 public int getYears() { 1219 return getInt(DatatypeConstants.YEARS); 1220 } 1221 1222 /** 1223 * Obtains the value of the MONTHS field as an integer value, 1224 * or 0 if not present. 1225 * 1226 * This method works just like {@link #getYears()} except 1227 * that this method works on the MONTHS field. 1228 * 1229 * @return Months of this <code>Duration</code>. 1230 */ 1231 public int getMonths() { 1232 return getInt(DatatypeConstants.MONTHS); 1233 } 1234 1235 /** 1236 * Obtains the value of the DAYS field as an integer value, 1237 * or 0 if not present. 1238 * 1239 * This method works just like {@link #getYears()} except 1240 * that this method works on the DAYS field. 1241 * 1242 * @return Days of this <code>Duration</code>. 1243 */ 1244 public int getDays() { 1245 return getInt(DatatypeConstants.DAYS); 1246 } 1247 1248 /** 1249 * Obtains the value of the HOURS field as an integer value, 1250 * or 0 if not present. 1251 * 1252 * This method works just like {@link #getYears()} except 1253 * that this method works on the HOURS field. 1254 * 1255 * @return Hours of this <code>Duration</code>. 1256 * 1257 */ 1258 public int getHours() { 1259 return getInt(DatatypeConstants.HOURS); 1260 } 1261 1262 /** 1263 * Obtains the value of the MINUTES field as an integer value, 1264 * or 0 if not present. 1265 * 1266 * This method works just like {@link #getYears()} except 1267 * that this method works on the MINUTES field. 1268 * 1269 * @return Minutes of this <code>Duration</code>. 1270 * 1271 */ 1272 public int getMinutes() { 1273 return getInt(DatatypeConstants.MINUTES); 1274 } 1275 1276 /** 1277 * Obtains the value of the SECONDS field as an integer value, 1278 * or 0 if not present. 1279 * 1280 * This method works just like {@link #getYears()} except 1281 * that this method works on the SECONDS field. 1282 * 1283 * @return seconds in the integer value. The fraction of seconds 1284 * will be discarded (for example, if the actual value is 2.5, 1285 * this method returns 2) 1286 */ 1287 public int getSeconds() { 1288 return getInt(DatatypeConstants.SECONDS); 1289 } 1290 1291 /** 1292 * <p>Return the requested field value as an int.</p> 1293 * 1294 * <p>If field is not set, i.e. == null, 0 is returned.</p> 1295 * 1296 * @param field To get value for. 1297 * 1298 * @return int value of field or 0 if field is not set. 1299 */ 1300 private int getInt(DatatypeConstants.Field field) { 1301 Number n = getField(field); 1302 if (n == null) { 1303 return 0; 1304 } else { 1305 return n.intValue(); 1306 } 1307 } 1308 1309 /** 1310 * <p>Returns the length of the duration in milli-seconds.</p> 1311 * 1312 * <p>If the seconds field carries more digits than milli-second order, 1313 * those will be simply discarded (or in other words, rounded to zero.) 1314 * For example, for any Calendar value <code>x<code>,</p> 1315 * <pre> 1316 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. 1317 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. 1318 * </pre> 1319 * 1320 * <p> 1321 * Note that this method uses the {@link #addTo(Calendar)} method, 1322 * which may work incorectly with {@link Duration} objects with 1323 * very large values in its fields. See the {@link #addTo(Calendar)} 1324 * method for details. 1325 * 1326 * @param startInstant 1327 * The length of a month/year varies. The <code>startInstant</code> is 1328 * used to disambiguate this variance. Specifically, this method 1329 * returns the difference between <code>startInstant</code> and 1330 * <code>startInstant+duration</code> 1331 * 1332 * @return milliseconds between <code>startInstant</code> and 1333 * <code>startInstant</code> plus this <code>Duration</code> 1334 * 1335 * @throws NullPointerException if <code>startInstant</code> parameter 1336 * is null. 1337 * 1338 */ 1339 public long getTimeInMillis(final Calendar startInstant) { 1340 Calendar cal = (Calendar) startInstant.clone(); 1341 addTo(cal); 1342 return getCalendarTimeInMillis(cal) 1343 - getCalendarTimeInMillis(startInstant); 1344 } 1345 1346 /** 1347 * <p>Returns the length of the duration in milli-seconds.</p> 1348 * 1349 * <p>If the seconds field carries more digits than milli-second order, 1350 * those will be simply discarded (or in other words, rounded to zero.) 1351 * For example, for any <code>Date</code> value <code>x<code>,</p> 1352 * <pre> 1353 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. 1354 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. 1355 * </pre> 1356 * 1357 * <p> 1358 * Note that this method uses the {@link #addTo(Date)} method, 1359 * which may work incorectly with {@link Duration} objects with 1360 * very large values in its fields. See the {@link #addTo(Date)} 1361 * method for details. 1362 * 1363 * @param startInstant 1364 * The length of a month/year varies. The <code>startInstant</code> is 1365 * used to disambiguate this variance. Specifically, this method 1366 * returns the difference between <code>startInstant</code> and 1367 * <code>startInstant+duration</code>. 1368 * 1369 * @throws NullPointerException 1370 * If the startInstant parameter is null. 1371 * 1372 * @return milliseconds between <code>startInstant</code> and 1373 * <code>startInstant</code> plus this <code>Duration</code> 1374 * 1375 * @see #getTimeInMillis(Calendar) 1376 */ 1377 public long getTimeInMillis(final Date startInstant) { 1378 Calendar cal = new GregorianCalendar(); 1379 cal.setTime(startInstant); 1380 this.addTo(cal); 1381 return getCalendarTimeInMillis(cal) - startInstant.getTime(); 1382 } 1383 1384 // /** 1385 // * Returns an equivalent but "normalized" duration value. 1386 // * 1387 // * Intuitively, the normalization moves YEARS into 1388 // * MONTHS (by x12) and moves DAYS, HOURS, and MINUTES fields 1389 // * into SECONDS (by x86400, x3600, and x60 respectively.) 1390 // * 1391 // * 1392 // * Formally, this method satisfies the following conditions: 1393 // * <ul> 1394 // * <li>x.normalize().equals(x) 1395 // * <li>!x.normalize().isSet(Duration.YEARS) 1396 // * <li>!x.normalize().isSet(Duration.DAYS) 1397 // * <li>!x.normalize().isSet(Duration.HOURS) 1398 // * <li>!x.normalize().isSet(Duration.MINUTES) 1399 // * </ul> 1400 // * 1401 // * @return 1402 // * always return a non-null valid value. 1403 // */ 1404 // public Duration normalize() { 1405 // return null; 1406 // } 1407 1408 /** 1409 * <p>Converts the years and months fields into the days field 1410 * by using a specific time instant as the reference point.</p> 1411 * 1412 * <p>For example, duration of one month normalizes to 31 days 1413 * given the start time instance "July 8th 2003, 17:40:32".</p> 1414 * 1415 * <p>Formally, the computation is done as follows:</p> 1416 * <ol> 1417 * <li>The given Calendar object is cloned. 1418 * <li>The years, months and days fields will be added to 1419 * the {@link Calendar} object 1420 * by using the {@link Calendar#add(int,int)} method. 1421 * <li>The difference between two Calendars are computed in terms of days. 1422 * <li>The computed days, along with the hours, minutes and seconds 1423 * fields of this duration object is used to construct a new 1424 * Duration object. 1425 * </ol> 1426 * 1427 * <p>Note that since the Calendar class uses <code>int</code> to 1428 * hold the value of year and month, this method may produce 1429 * an unexpected result if this duration object holds 1430 * a very large value in the years or months fields.</p> 1431 * 1432 * @param startTimeInstant <code>Calendar</code> reference point. 1433 * 1434 * @return <code>Duration</code> of years and months of this <code>Duration</code> as days. 1435 * 1436 * @throws NullPointerException If the startTimeInstant parameter is null. 1437 */ 1438 public Duration normalizeWith(Calendar startTimeInstant) { 1439 1440 Calendar c = (Calendar) startTimeInstant.clone(); 1441 1442 // using int may cause overflow, but 1443 // Calendar internally treats value as int anyways. 1444 c.add(Calendar.YEAR, getYears() * signum); 1445 c.add(Calendar.MONTH, getMonths() * signum); 1446 c.add(Calendar.DAY_OF_MONTH, getDays() * signum); 1447 1448 // obtain the difference in terms of days 1449 long diff = getCalendarTimeInMillis(c) - getCalendarTimeInMillis(startTimeInstant); 1450 int days = (int) (diff / (1000L * 60L * 60L * 24L)); 1451 1452 return new DurationImpl( 1453 days >= 0, 1454 null, 1455 null, 1456 wrap(Math.abs(days)), 1457 (BigInteger) getField(DatatypeConstants.HOURS), 1458 (BigInteger) getField(DatatypeConstants.MINUTES), 1459 (BigDecimal) getField(DatatypeConstants.SECONDS)); 1460 } 1461 1462 /** 1463 * <p>Computes a new duration whose value is <code>factor</code> times 1464 * longer than the value of this duration.</p> 1465 * 1466 * <p>This method is provided for the convenience. 1467 * It is functionally equivalent to the following code:</p> 1468 * <pre> 1469 * multiply(new BigDecimal(String.valueOf(factor))) 1470 * </pre> 1471 * 1472 * @param factor Factor times longer of new <code>Duration</code> to create. 1473 * 1474 * @return New <code>Duration</code> that is <code>factor</code>times longer than this <code>Duration</code>. 1475 * 1476 * @see #multiply(BigDecimal) 1477 */ 1478 public Duration multiply(int factor) { 1479 return multiply(BigDecimal.valueOf(factor)); 1480 } 1481 1482 /** 1483 * Computes a new duration whose value is <code>factor</code> times 1484 * longer than the value of this duration. 1485 * 1486 * <p> 1487 * For example, 1488 * <pre> 1489 * "P1M" (1 month) * "12" = "P12M" (12 months) 1490 * "PT1M" (1 min) * "0.3" = "PT18S" (18 seconds) 1491 * "P1M" (1 month) * "1.5" = IllegalStateException 1492 * </pre> 1493 * 1494 * <p> 1495 * Since the {@link Duration} class is immutable, this method 1496 * doesn't change the value of this object. It simply computes 1497 * a new Duration object and returns it. 1498 * 1499 * <p> 1500 * The operation will be performed field by field with the precision 1501 * of {@link BigDecimal}. Since all the fields except seconds are 1502 * restricted to hold integers, 1503 * any fraction produced by the computation will be 1504 * carried down toward the next lower unit. For example, 1505 * if you multiply "P1D" (1 day) with "0.5", then it will be 0.5 day, 1506 * which will be carried down to "PT12H" (12 hours). 1507 * When fractions of month cannot be meaningfully carried down 1508 * to days, or year to months, this will cause an 1509 * {@link IllegalStateException} to be thrown. 1510 * For example if you multiple one month by 0.5.</p> 1511 * 1512 * <p> 1513 * To avoid {@link IllegalStateException}, use 1514 * the {@link #normalizeWith(Calendar)} method to remove the years 1515 * and months fields. 1516 * 1517 * @param factor to multiply by 1518 * 1519 * @return 1520 * returns a non-null valid {@link Duration} object 1521 * 1522 * @throws IllegalStateException if operation produces fraction in 1523 * the months field. 1524 * 1525 * @throws NullPointerException if the <code>factor</code> parameter is 1526 * <code>null</code>. 1527 * 1528 */ 1529 public Duration multiply(BigDecimal factor) { 1530 BigDecimal carry = ZERO; 1531 int factorSign = factor.signum(); 1532 factor = factor.abs(); 1533 1534 BigDecimal[] buf = new BigDecimal[6]; 1535 1536 for (int i = 0; i < 5; i++) { 1537 BigDecimal bd = getFieldAsBigDecimal(FIELDS[i]); 1538 bd = bd.multiply(factor).add(carry); 1539 1540 buf[i] = bd.setScale(0, BigDecimal.ROUND_DOWN); 1541 1542 bd = bd.subtract(buf[i]); 1543 if (i == 1) { 1544 if (bd.signum() != 0) { 1545 throw new IllegalStateException(); // illegal carry-down 1546 } else { 1547 carry = ZERO; 1548 } 1549 } else { 1550 carry = bd.multiply(FACTORS[i]); 1551 } 1552 } 1553 1554 if (seconds != null) { 1555 buf[5] = seconds.multiply(factor).add(carry); 1556 } else { 1557 buf[5] = carry; 1558 } 1559 1560 return new DurationImpl( 1561 this.signum * factorSign >= 0, 1562 toBigInteger(buf[0], null == years), 1563 toBigInteger(buf[1], null == months), 1564 toBigInteger(buf[2], null == days), 1565 toBigInteger(buf[3], null == hours), 1566 toBigInteger(buf[4], null == minutes), 1567 (buf[5].signum() == 0 && seconds == null) ? null : buf[5]); 1568 } 1569 1570 /** 1571 * <p>Gets the value of the field as a {@link BigDecimal}.</p> 1572 * 1573 * <p>If the field is unset, return 0.</p> 1574 * 1575 * @param f Field to get value for. 1576 * 1577 * @return non-null valid {@link BigDecimal}. 1578 */ 1579 private BigDecimal getFieldAsBigDecimal(DatatypeConstants.Field f) { 1580 if (f == DatatypeConstants.SECONDS) { 1581 if (seconds != null) { 1582 return seconds; 1583 } else { 1584 return ZERO; 1585 } 1586 } else { 1587 BigInteger bi = (BigInteger) getField(f); 1588 if (bi == null) { 1589 return ZERO; 1590 } else { 1591 return new BigDecimal(bi); 1592 } 1593 } 1594 } 1595 1596 /** 1597 * <p>BigInteger value of BigDecimal value.</p> 1598 * 1599 * @param value Value to convert. 1600 * @param canBeNull Can returned value be null? 1601 * 1602 * @return BigInteger value of BigDecimal, possibly null. 1603 */ 1604 private static BigInteger toBigInteger( 1605 BigDecimal value, 1606 boolean canBeNull) { 1607 if (canBeNull && value.signum() == 0) { 1608 return null; 1609 } else { 1610 return value.unscaledValue(); 1611 } 1612 } 1613 1614 /** 1615 * 1 unit of FIELDS[i] is equivalent to <code>FACTORS[i]</code> unit of 1616 * FIELDS[i+1]. 1617 */ 1618 private static final BigDecimal[] FACTORS = new BigDecimal[]{ 1619 BigDecimal.valueOf(12), 1620 null/*undefined*/, 1621 BigDecimal.valueOf(24), 1622 BigDecimal.valueOf(60), 1623 BigDecimal.valueOf(60) 1624 }; 1625 1626 /** 1627 * <p>Computes a new duration whose value is <code>this+rhs</code>.</p> 1628 * 1629 * <p>For example,</p> 1630 * <pre> 1631 * "1 day" + "-3 days" = "-2 days" 1632 * "1 year" + "1 day" = "1 year and 1 day" 1633 * "-(1 hour,50 minutes)" + "-20 minutes" = "-(1 hours,70 minutes)" 1634 * "15 hours" + "-3 days" = "-(2 days,9 hours)" 1635 * "1 year" + "-1 day" = IllegalStateException 1636 * </pre> 1637 * 1638 * <p>Since there's no way to meaningfully subtract 1 day from 1 month, 1639 * there are cases where the operation fails in 1640 * {@link IllegalStateException}.</p> 1641 * 1642 * <p> 1643 * Formally, the computation is defined as follows.</p> 1644 * <p> 1645 * Firstly, we can assume that two {@link Duration}s to be added 1646 * are both positive without losing generality (i.e., 1647 * <code>(-X)+Y=Y-X</code>, <code>X+(-Y)=X-Y</code>, 1648 * <code>(-X)+(-Y)=-(X+Y)</code>) 1649 * 1650 * <p> 1651 * Addition of two positive {@link Duration}s are simply defined as 1652 * field by field addition where missing fields are treated as 0. 1653 * <p> 1654 * A field of the resulting {@link Duration} will be unset if and 1655 * only if respective fields of two input {@link Duration}s are unset. 1656 * <p> 1657 * Note that <code>lhs.add(rhs)</code> will be always successful if 1658 * <code>lhs.signum()*rhs.signum()!=-1</code> or both of them are 1659 * normalized.</p> 1660 * 1661 * @param rhs <code>Duration</code> to add to this <code>Duration</code> 1662 * 1663 * @return 1664 * non-null valid Duration object. 1665 * 1666 * @throws NullPointerException 1667 * If the rhs parameter is null. 1668 * @throws IllegalStateException 1669 * If two durations cannot be meaningfully added. For 1670 * example, adding negative one day to one month causes 1671 * this exception. 1672 * 1673 * 1674 * @see #subtract(Duration) 1675 */ 1676 public Duration add(final Duration rhs) { 1677 Duration lhs = this; 1678 BigDecimal[] buf = new BigDecimal[6]; 1679 1680 buf[0] = sanitize((BigInteger) lhs.getField(DatatypeConstants.YEARS), 1681 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.YEARS), rhs.getSign())); 1682 buf[1] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MONTHS), 1683 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MONTHS), rhs.getSign())); 1684 buf[2] = sanitize((BigInteger) lhs.getField(DatatypeConstants.DAYS), 1685 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.DAYS), rhs.getSign())); 1686 buf[3] = sanitize((BigInteger) lhs.getField(DatatypeConstants.HOURS), 1687 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.HOURS), rhs.getSign())); 1688 buf[4] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MINUTES), 1689 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MINUTES), rhs.getSign())); 1690 buf[5] = sanitize((BigDecimal) lhs.getField(DatatypeConstants.SECONDS), 1691 lhs.getSign()).add(sanitize((BigDecimal) rhs.getField(DatatypeConstants.SECONDS), rhs.getSign())); 1692 1693 // align sign 1694 alignSigns(buf, 0, 2); // Y,M 1695 alignSigns(buf, 2, 6); // D,h,m,s 1696 1697 // make sure that the sign bit is consistent across all 6 fields. 1698 int s = 0; 1699 for (int i = 0; i < 6; i++) { 1700 if (s * buf[i].signum() < 0) { 1701 throw new IllegalStateException(); 1702 } 1703 if (s == 0) { 1704 s = buf[i].signum(); 1705 } 1706 } 1707 1708 return new DurationImpl( 1709 s >= 0, 1710 toBigInteger(sanitize(buf[0], s), 1711 lhs.getField(DatatypeConstants.YEARS) == null && rhs.getField(DatatypeConstants.YEARS) == null), 1712 toBigInteger(sanitize(buf[1], s), 1713 lhs.getField(DatatypeConstants.MONTHS) == null && rhs.getField(DatatypeConstants.MONTHS) == null), 1714 toBigInteger(sanitize(buf[2], s), 1715 lhs.getField(DatatypeConstants.DAYS) == null && rhs.getField(DatatypeConstants.DAYS) == null), 1716 toBigInteger(sanitize(buf[3], s), 1717 lhs.getField(DatatypeConstants.HOURS) == null && rhs.getField(DatatypeConstants.HOURS) == null), 1718 toBigInteger(sanitize(buf[4], s), 1719 lhs.getField(DatatypeConstants.MINUTES) == null && rhs.getField(DatatypeConstants.MINUTES) == null), 1720 (buf[5].signum() == 0 1721 && lhs.getField(DatatypeConstants.SECONDS) == null 1722 && rhs.getField(DatatypeConstants.SECONDS) == null) ? null : sanitize(buf[5], s)); 1723 } 1724 1725 private static void alignSigns(BigDecimal[] buf, int start, int end) { 1726 // align sign 1727 boolean touched; 1728 1729 do { // repeat until all the sign bits become consistent 1730 touched = false; 1731 int s = 0; // sign of the left fields 1732 1733 for (int i = start; i < end; i++) { 1734 if (s * buf[i].signum() < 0) { 1735 // this field has different sign than its left field. 1736 touched = true; 1737 1738 // compute the number of unit that needs to be borrowed. 1739 BigDecimal borrow = 1740 buf[i].abs().divide( 1741 FACTORS[i - 1], 1742 BigDecimal.ROUND_UP); 1743 if (buf[i].signum() > 0) { 1744 borrow = borrow.negate(); 1745 } 1746 1747 // update values 1748 buf[i - 1] = buf[i - 1].subtract(borrow); 1749 buf[i] = buf[i].add(borrow.multiply(FACTORS[i - 1])); 1750 } 1751 if (buf[i].signum() != 0) { 1752 s = buf[i].signum(); 1753 } 1754 } 1755 } while (touched); 1756 } 1757 1758 /** 1759 * Compute <code>value*signum</code> where value==null is treated as 1760 * value==0. 1761 * @param value Value to sanitize. 1762 * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. 1763 * 1764 * @return non-null {@link BigDecimal}. 1765 */ 1766 private static BigDecimal sanitize(BigInteger value, int signum) { 1767 if (signum == 0 || value == null) { 1768 return ZERO; 1769 } 1770 if (signum > 0) { 1771 return new BigDecimal(value); 1772 } 1773 return new BigDecimal(value.negate()); 1774 } 1775 1776 /** 1777 * <p>Compute <code>value*signum</code> where <code>value==null</code> is treated as <code>value==0</code></p>. 1778 * 1779 * @param value Value to sanitize. 1780 * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. 1781 * 1782 * @return non-null {@link BigDecimal}. 1783 */ 1784 static BigDecimal sanitize(BigDecimal value, int signum) { 1785 if (signum == 0 || value == null) { 1786 return ZERO; 1787 } 1788 if (signum > 0) { 1789 return value; 1790 } 1791 return value.negate(); 1792 } 1793 1794 /** 1795 * <p>Computes a new duration whose value is <code>this-rhs</code>.</p> 1796 * 1797 * <p>For example:</p> 1798 * <pre> 1799 * "1 day" - "-3 days" = "4 days" 1800 * "1 year" - "1 day" = IllegalStateException 1801 * "-(1 hour,50 minutes)" - "-20 minutes" = "-(1hours,30 minutes)" 1802 * "15 hours" - "-3 days" = "3 days and 15 hours" 1803 * "1 year" - "-1 day" = "1 year and 1 day" 1804 * </pre> 1805 * 1806 * <p>Since there's no way to meaningfully subtract 1 day from 1 month, 1807 * there are cases where the operation fails in {@link IllegalStateException}.</p> 1808 * 1809 * <p>Formally the computation is defined as follows. 1810 * First, we can assume that two {@link Duration}s are both positive 1811 * without losing generality. (i.e., 1812 * <code>(-X)-Y=-(X+Y)</code>, <code>X-(-Y)=X+Y</code>, 1813 * <code>(-X)-(-Y)=-(X-Y)</code>)</p> 1814 * 1815 * <p>Then two durations are subtracted field by field. 1816 * If the sign of any non-zero field <tt>F</tt> is different from 1817 * the sign of the most significant field, 1818 * 1 (if <tt>F</tt> is negative) or -1 (otherwise) 1819 * will be borrowed from the next bigger unit of <tt>F</tt>.</p> 1820 * 1821 * <p>This process is repeated until all the non-zero fields have 1822 * the same sign.</p> 1823 * 1824 * <p>If a borrow occurs in the days field (in other words, if 1825 * the computation needs to borrow 1 or -1 month to compensate 1826 * days), then the computation fails by throwing an 1827 * {@link IllegalStateException}.</p> 1828 * 1829 * @param rhs <code>Duration</code> to substract from this <code>Duration</code>. 1830 * 1831 * @return New <code>Duration</code> created from subtracting <code>rhs</code> from this <code>Duration</code>. 1832 * 1833 * @throws IllegalStateException 1834 * If two durations cannot be meaningfully subtracted. For 1835 * example, subtracting one day from one month causes 1836 * this exception. 1837 * 1838 * @throws NullPointerException 1839 * If the rhs parameter is null. 1840 * 1841 * @see #add(Duration) 1842 */ 1843 public Duration subtract(final Duration rhs) { 1844 return add(rhs.negate()); 1845 } 1846 1847 /** 1848 * Returns a new {@link Duration} object whose 1849 * value is <code>-this</code>. 1850 * 1851 * <p> 1852 * Since the {@link Duration} class is immutable, this method 1853 * doesn't change the value of this object. It simply computes 1854 * a new Duration object and returns it. 1855 * 1856 * @return 1857 * always return a non-null valid {@link Duration} object. 1858 */ 1859 public Duration negate() { 1860 return new DurationImpl( 1861 signum <= 0, 1862 years, 1863 months, 1864 days, 1865 hours, 1866 minutes, 1867 seconds); 1868 } 1869 1870 /** 1871 * Returns the sign of this duration in -1,0, or 1. 1872 * 1873 * @return 1874 * -1 if this duration is negative, 0 if the duration is zero, 1875 * and 1 if the duration is postive. 1876 */ 1877 public int signum() { 1878 return signum; 1879 } 1880 1881 1882 /** 1883 * Adds this duration to a {@link Calendar} object. 1884 * 1885 * <p> 1886 * Calls {@link java.util.Calendar#add(int,int)} in the 1887 * order of YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS, and MILLISECONDS 1888 * if those fields are present. Because the {@link Calendar} class 1889 * uses int to hold values, there are cases where this method 1890 * won't work correctly (for example if values of fields 1891 * exceed the range of int.) 1892 * </p> 1893 * 1894 * <p> 1895 * Also, since this duration class is a Gregorian duration, this 1896 * method will not work correctly if the given {@link Calendar} 1897 * object is based on some other calendar systems. 1898 * </p> 1899 * 1900 * <p> 1901 * Any fractional parts of this {@link Duration} object 1902 * beyond milliseconds will be simply ignored. For example, if 1903 * this duration is "P1.23456S", then 1 is added to SECONDS, 1904 * 234 is added to MILLISECONDS, and the rest will be unused. 1905 * </p> 1906 * 1907 * <p> 1908 * Note that because {@link Calendar#add(int, int)} is using 1909 * <tt>int</tt>, {@link Duration} with values beyond the 1910 * range of <tt>int</tt> in its fields 1911 * will cause overflow/underflow to the given {@link Calendar}. 1912 * {@link XMLGregorianCalendar#add(Duration)} provides the same 1913 * basic operation as this method while avoiding 1914 * the overflow/underflow issues. 1915 * 1916 * @param calendar 1917 * A calendar object whose value will be modified. 1918 * @throws NullPointerException 1919 * if the calendar parameter is null. 1920 */ 1921 public void addTo(Calendar calendar) { 1922 calendar.add(Calendar.YEAR, getYears() * signum); 1923 calendar.add(Calendar.MONTH, getMonths() * signum); 1924 calendar.add(Calendar.DAY_OF_MONTH, getDays() * signum); 1925 calendar.add(Calendar.HOUR, getHours() * signum); 1926 calendar.add(Calendar.MINUTE, getMinutes() * signum); 1927 calendar.add(Calendar.SECOND, getSeconds() * signum); 1928 1929 if (seconds != null) { 1930 BigDecimal fraction = 1931 seconds.subtract(seconds.setScale(0, BigDecimal.ROUND_DOWN)); 1932 int millisec = fraction.movePointRight(3).intValue(); 1933 calendar.add(Calendar.MILLISECOND, millisec * signum); 1934 } 1935 } 1936 1937 /** 1938 * Adds this duration to a {@link Date} object. 1939 * 1940 * <p> 1941 * The given date is first converted into 1942 * a {@link java.util.GregorianCalendar}, then the duration 1943 * is added exactly like the {@link #addTo(Calendar)} method. 1944 * 1945 * <p> 1946 * The updated time instant is then converted back into a 1947 * {@link Date} object and used to update the given {@link Date} object. 1948 * 1949 * <p> 1950 * This somewhat redundant computation is necessary to unambiguously 1951 * determine the duration of months and years. 1952 * 1953 * @param date 1954 * A date object whose value will be modified. 1955 * @throws NullPointerException 1956 * if the date parameter is null. 1957 */ 1958 public void addTo(Date date) { 1959 Calendar cal = new GregorianCalendar(); 1960 cal.setTime(date); // this will throw NPE if date==null 1961 this.addTo(cal); 1962 date.setTime(getCalendarTimeInMillis(cal)); 1963 } 1964 1965 /** 1966 * <p>Stream Unique Identifier.</p> 1967 * 1968 * <p>TODO: Serialization should use the XML string representation as 1969 * the serialization format to ensure future compatibility.</p> 1970 */ 1971 private static final long serialVersionUID = 1L; 1972 1973 /** 1974 * Writes {@link Duration} as a lexical representation 1975 * for maximum future compatibility. 1976 * 1977 * @return 1978 * An object that encapsulates the string 1979 * returned by <code>this.toString()</code>. 1980 */ 1981 private Object writeReplace() throws IOException { 1982 return new DurationStream(this.toString()); 1983 } 1984 1985 /** 1986 * Representation of {@link Duration} in the object stream. 1987 * 1988 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 1989 */ 1990 private static class DurationStream implements Serializable { 1991 private final String lexical; 1992 1993 private DurationStream(String _lexical) { 1994 this.lexical = _lexical; 1995 } 1996 1997 private Object readResolve() throws ObjectStreamException { 1998 // try { 1999 return new DurationImpl(lexical); 2000 // } catch( ParseException e ) { 2001 // throw new StreamCorruptedException("unable to parse "+lexical+" as duration"); 2002 // } 2003 } 2004 2005 private static final long serialVersionUID = 1L; 2006 } 2007 2008 /** 2009 * Calls the {@link Calendar#getTimeInMillis} method. 2010 * Prior to JDK1.4, this method was protected and therefore 2011 * cannot be invoked directly. 2012 * 2013 * In future, this should be replaced by 2014 * <code>cal.getTimeInMillis()</code> 2015 */ 2016 private static long getCalendarTimeInMillis(Calendar cal) { 2017 return cal.getTime().getTime(); 2018 } 2019 }