/* * reserved comment block * DO NOT REMOVE OR ALTER! */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.org.apache.xerces.internal.jaxp.datatype; import com.sun.org.apache.xerces.internal.util.DatatypeMessageFormatter; import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; /** * <p>Immutable representation of a time span as defined in * the W3C XML Schema 1.0 specification.</p> * * <p>A Duration object represents a period of Gregorian time, * which consists of six fields (years, months, days, hours, * minutes, and seconds) plus a sign (+/-) field.</p> * * <p>The first five fields have non-negative (>=0) integers or null * (which represents that the field is not set), * and the seconds field has a non-negative decimal or null. * A negative sign indicates a negative duration.</p> * * <p>This class provides a number of methods that make it easy * to use for the duration datatype of XML Schema 1.0 with * the errata.</p> * * <h2>Order relationship</h2> * <p>Duration objects only have partial order, where two values A and B * maybe either:</p> * <ol> * <li>A<B (A is shorter than B) * <li>A>B (A is longer than B) * <li>A==B (A and B are of the same duration) * <li>A<>B (Comparison between A and B is indeterminate) * </ol> * <p>For example, 30 days cannot be meaningfully compared to one month. * The {@link #compare(Duration)} method implements this * relationship.</p> * * <p>See the {@link #isLongerThan(Duration)} method for details about * the order relationship among {@link Duration} objects.</p> * * * * <h2>Operations over Duration</h2> * <p>This class provides a set of basic arithmetic operations, such * as addition, subtraction and multiplication. * Because durations don't have total order, an operation could * fail for some combinations of operations. For example, you cannot * subtract 15 days from 1 month. See the javadoc of those methods * for detailed conditions where this could happen.</p> * * <p>Also, division of a duration by a number is not provided because * the {@link Duration} class can only deal with finite precision * decimal numbers. For example, one cannot represent 1 sec divided by 3.</p> * * <p>However, you could substitute a division by 3 with multiplying * by numbers such as 0.3 or 0.333.</p> * * * * <h2>Range of allowed values</h2> * <p> * Because some operations of {@link Duration} rely on {@link Calendar} * even though {@link Duration} can hold very large or very small values, * some of the methods may not work correctly on such {@link Duration}s. * The impacted methods document their dependency on {@link Calendar}. * * * @author <a href="mailto:Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> * @author <a href="mailto:Joseph.Fialli@Sun.com">Joseph Fialli</a> * @see XMLGregorianCalendar#add(Duration) */ class DurationImpl extends Duration implements Serializable { /** * <p>Internal array of value Fields.</p> */ private static final DatatypeConstants.Field[] FIELDS = new DatatypeConstants.Field[]{ DatatypeConstants.YEARS, DatatypeConstants.MONTHS, DatatypeConstants.DAYS, DatatypeConstants.HOURS, DatatypeConstants.MINUTES, DatatypeConstants.SECONDS }; /** * TimeZone for GMT. */ private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); /** * <p>BigDecimal value of 0.</p> */ private static final BigDecimal ZERO = BigDecimal.valueOf(0); /** * BigInteger value of Integer's max value.</p> */ private static final BigInteger MaxIntAsBigInt = BigInteger.valueOf((long) Integer.MAX_VALUE); /** * <p>Indicates the sign. -1, 0 or 1 if the duration is negative, * zero, or positive.</p> */ protected int signum; /** * <p>Years of this <code>Duration</code>.</p> */ /** * These were final since Duration is immutable. But new subclasses need * to be able to set after conversion. It won't break the immutable nature * of them since there's no other way to set new values to them */ protected BigInteger years; /** * <p>Months of this <code>Duration</code>.</p> */ protected BigInteger months; /** * <p>Days of this <code>Duration</code>.</p> */ protected BigInteger days; /** * <p>Hours of this <code>Duration</code>.</p> */ protected BigInteger hours; /** * <p>Minutes of this <code>Duration</code>.</p> */ protected BigInteger minutes; /** * <p>Seconds of this <code>Duration</code>.</p> */ protected BigDecimal seconds; /** * Returns the sign of this duration in -1,0, or 1. * * @return * -1 if this duration is negative, 0 if the duration is zero, * and 1 if the duration is postive. */ public int getSign() { return signum; } /** * Determine the sign of the duration. * * @param isPositive Sign. * @return 1 if positive, -1 negative, or 0 if all fields are zero. */ protected int calcSignum(boolean isPositive) { if ((years == null || years.signum() == 0) && (months == null || months.signum() == 0) && (days == null || days.signum() == 0) && (hours == null || hours.signum() == 0) && (minutes == null || minutes.signum() == 0) && (seconds == null || seconds.signum() == 0)) { return 0; } if (isPositive) { return 1; } else { return -1; } } /** * <p>Constructs a new Duration object by specifying each field individually.</p> * * <p>All the parameters are optional as long as at least one field is present. * If specified, parameters have to be zero or positive.</p> * * @param isPositive Set to <code>false</code> to create a negative duration. When the length * of the duration is zero, this parameter will be ignored. * @param years of this <code>Duration</code> * @param months of this <code>Duration</code> * @param days of this <code>Duration</code> * @param hours of this <code>Duration</code> * @param minutes of this <code>Duration</code> * @param seconds of this <code>Duration</code> * * @throws IllegalArgumentException * If years, months, days, hours, minutes and * seconds parameters are all <code>null</code>. Or if any * of those parameters are negative. */ protected DurationImpl( boolean isPositive, BigInteger years, BigInteger months, BigInteger days, BigInteger hours, BigInteger minutes, BigDecimal seconds) { this.years = years; this.months = months; this.days = days; this.hours = hours; this.minutes = minutes; this.seconds = seconds; this.signum = calcSignum(isPositive); // sanity check if (years == null && months == null && days == null && hours == null && minutes == null && seconds == null) { throw new IllegalArgumentException( //"all the fields are null" DatatypeMessageFormatter.formatMessage(null, "AllFieldsNull", null) ); } testNonNegative(years, DatatypeConstants.YEARS); testNonNegative(months, DatatypeConstants.MONTHS); testNonNegative(days, DatatypeConstants.DAYS); testNonNegative(hours, DatatypeConstants.HOURS); testNonNegative(minutes, DatatypeConstants.MINUTES); testNonNegative(seconds, DatatypeConstants.SECONDS); } /** * <p>Makes sure that the given number is non-negative. If it is not, * throw {@link IllegalArgumentException}.</p> * * @param n Number to test. * @param f Field to test. */ protected static void testNonNegative(BigInteger n, DatatypeConstants.Field f) { if (n != null && n.signum() < 0) { throw new IllegalArgumentException( DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) ); } } /** * <p>Makes sure that the given number is non-negative. If it is not, * throw {@link IllegalArgumentException}.</p> * * @param n Number to test. * @param f Field to test. */ protected static void testNonNegative(BigDecimal n, DatatypeConstants.Field f) { if (n != null && n.signum() < 0) { throw new IllegalArgumentException( DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) ); } } /** * <p>Constructs a new Duration object by specifying each field * individually.</p> * * <p>This method is functionally equivalent to * invoking another constructor by wrapping * all non-zero parameters into {@link BigInteger} and {@link BigDecimal}. * Zero value of int parameter is equivalent of null value of * the corresponding field.</p> * * @see #DurationImpl(boolean, BigInteger, BigInteger, BigInteger, BigInteger, * BigInteger, BigDecimal) */ protected DurationImpl( final boolean isPositive, final int years, final int months, final int days, final int hours, final int minutes, final int seconds) { this( isPositive, wrap(years), wrap(months), wrap(days), wrap(hours), wrap(minutes), seconds != DatatypeConstants.FIELD_UNDEFINED ? new BigDecimal(String.valueOf(seconds)) : null); } /** * TODO: Javadoc * * @param i int to convert to BigInteger. * * @return BigInteger representation of int. */ protected static BigInteger wrap(final int i) { // field may not be set if (i == DatatypeConstants.FIELD_UNDEFINED) { return null; } // int -> BigInteger return BigInteger.valueOf(i); } /** * <p>Constructs a new Duration object by specifying the duration * in milliseconds.</p> * * @param durationInMilliSeconds * The length of the duration in milliseconds. */ protected DurationImpl(final long durationInMilliSeconds) { boolean is0x8000000000000000L = false; long l = durationInMilliSeconds; if (l > 0) { signum = 1; } else if (l < 0) { signum = -1; if (l == 0x8000000000000000L) { // negating 0x8000000000000000L causes an overflow l++; is0x8000000000000000L = true; } l *= -1; } else { signum = 0; } // let GregorianCalendar do the heavy lifting GregorianCalendar gregorianCalendar = new GregorianCalendar(GMT); // duration is the offset from the Epoch gregorianCalendar.setTimeInMillis(l); // now find out how much each field has changed long int2long = 0L; // years int2long = gregorianCalendar.get(Calendar.YEAR) - 1970; this.years = BigInteger.valueOf(int2long); // months int2long = gregorianCalendar.get(Calendar.MONTH); this.months = BigInteger.valueOf(int2long); // days int2long = gregorianCalendar.get(Calendar.DAY_OF_MONTH) - 1; this.days = BigInteger.valueOf(int2long); // hours int2long = gregorianCalendar.get(Calendar.HOUR_OF_DAY); this.hours = BigInteger.valueOf(int2long); // minutes int2long = gregorianCalendar.get(Calendar.MINUTE); this.minutes = BigInteger.valueOf(int2long); // seconds & milliseconds int2long = (gregorianCalendar.get(Calendar.SECOND) * 1000) + gregorianCalendar.get(Calendar.MILLISECOND) + (is0x8000000000000000L ? 1 : 0); this.seconds = BigDecimal.valueOf(int2long, 3); } /** * Constructs a new Duration object by * parsing its string representation * "PnYnMnDTnHnMnS" as defined in XML Schema 1.0 section 3.2.6.1. * * <p> * The string representation may not have any leading * and trailing whitespaces. * * <p> * For example, this method parses strings like * "P1D" (1 day), "-PT100S" (-100 sec.), "P1DT12H" (1 days and 12 hours). * * <p> * The parsing is done field by field so that * the following holds for any lexically correct string x: * <pre> * new Duration(x).toString().equals(x) * </pre> * * Returns a non-null valid duration object that holds the value * indicated by the lexicalRepresentation parameter. * * @param lexicalRepresentation * Lexical representation of a duration. * @throws IllegalArgumentException * If the given string does not conform to the aforementioned * specification. * @throws NullPointerException * If the given string is null. */ protected DurationImpl(String lexicalRepresentation) throws IllegalArgumentException { // only if I could use the JDK1.4 regular expression .... if (lexicalRepresentation == null) { throw new NullPointerException(); } final String s = lexicalRepresentation; boolean positive; int[] idx = new int[1]; int length = s.length(); boolean timeRequired = false; idx[0] = 0; if (length != idx[0] && s.charAt(idx[0]) == '-') { idx[0]++; positive = false; } else { positive = true; } if (length != idx[0] && s.charAt(idx[0]++) != 'P') { throw new IllegalArgumentException(s); //,idx[0]-1); } // phase 1: chop the string into chunks // (where a chunk is '<number><a symbol>' //-------------------------------------- int dateLen = 0; String[] dateParts = new String[3]; int[] datePartsIndex = new int[3]; while (length != idx[0] && isDigit(s.charAt(idx[0])) && dateLen < 3) { datePartsIndex[dateLen] = idx[0]; dateParts[dateLen++] = parsePiece(s, idx); } if (length != idx[0]) { if (s.charAt(idx[0]++) == 'T') { timeRequired = true; } else { throw new IllegalArgumentException(s); // ,idx[0]-1); } } int timeLen = 0; String[] timeParts = new String[3]; int[] timePartsIndex = new int[3]; while (length != idx[0] && isDigitOrPeriod(s.charAt(idx[0])) && timeLen < 3) { timePartsIndex[timeLen] = idx[0]; timeParts[timeLen++] = parsePiece(s, idx); } if (timeRequired && timeLen == 0) { throw new IllegalArgumentException(s); // ,idx[0]); } if (length != idx[0]) { throw new IllegalArgumentException(s); // ,idx[0]); } if (dateLen == 0 && timeLen == 0) { throw new IllegalArgumentException(s); // ,idx[0]); } // phase 2: check the ordering of chunks //-------------------------------------- organizeParts(s, dateParts, datePartsIndex, dateLen, "YMD"); organizeParts(s, timeParts, timePartsIndex, timeLen, "HMS"); // parse into numbers years = parseBigInteger(s, dateParts[0], datePartsIndex[0]); months = parseBigInteger(s, dateParts[1], datePartsIndex[1]); days = parseBigInteger(s, dateParts[2], datePartsIndex[2]); hours = parseBigInteger(s, timeParts[0], timePartsIndex[0]); minutes = parseBigInteger(s, timeParts[1], timePartsIndex[1]); seconds = parseBigDecimal(s, timeParts[2], timePartsIndex[2]); signum = calcSignum(positive); } /** * TODO: Javadoc * * @param ch char to test. * * @return true if ch is a digit, else false. */ private static boolean isDigit(char ch) { return '0' <= ch && ch <= '9'; } /** * TODO: Javadoc * * @param ch to test. * * @return true if ch is a digit or a period, else false. */ private static boolean isDigitOrPeriod(char ch) { return isDigit(ch) || ch == '.'; } /** * TODO: Javadoc * * @param whole String to parse. * @param idx TODO: ??? * * @return Result of parsing. * * @throws IllegalArgumentException If whole cannot be parsed. */ private static String parsePiece(String whole, int[] idx) throws IllegalArgumentException { int start = idx[0]; while (idx[0] < whole.length() && isDigitOrPeriod(whole.charAt(idx[0]))) { idx[0]++; } if (idx[0] == whole.length()) { throw new IllegalArgumentException(whole); // ,idx[0]); } idx[0]++; return whole.substring(start, idx[0]); } /** * TODO: Javadoc. * * @param whole TODO: ??? * @param parts TODO: ??? * @param partsIndex TODO: ??? * @param len TODO: ??? * @param tokens TODO: ??? * * @throws IllegalArgumentException TODO: ??? */ private static void organizeParts( String whole, String[] parts, int[] partsIndex, int len, String tokens) throws IllegalArgumentException { int idx = tokens.length(); for (int i = len - 1; i >= 0; i--) { if (parts[i] == null) { throw new IllegalArgumentException(whole); } int nidx = tokens.lastIndexOf( parts[i].charAt(parts[i].length() - 1), idx - 1); if (nidx == -1) { throw new IllegalArgumentException(whole); // ,partsIndex[i]+parts[i].length()-1); } for (int j = nidx + 1; j < idx; j++) { parts[j] = null; } idx = nidx; parts[idx] = parts[i]; partsIndex[idx] = partsIndex[i]; } for (idx--; idx >= 0; idx--) { parts[idx] = null; } } /** * TODO: Javadoc * * @param whole TODO: ???. * @param part TODO: ???. * @param index TODO: ???. * * @return TODO: ???. * * @throws IllegalArgumentException TODO: ???. */ private static BigInteger parseBigInteger( String whole, String part, int index) throws IllegalArgumentException { if (part == null) { return null; } part = part.substring(0, part.length() - 1); // try { return new BigInteger(part); // } catch( NumberFormatException e ) { // throw new ParseException( whole, index ); // } } /** * TODO: Javadoc. * * @param whole TODO: ???. * @param part TODO: ???. * @param index TODO: ???. * * @return TODO: ???. * * @throws IllegalArgumentException TODO: ???. */ private static BigDecimal parseBigDecimal( String whole, String part, int index) throws IllegalArgumentException { if (part == null) { return null; } part = part.substring(0, part.length() - 1); // NumberFormatException is IllegalArgumentException // try { return new BigDecimal(part); // } catch( NumberFormatException e ) { // throw new ParseException( whole, index ); // } } /** * <p>Four constants defined for the comparison of durations.</p> */ private static final XMLGregorianCalendar[] TEST_POINTS = new XMLGregorianCalendar[] { XMLGregorianCalendarImpl.parse("1696-09-01T00:00:00Z"), XMLGregorianCalendarImpl.parse("1697-02-01T00:00:00Z"), XMLGregorianCalendarImpl.parse("1903-03-01T00:00:00Z"), XMLGregorianCalendarImpl.parse("1903-07-01T00:00:00Z") }; /** * <p>Partial order relation comparison with this <code>Duration</code> instance.</p> * * <p>Comparison result must be in accordance with * <a href="http://www.w3.org/TR/xmlschema-2/#duration-order">W3C XML Schema 1.0 Part 2, Section 3.2.7.6.2, * <i>Order relation on duration</i></a>.</p> * * <p>Return:</p> * <ul> * <li>{@link DatatypeConstants#LESSER} if this <code>Duration</code> is shorter than <code>duration</code> parameter</li> * <li>{@link DatatypeConstants#EQUAL} if this <code>Duration</code> is equal to <code>duration</code> parameter</li> * <li>{@link DatatypeConstants#GREATER} if this <code>Duration</code> is longer than <code>duration</code> parameter</li> * <li>{@link DatatypeConstants#INDETERMINATE} if a conclusive partial order relation cannot be determined</li> * </ul> * * @param duration to compare * * @return the relationship between <code>this</code> <code>Duration</code>and <code>duration</code> parameter as * {@link DatatypeConstants#LESSER}, {@link DatatypeConstants#EQUAL}, {@link DatatypeConstants#GREATER} * or {@link DatatypeConstants#INDETERMINATE}. * * @throws UnsupportedOperationException If the underlying implementation * cannot reasonably process the request, e.g. W3C XML Schema allows for * arbitrarily large/small/precise values, the request may be beyond the * implementations capability. * @throws NullPointerException if <code>duration</code> is <code>null</code>. * * @see #isShorterThan(Duration) * @see #isLongerThan(Duration) */ public int compare(Duration rhs) { /** check if any field in the Durations is too large for the operation * that uses XMLGregorianCalendar for comparison */ for (DatatypeConstants.Field field : FIELDS) { checkMaxValue(getField(field), field); checkMaxValue(rhs.getField(field), field); } return compareDates(this, rhs); } /** * Check if a field exceeds the maximum value * @param field the value of a field * @param fieldType type of the field, e.g. year, month, day, hour, minute or second. */ private void checkMaxValue(Number field, DatatypeConstants.Field fieldType) { BigInteger fieldValue = null; if (fieldType != DatatypeConstants.SECONDS) { fieldValue = (BigInteger) field; } else { BigDecimal rhsSecondsAsBigDecimal = (BigDecimal) field; if ( rhsSecondsAsBigDecimal != null ) { fieldValue = rhsSecondsAsBigDecimal.toBigInteger(); } } if (fieldValue != null && fieldValue.compareTo(MaxIntAsBigInt) == 1) { throw new UnsupportedOperationException( DatatypeMessageFormatter.formatMessage(null, "TooLarge", new Object[]{this.getClass().getName() + "#compare(Duration duration)" + fieldType, field.toString()}) ); } } /** * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration") * * @param duration1 Unnormalized duration * @param duration2 Unnormalized duration * @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate. * EQUAL if the order relation between date1 and date2 is EQUAL. * If the strict parameter is true, return LESS_THAN if date1 is less than date2 and * return GREATER_THAN if date1 is greater than date2. * If the strict parameter is false, return LESS_THAN if date1 is less than OR equal to date2 and * return GREATER_THAN if date1 is greater than OR equal to date2 */ private int compareDates(Duration duration1, Duration duration2) { int resultA = DatatypeConstants.INDETERMINATE; int resultB = DatatypeConstants.INDETERMINATE; XMLGregorianCalendar tempA = (XMLGregorianCalendar)TEST_POINTS[0].clone(); XMLGregorianCalendar tempB = (XMLGregorianCalendar)TEST_POINTS[0].clone(); //long comparison algorithm is required tempA.add(duration1); tempB.add(duration2); resultA = tempA.compare(tempB); if ( resultA == DatatypeConstants.INDETERMINATE ) { return DatatypeConstants.INDETERMINATE; } tempA = (XMLGregorianCalendar)TEST_POINTS[1].clone(); tempB = (XMLGregorianCalendar)TEST_POINTS[1].clone(); tempA.add(duration1); tempB.add(duration2); resultB = tempA.compare(tempB); resultA = compareResults(resultA, resultB); if (resultA == DatatypeConstants.INDETERMINATE) { return DatatypeConstants.INDETERMINATE; } tempA = (XMLGregorianCalendar)TEST_POINTS[2].clone(); tempB = (XMLGregorianCalendar)TEST_POINTS[2].clone(); tempA.add(duration1); tempB.add(duration2); resultB = tempA.compare(tempB); resultA = compareResults(resultA, resultB); if (resultA == DatatypeConstants.INDETERMINATE) { return DatatypeConstants.INDETERMINATE; } tempA = (XMLGregorianCalendar)TEST_POINTS[3].clone(); tempB = (XMLGregorianCalendar)TEST_POINTS[3].clone(); tempA.add(duration1); tempB.add(duration2); resultB = tempA.compare(tempB); resultA = compareResults(resultA, resultB); return resultA; } private int compareResults(int resultA, int resultB) { if ( resultB == DatatypeConstants.INDETERMINATE ) { return DatatypeConstants.INDETERMINATE; } else if ( resultA!=resultB) { return DatatypeConstants.INDETERMINATE; } return resultA; } /** * Returns a hash code consistent with the definition of the equals method. * * @see Object#hashCode() */ public int hashCode() { // component wise hash is not correct because 1day = 24hours Calendar cal = TEST_POINTS[0].toGregorianCalendar(); this.addTo(cal); return (int) getCalendarTimeInMillis(cal); } /** * Returns a string representation of this duration object. * * <p> * The result is formatter according to the XML Schema 1.0 * spec and can be always parsed back later into the * equivalent duration object by * the {@link #DurationImpl(String)} constructor. * * <p> * Formally, the following holds for any {@link Duration} * object x. * <pre> * new Duration(x.toString()).equals(x) * </pre> * * @return * Always return a non-null valid String object. */ public String toString() { StringBuffer buf = new StringBuffer(); if (signum < 0) { buf.append('-'); } buf.append('P'); if (years != null) { buf.append(years).append('Y'); } if (months != null) { buf.append(months).append('M'); } if (days != null) { buf.append(days).append('D'); } if (hours != null || minutes != null || seconds != null) { buf.append('T'); if (hours != null) { buf.append(hours).append('H'); } if (minutes != null) { buf.append(minutes).append('M'); } if (seconds != null) { buf.append(toString(seconds)).append('S'); } } return buf.toString(); } /** * <p>Turns {@link BigDecimal} to a string representation.</p> * * <p>Due to a behavior change in the {@link BigDecimal#toString()} * method in JDK1.5, this had to be implemented here.</p> * * @param bd <code>BigDecimal</code> to format as a <code>String</code> * * @return <code>String</code> representation of <code>BigDecimal</code> */ private String toString(BigDecimal bd) { String intString = bd.unscaledValue().toString(); int scale = bd.scale(); if (scale == 0) { return intString; } /* Insert decimal point */ StringBuffer buf; int insertionPoint = intString.length() - scale; if (insertionPoint == 0) { /* Point goes right before intVal */ return "0." + intString; } else if (insertionPoint > 0) { /* Point goes inside intVal */ buf = new StringBuffer(intString); buf.insert(insertionPoint, '.'); } else { /* We must insert zeros between point and intVal */ buf = new StringBuffer(3 - insertionPoint + intString.length()); buf.append("0."); for (int i = 0; i < -insertionPoint; i++) { buf.append('0'); } buf.append(intString); } return buf.toString(); } /** * Checks if a field is set. * * A field of a duration object may or may not be present. * This method can be used to test if a field is present. * * @param field * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, * MINUTES, or SECONDS.) * @return * true if the field is present. false if not. * * @throws NullPointerException * If the field parameter is null. */ public boolean isSet(DatatypeConstants.Field field) { if (field == null) { String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)" ; throw new NullPointerException( //"cannot be called with field == null" DatatypeMessageFormatter.formatMessage(null, "FieldCannotBeNull", new Object[]{methodName}) ); } if (field == DatatypeConstants.YEARS) { return years != null; } if (field == DatatypeConstants.MONTHS) { return months != null; } if (field == DatatypeConstants.DAYS) { return days != null; } if (field == DatatypeConstants.HOURS) { return hours != null; } if (field == DatatypeConstants.MINUTES) { return minutes != null; } if (field == DatatypeConstants.SECONDS) { return seconds != null; } String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)"; throw new IllegalArgumentException( DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) ); } /** * Gets the value of a field. * * Fields of a duration object may contain arbitrary large value. * Therefore this method is designed to return a {@link Number} object. * * In case of YEARS, MONTHS, DAYS, HOURS, and MINUTES, the returned * number will be a non-negative integer. In case of seconds, * the returned number may be a non-negative decimal value. * * @param field * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, * MINUTES, or SECONDS.) * @return * If the specified field is present, this method returns * a non-null non-negative {@link Number} object that * represents its value. If it is not present, return null. * For YEARS, MONTHS, DAYS, HOURS, and MINUTES, this method * returns a {@link BigInteger} object. For SECONDS, this * method returns a {@link BigDecimal}. * * @throws NullPointerException * If the field parameter is null. */ public Number getField(DatatypeConstants.Field field) { if (field == null) { String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field) " ; throw new NullPointerException( DatatypeMessageFormatter.formatMessage(null,"FieldCannotBeNull", new Object[]{methodName}) ); } if (field == DatatypeConstants.YEARS) { return years; } if (field == DatatypeConstants.MONTHS) { return months; } if (field == DatatypeConstants.DAYS) { return days; } if (field == DatatypeConstants.HOURS) { return hours; } if (field == DatatypeConstants.MINUTES) { return minutes; } if (field == DatatypeConstants.SECONDS) { return seconds; } /** throw new IllegalArgumentException( "javax.xml.datatype.Duration" + "#(getSet(DatatypeConstants.Field field) called with an unknown field: " + field.toString() ); */ String methodName = "javax.xml.datatype.Duration" + "#(getSet(DatatypeConstants.Field field)"; throw new IllegalArgumentException( DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) ); } /** * Obtains the value of the YEARS field as an integer value, * or 0 if not present. * * <p> * This method is a convenience method around the * {@link #getField(DatatypeConstants.Field)} method. * * <p> * Note that since this method returns <tt>int</tt>, this * method will return an incorrect value for {@link Duration}s * with the year field that goes beyond the range of <tt>int</tt>. * Use <code>getField(YEARS)</code> to avoid possible loss of precision.</p> * * @return * If the YEARS field is present, return * its value as an integer by using the {@link Number#intValue()} * method. If the YEARS field is not present, return 0. */ public int getYears() { return getInt(DatatypeConstants.YEARS); } /** * Obtains the value of the MONTHS field as an integer value, * or 0 if not present. * * This method works just like {@link #getYears()} except * that this method works on the MONTHS field. * * @return Months of this <code>Duration</code>. */ public int getMonths() { return getInt(DatatypeConstants.MONTHS); } /** * Obtains the value of the DAYS field as an integer value, * or 0 if not present. * * This method works just like {@link #getYears()} except * that this method works on the DAYS field. * * @return Days of this <code>Duration</code>. */ public int getDays() { return getInt(DatatypeConstants.DAYS); } /** * Obtains the value of the HOURS field as an integer value, * or 0 if not present. * * This method works just like {@link #getYears()} except * that this method works on the HOURS field. * * @return Hours of this <code>Duration</code>. * */ public int getHours() { return getInt(DatatypeConstants.HOURS); } /** * Obtains the value of the MINUTES field as an integer value, * or 0 if not present. * * This method works just like {@link #getYears()} except * that this method works on the MINUTES field. * * @return Minutes of this <code>Duration</code>. * */ public int getMinutes() { return getInt(DatatypeConstants.MINUTES); } /** * Obtains the value of the SECONDS field as an integer value, * or 0 if not present. * * This method works just like {@link #getYears()} except * that this method works on the SECONDS field. * * @return seconds in the integer value. The fraction of seconds * will be discarded (for example, if the actual value is 2.5, * this method returns 2) */ public int getSeconds() { return getInt(DatatypeConstants.SECONDS); } /** * <p>Return the requested field value as an int.</p> * * <p>If field is not set, i.e. == null, 0 is returned.</p> * * @param field To get value for. * * @return int value of field or 0 if field is not set. */ private int getInt(DatatypeConstants.Field field) { Number n = getField(field); if (n == null) { return 0; } else { return n.intValue(); } } /** * <p>Returns the length of the duration in milli-seconds.</p> * * <p>If the seconds field carries more digits than milli-second order, * those will be simply discarded (or in other words, rounded to zero.) * For example, for any Calendar value <code>x<code>,</p> * <pre> * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. * </pre> * * <p> * Note that this method uses the {@link #addTo(Calendar)} method, * which may work incorectly with {@link Duration} objects with * very large values in its fields. See the {@link #addTo(Calendar)} * method for details. * * @param startInstant * The length of a month/year varies. The <code>startInstant</code> is * used to disambiguate this variance. Specifically, this method * returns the difference between <code>startInstant</code> and * <code>startInstant+duration</code> * * @return milliseconds between <code>startInstant</code> and * <code>startInstant</code> plus this <code>Duration</code> * * @throws NullPointerException if <code>startInstant</code> parameter * is null. * */ public long getTimeInMillis(final Calendar startInstant) { Calendar cal = (Calendar) startInstant.clone(); addTo(cal); return getCalendarTimeInMillis(cal) - getCalendarTimeInMillis(startInstant); } /** * <p>Returns the length of the duration in milli-seconds.</p> * * <p>If the seconds field carries more digits than milli-second order, * those will be simply discarded (or in other words, rounded to zero.) * For example, for any <code>Date</code> value <code>x<code>,</p> * <pre> * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. * </pre> * * <p> * Note that this method uses the {@link #addTo(Date)} method, * which may work incorectly with {@link Duration} objects with * very large values in its fields. See the {@link #addTo(Date)} * method for details. * * @param startInstant * The length of a month/year varies. The <code>startInstant</code> is * used to disambiguate this variance. Specifically, this method * returns the difference between <code>startInstant</code> and * <code>startInstant+duration</code>. * * @throws NullPointerException * If the startInstant parameter is null. * * @return milliseconds between <code>startInstant</code> and * <code>startInstant</code> plus this <code>Duration</code> * * @see #getTimeInMillis(Calendar) */ public long getTimeInMillis(final Date startInstant) { Calendar cal = new GregorianCalendar(); cal.setTime(startInstant); this.addTo(cal); return getCalendarTimeInMillis(cal) - startInstant.getTime(); } // /** // * Returns an equivalent but "normalized" duration value. // * // * Intuitively, the normalization moves YEARS into // * MONTHS (by x12) and moves DAYS, HOURS, and MINUTES fields // * into SECONDS (by x86400, x3600, and x60 respectively.) // * // * // * Formally, this method satisfies the following conditions: // * <ul> // * <li>x.normalize().equals(x) // * <li>!x.normalize().isSet(Duration.YEARS) // * <li>!x.normalize().isSet(Duration.DAYS) // * <li>!x.normalize().isSet(Duration.HOURS) // * <li>!x.normalize().isSet(Duration.MINUTES) // * </ul> // * // * @return // * always return a non-null valid value. // */ // public Duration normalize() { // return null; // } /** * <p>Converts the years and months fields into the days field * by using a specific time instant as the reference point.</p> * * <p>For example, duration of one month normalizes to 31 days * given the start time instance "July 8th 2003, 17:40:32".</p> * * <p>Formally, the computation is done as follows:</p> * <ol> * <li>The given Calendar object is cloned. * <li>The years, months and days fields will be added to * the {@link Calendar} object * by using the {@link Calendar#add(int,int)} method. * <li>The difference between two Calendars are computed in terms of days. * <li>The computed days, along with the hours, minutes and seconds * fields of this duration object is used to construct a new * Duration object. * </ol> * * <p>Note that since the Calendar class uses <code>int</code> to * hold the value of year and month, this method may produce * an unexpected result if this duration object holds * a very large value in the years or months fields.</p> * * @param startTimeInstant <code>Calendar</code> reference point. * * @return <code>Duration</code> of years and months of this <code>Duration</code> as days. * * @throws NullPointerException If the startTimeInstant parameter is null. */ public Duration normalizeWith(Calendar startTimeInstant) { Calendar c = (Calendar) startTimeInstant.clone(); // using int may cause overflow, but // Calendar internally treats value as int anyways. c.add(Calendar.YEAR, getYears() * signum); c.add(Calendar.MONTH, getMonths() * signum); c.add(Calendar.DAY_OF_MONTH, getDays() * signum); // obtain the difference in terms of days long diff = getCalendarTimeInMillis(c) - getCalendarTimeInMillis(startTimeInstant); int days = (int) (diff / (1000L * 60L * 60L * 24L)); return new DurationImpl( days >= 0, null, null, wrap(Math.abs(days)), (BigInteger) getField(DatatypeConstants.HOURS), (BigInteger) getField(DatatypeConstants.MINUTES), (BigDecimal) getField(DatatypeConstants.SECONDS)); } /** * <p>Computes a new duration whose value is <code>factor</code> times * longer than the value of this duration.</p> * * <p>This method is provided for the convenience. * It is functionally equivalent to the following code:</p> * <pre> * multiply(new BigDecimal(String.valueOf(factor))) * </pre> * * @param factor Factor times longer of new <code>Duration</code> to create. * * @return New <code>Duration</code> that is <code>factor</code>times longer than this <code>Duration</code>. * * @see #multiply(BigDecimal) */ public Duration multiply(int factor) { return multiply(BigDecimal.valueOf(factor)); } /** * Computes a new duration whose value is <code>factor</code> times * longer than the value of this duration. * * <p> * For example, * <pre> * "P1M" (1 month) * "12" = "P12M" (12 months) * "PT1M" (1 min) * "0.3" = "PT18S" (18 seconds) * "P1M" (1 month) * "1.5" = IllegalStateException * </pre> * * <p> * Since the {@link Duration} class is immutable, this method * doesn't change the value of this object. It simply computes * a new Duration object and returns it. * * <p> * The operation will be performed field by field with the precision * of {@link BigDecimal}. Since all the fields except seconds are * restricted to hold integers, * any fraction produced by the computation will be * carried down toward the next lower unit. For example, * if you multiply "P1D" (1 day) with "0.5", then it will be 0.5 day, * which will be carried down to "PT12H" (12 hours). * When fractions of month cannot be meaningfully carried down * to days, or year to months, this will cause an * {@link IllegalStateException} to be thrown. * For example if you multiple one month by 0.5.</p> * * <p> * To avoid {@link IllegalStateException}, use * the {@link #normalizeWith(Calendar)} method to remove the years * and months fields. * * @param factor to multiply by * * @return * returns a non-null valid {@link Duration} object * * @throws IllegalStateException if operation produces fraction in * the months field. * * @throws NullPointerException if the <code>factor</code> parameter is * <code>null</code>. * */ public Duration multiply(BigDecimal factor) { BigDecimal carry = ZERO; int factorSign = factor.signum(); factor = factor.abs(); BigDecimal[] buf = new BigDecimal[6]; for (int i = 0; i < 5; i++) { BigDecimal bd = getFieldAsBigDecimal(FIELDS[i]); bd = bd.multiply(factor).add(carry); buf[i] = bd.setScale(0, BigDecimal.ROUND_DOWN); bd = bd.subtract(buf[i]); if (i == 1) { if (bd.signum() != 0) { throw new IllegalStateException(); // illegal carry-down } else { carry = ZERO; } } else { carry = bd.multiply(FACTORS[i]); } } if (seconds != null) { buf[5] = seconds.multiply(factor).add(carry); } else { buf[5] = carry; } return new DurationImpl( this.signum * factorSign >= 0, toBigInteger(buf[0], null == years), toBigInteger(buf[1], null == months), toBigInteger(buf[2], null == days), toBigInteger(buf[3], null == hours), toBigInteger(buf[4], null == minutes), (buf[5].signum() == 0 && seconds == null) ? null : buf[5]); } /** * <p>Gets the value of the field as a {@link BigDecimal}.</p> * * <p>If the field is unset, return 0.</p> * * @param f Field to get value for. * * @return non-null valid {@link BigDecimal}. */ private BigDecimal getFieldAsBigDecimal(DatatypeConstants.Field f) { if (f == DatatypeConstants.SECONDS) { if (seconds != null) { return seconds; } else { return ZERO; } } else { BigInteger bi = (BigInteger) getField(f); if (bi == null) { return ZERO; } else { return new BigDecimal(bi); } } } /** * <p>BigInteger value of BigDecimal value.</p> * * @param value Value to convert. * @param canBeNull Can returned value be null? * * @return BigInteger value of BigDecimal, possibly null. */ private static BigInteger toBigInteger( BigDecimal value, boolean canBeNull) { if (canBeNull && value.signum() == 0) { return null; } else { return value.unscaledValue(); } } /** * 1 unit of FIELDS[i] is equivalent to <code>FACTORS[i]</code> unit of * FIELDS[i+1]. */ private static final BigDecimal[] FACTORS = new BigDecimal[] { BigDecimal.valueOf(12), null/*undefined*/, BigDecimal.valueOf(24), BigDecimal.valueOf(60), BigDecimal.valueOf(60) }; /** * <p>Computes a new duration whose value is <code>this+rhs</code>.</p> * * <p>For example,</p> * <pre> * "1 day" + "-3 days" = "-2 days" * "1 year" + "1 day" = "1 year and 1 day" * "-(1 hour,50 minutes)" + "-20 minutes" = "-(1 hours,70 minutes)" * "15 hours" + "-3 days" = "-(2 days,9 hours)" * "1 year" + "-1 day" = IllegalStateException * </pre> * * <p>Since there's no way to meaningfully subtract 1 day from 1 month, * there are cases where the operation fails in * {@link IllegalStateException}.</p> * * <p> * Formally, the computation is defined as follows.</p> * <p> * Firstly, we can assume that two {@link Duration}s to be added * are both positive without losing generality (i.e., * <code>(-X)+Y=Y-X</code>, <code>X+(-Y)=X-Y</code>, * <code>(-X)+(-Y)=-(X+Y)</code>) * * <p> * Addition of two positive {@link Duration}s are simply defined as * field by field addition where missing fields are treated as 0. * <p> * A field of the resulting {@link Duration} will be unset if and * only if respective fields of two input {@link Duration}s are unset. * <p> * Note that <code>lhs.add(rhs)</code> will be always successful if * <code>lhs.signum()*rhs.signum()!=-1</code> or both of them are * normalized.</p> * * @param rhs <code>Duration</code> to add to this <code>Duration</code> * * @return * non-null valid Duration object. * * @throws NullPointerException * If the rhs parameter is null. * @throws IllegalStateException * If two durations cannot be meaningfully added. For * example, adding negative one day to one month causes * this exception. * * * @see #subtract(Duration) */ public Duration add(final Duration rhs) { Duration lhs = this; BigDecimal[] buf = new BigDecimal[6]; buf[0] = sanitize((BigInteger) lhs.getField(DatatypeConstants.YEARS), lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.YEARS), rhs.getSign())); buf[1] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MONTHS), lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MONTHS), rhs.getSign())); buf[2] = sanitize((BigInteger) lhs.getField(DatatypeConstants.DAYS), lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.DAYS), rhs.getSign())); buf[3] = sanitize((BigInteger) lhs.getField(DatatypeConstants.HOURS), lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.HOURS), rhs.getSign())); buf[4] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MINUTES), lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MINUTES), rhs.getSign())); buf[5] = sanitize((BigDecimal) lhs.getField(DatatypeConstants.SECONDS), lhs.getSign()).add(sanitize((BigDecimal) rhs.getField(DatatypeConstants.SECONDS), rhs.getSign())); // align sign alignSigns(buf, 0, 2); // Y,M alignSigns(buf, 2, 6); // D,h,m,s // make sure that the sign bit is consistent across all 6 fields. int s = 0; for (int i = 0; i < 6; i++) { if (s * buf[i].signum() < 0) { throw new IllegalStateException(); } if (s == 0) { s = buf[i].signum(); } } return new DurationImpl( s >= 0, toBigInteger(sanitize(buf[0], s), lhs.getField(DatatypeConstants.YEARS) == null && rhs.getField(DatatypeConstants.YEARS) == null), toBigInteger(sanitize(buf[1], s), lhs.getField(DatatypeConstants.MONTHS) == null && rhs.getField(DatatypeConstants.MONTHS) == null), toBigInteger(sanitize(buf[2], s), lhs.getField(DatatypeConstants.DAYS) == null && rhs.getField(DatatypeConstants.DAYS) == null), toBigInteger(sanitize(buf[3], s), lhs.getField(DatatypeConstants.HOURS) == null && rhs.getField(DatatypeConstants.HOURS) == null), toBigInteger(sanitize(buf[4], s), lhs.getField(DatatypeConstants.MINUTES) == null && rhs.getField(DatatypeConstants.MINUTES) == null), (buf[5].signum() == 0 && lhs.getField(DatatypeConstants.SECONDS) == null && rhs.getField(DatatypeConstants.SECONDS) == null) ? null : sanitize(buf[5], s)); } private static void alignSigns(BigDecimal[] buf, int start, int end) { // align sign boolean touched; do { // repeat until all the sign bits become consistent touched = false; int s = 0; // sign of the left fields for (int i = start; i < end; i++) { if (s * buf[i].signum() < 0) { // this field has different sign than its left field. touched = true; // compute the number of unit that needs to be borrowed. BigDecimal borrow = buf[i].abs().divide( FACTORS[i - 1], BigDecimal.ROUND_UP); if (buf[i].signum() > 0) { borrow = borrow.negate(); } // update values buf[i - 1] = buf[i - 1].subtract(borrow); buf[i] = buf[i].add(borrow.multiply(FACTORS[i - 1])); } if (buf[i].signum() != 0) { s = buf[i].signum(); } } } while (touched); } /** * Compute <code>value*signum</code> where value==null is treated as * value==0. * @param value Value to sanitize. * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. * * @return non-null {@link BigDecimal}. */ private static BigDecimal sanitize(BigInteger value, int signum) { if (signum == 0 || value == null) { return ZERO; } if (signum > 0) { return new BigDecimal(value); } return new BigDecimal(value.negate()); } /** * <p>Compute <code>value*signum</code> where <code>value==null</code> is treated as <code>value==0</code></p>. * * @param value Value to sanitize. * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. * * @return non-null {@link BigDecimal}. */ static BigDecimal sanitize(BigDecimal value, int signum) { if (signum == 0 || value == null) { return ZERO; } if (signum > 0) { return value; } return value.negate(); } /** * <p>Computes a new duration whose value is <code>this-rhs</code>.</p> * * <p>For example:</p> * <pre> * "1 day" - "-3 days" = "4 days" * "1 year" - "1 day" = IllegalStateException * "-(1 hour,50 minutes)" - "-20 minutes" = "-(1hours,30 minutes)" * "15 hours" - "-3 days" = "3 days and 15 hours" * "1 year" - "-1 day" = "1 year and 1 day" * </pre> * * <p>Since there's no way to meaningfully subtract 1 day from 1 month, * there are cases where the operation fails in {@link IllegalStateException}.</p> * * <p>Formally the computation is defined as follows. * First, we can assume that two {@link Duration}s are both positive * without losing generality. (i.e., * <code>(-X)-Y=-(X+Y)</code>, <code>X-(-Y)=X+Y</code>, * <code>(-X)-(-Y)=-(X-Y)</code>)</p> * * <p>Then two durations are subtracted field by field. * If the sign of any non-zero field <tt>F</tt> is different from * the sign of the most significant field, * 1 (if <tt>F</tt> is negative) or -1 (otherwise) * will be borrowed from the next bigger unit of <tt>F</tt>.</p> * * <p>This process is repeated until all the non-zero fields have * the same sign.</p> * * <p>If a borrow occurs in the days field (in other words, if * the computation needs to borrow 1 or -1 month to compensate * days), then the computation fails by throwing an * {@link IllegalStateException}.</p> * * @param rhs <code>Duration</code> to substract from this <code>Duration</code>. * * @return New <code>Duration</code> created from subtracting <code>rhs</code> from this <code>Duration</code>. * * @throws IllegalStateException * If two durations cannot be meaningfully subtracted. For * example, subtracting one day from one month causes * this exception. * * @throws NullPointerException * If the rhs parameter is null. * * @see #add(Duration) */ public Duration subtract(final Duration rhs) { return add(rhs.negate()); } /** * Returns a new {@link Duration} object whose * value is <code>-this</code>. * * <p> * Since the {@link Duration} class is immutable, this method * doesn't change the value of this object. It simply computes * a new Duration object and returns it. * * @return * always return a non-null valid {@link Duration} object. */ public Duration negate() { return new DurationImpl( signum <= 0, years, months, days, hours, minutes, seconds); } /** * Returns the sign of this duration in -1,0, or 1. * * @return * -1 if this duration is negative, 0 if the duration is zero, * and 1 if the duration is postive. */ public int signum() { return signum; } /** * Adds this duration to a {@link Calendar} object. * * <p> * Calls {@link java.util.Calendar#add(int,int)} in the * order of YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS, and MILLISECONDS * if those fields are present. Because the {@link Calendar} class * uses int to hold values, there are cases where this method * won't work correctly (for example if values of fields * exceed the range of int.) * </p> * * <p> * Also, since this duration class is a Gregorian duration, this * method will not work correctly if the given {@link Calendar} * object is based on some other calendar systems. * </p> * * <p> * Any fractional parts of this {@link Duration} object * beyond milliseconds will be simply ignored. For example, if * this duration is "P1.23456S", then 1 is added to SECONDS, * 234 is added to MILLISECONDS, and the rest will be unused. * </p> * * <p> * Note that because {@link Calendar#add(int, int)} is using * <tt>int</tt>, {@link Duration} with values beyond the * range of <tt>int</tt> in its fields * will cause overflow/underflow to the given {@link Calendar}. * {@link XMLGregorianCalendar#add(Duration)} provides the same * basic operation as this method while avoiding * the overflow/underflow issues. * * @param calendar * A calendar object whose value will be modified. * @throws NullPointerException * if the calendar parameter is null. */ public void addTo(Calendar calendar) { calendar.add(Calendar.YEAR, getYears() * signum); calendar.add(Calendar.MONTH, getMonths() * signum); calendar.add(Calendar.DAY_OF_MONTH, getDays() * signum); calendar.add(Calendar.HOUR, getHours() * signum); calendar.add(Calendar.MINUTE, getMinutes() * signum); calendar.add(Calendar.SECOND, getSeconds() * signum); if (seconds != null) { BigDecimal fraction = seconds.subtract(seconds.setScale(0, BigDecimal.ROUND_DOWN)); int millisec = fraction.movePointRight(3).intValue(); calendar.add(Calendar.MILLISECOND, millisec * signum); } } /** * Adds this duration to a {@link Date} object. * * <p> * The given date is first converted into * a {@link java.util.GregorianCalendar}, then the duration * is added exactly like the {@link #addTo(Calendar)} method. * * <p> * The updated time instant is then converted back into a * {@link Date} object and used to update the given {@link Date} object. * * <p> * This somewhat redundant computation is necessary to unambiguously * determine the duration of months and years. * * @param date * A date object whose value will be modified. * @throws NullPointerException * if the date parameter is null. */ public void addTo(Date date) { Calendar cal = new GregorianCalendar(); cal.setTime(date); // this will throw NPE if date==null this.addTo(cal); date.setTime(getCalendarTimeInMillis(cal)); } /** * Returns time value in milliseconds * @param cal A calendar object * @return time value * * Diff from Xerces; Use JDK 1.5 feature. */ private static long getCalendarTimeInMillis(Calendar cal) { return cal.getTimeInMillis(); } /** * <p>Stream Unique Identifier.</p> * * <p>Serialization uses the lexical form returned by toString().</p> */ private static final long serialVersionUID = 1L; /** * Writes {@link Duration} as a lexical representation * for maximum future compatibility. * * @return * An object that encapsulates the string * returned by <code>this.toString()</code>. */ private Object writeReplace() throws IOException { return new DurationStream(this.toString()); } /** * Representation of {@link Duration} in the object stream. * * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) */ private static class DurationStream implements Serializable { private final String lexical; private DurationStream(String _lexical) { this.lexical = _lexical; } private Object readResolve() throws ObjectStreamException { return new DurationImpl(lexical); } private static final long serialVersionUID = 1L; } }