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&lt;B (A is shorter than B)
  61  *  <li>A&gt;B (A is longer than B)
  62  *  <li>A==B   (A and B are of the same duration)
  63  *  <li>A&lt;>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 }