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