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