You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-dev@axis.apache.org by di...@apache.org on 2004/11/27 22:08:12 UTC

cvs commit: ws-axis/java/test/types TestDuration.java

dims        2004/11/27 13:08:12

  Modified:    java/src/org/apache/axis/types Duration.java
               java/src/org/apache/axis/i18n resource.properties
               java/test/types TestDuration.java
  Log:
  Fix for AXIS-1017 - org.apache.axis.types.Duration
  from Dominik Kacprzak (dominik@opentoolbox.com)
  
  Revision  Changes    Path
  1.10      +171 -61   ws-axis/java/src/org/apache/axis/types/Duration.java
  
  Index: Duration.java
  ===================================================================
  RCS file: /home/cvs/ws-axis/java/src/org/apache/axis/types/Duration.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- Duration.java	18 Apr 2004 18:07:37 -0000	1.9
  +++ Duration.java	27 Nov 2004 21:08:12 -0000	1.10
  @@ -1,12 +1,12 @@
   /*
    * Copyright 2002-2004 The Apache Software Foundation.
  - * 
  + *
    * Licensed 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.
  @@ -20,13 +20,14 @@
   
   import java.util.Calendar;
   
  -
   /**
  - * Implementation of the XML Schema type duration
  - * 
  + * Implementation of the XML Schema type duration. Duration supports a minimum
  + * fractional second precision of milliseconds.
  + *
    * @author Wes Moulder <we...@themindelectric.com>
  + * @author Dominik Kacprzak (dominik@opentoolbox.com)
    * @see <a href="http://www.w3.org/TR/xmlschema-2/#duration">XML Schema 3.2.6</a>
  - */ 
  + */
   public class Duration implements java.io.Serializable {
       boolean isNegative = false;
       int years;
  @@ -51,49 +52,115 @@
        * @param aMinutes
        * @param aSeconds
        */
  -    public Duration(boolean negative, int aYears, int aMonths, int aDays, int aHours, int aMinutes, double aSeconds) {
  +    public Duration(boolean negative, int aYears, int aMonths, int aDays,
  +                    int aHours, int aMinutes, double aSeconds) {
           isNegative = negative;
           years = aYears;
           months = aMonths;
           days = aDays;
           hours = aHours;
           minutes = aMinutes;
  -        seconds = aSeconds;
  +        setSeconds(aSeconds);
       }
   
       /**
  -     * This method takes a string that represents an xsd:duration and parses it.
  +     * Constructs Duration from a String in an xsd:duration format -
  +     * PnYnMnDTnHnMnS.
        *
  -     * @param duration
  +     * @param duration String
        * @throws SchemaException if the string doesn't parse correctly.
        */
       public Duration(String duration) throws IllegalArgumentException {
           int position = 1;
           int timePosition = duration.indexOf("T");
   
  -        if (duration.indexOf("P") == -1)
  +        // P is required but P by itself is invalid
  +        if (duration.indexOf("P") == -1 || duration.equals("P")) {
               throw new IllegalArgumentException(
                       Messages.getMessage("badDuration"));
  +        }
  +
  +        // if does not have time
  +        if (duration.indexOf("T") == -1) {
  +            // no time information
  +            if (!duration.matches("-?P[0-9]*Y?[0-9]*M?[0-9]*D?")) {
  +                throw new IllegalArgumentException(
  +                        Messages.getMessage("badDuration"));
  +            }
  +        } else {
  +            if (!duration.matches(
  +                    "-?P[0-9]*Y?[0-9]*M?[0-9]*D?T[0-9]*H?[0-9]*M?[0-9]*\\.?[0-9]*S?")) {
  +                throw new IllegalArgumentException(
  +                        Messages.getMessage("badDuration"));
  +            }
  +        }
   
  +        // if present, time cannot be empty
  +        if (duration.lastIndexOf("T") == duration.length() - 1) {
  +            throw new IllegalArgumentException(
  +                    Messages.getMessage("badDuration"));
  +        }
  +
  +        // check the sign
           if (duration.startsWith("-")) {
               isNegative = true;
               position++;
           }
   
  -        if (timePosition != -1)
  +        // parse time part
  +        if (timePosition != -1) {
               parseTime(duration.substring(timePosition + 1));
  -        else
  +        } else {
               timePosition = duration.length();
  +        }
   
  -        parseDate(duration.substring(position, timePosition));
  +        // parse date part
  +        if (position != timePosition) {
  +            parseDate(duration.substring(position, timePosition));
  +        }
       }
   
       /**
  -     * This method parses the time portion of a duration.
  +     * Constructs Duration from a Calendar.
  +     *
  +     * @param calendar Calendar
  +     * @throws IllegalArgumentException if the calendar object does not
  +     * represent any date nor time.
  +     */
  +    public Duration(boolean negative, Calendar calendar) throws
  +            IllegalArgumentException {
  +        this.isNegative = negative;
  +        this.years = calendar.get(Calendar.YEAR);
  +        this.months = calendar.get(Calendar.MONTH);
  +        this.days = calendar.get(Calendar.DATE);
  +        this.hours = calendar.get(Calendar.HOUR);
  +        this.minutes = calendar.get(Calendar.MINUTE);
  +        this.seconds = calendar.get(Calendar.SECOND);
  +        this.seconds += ((double) calendar.get(Calendar.MILLISECOND)) / 100;
  +        if (years == 0 && months == 0 && days == 0 && hours == 0 &&
  +            minutes == 0 && seconds == 0) {
  +            throw new IllegalArgumentException(Messages.getMessage(
  +                    "badCalendarForDuration"));
  +        }
  +    }
  +
  +    /**
  +     * This method parses the time portion of a String that represents
  +     * xsd:duration - nHnMnS.
        *
        * @param time
  +     * @throws IllegalArgumentException if time does not match pattern
  +     *
  +     * @deprecated for creating a Duration object from a String, use
  +     * {@link #Duration(String) Duration(String)} constructor.
        */
  -    public void parseTime(String time) {
  +    public void parseTime(String time) throws IllegalArgumentException {
  +        if (time.length() == 0 ||
  +            !time.matches("[0-9]*H?[0-9]*M?[0-9]*\\.?[0-9]*S?")) {
  +            throw new IllegalArgumentException(
  +                    Messages.getMessage("badTimeDuration"));
  +        }
  +
           int start = 0;
           int end = time.indexOf("H");
   
  @@ -111,16 +178,27 @@
   
           end = time.indexOf("S");
   
  -        if (end != -1)
  +        if (end != -1) {
               seconds = Double.parseDouble(time.substring(start, end));
  +        }
       }
   
       /**
  -     * This method parses the date portion of a duration.
  +     * This method parses the date portion of a String that represents
  +     * xsd:duration - nYnMnD.
        *
        * @param date
  +     * @throws IllegalArgumentException if date does not match pattern
  +     *
  +     * @deprecated for creating a Duration object from a String, use
  +     * {@link #Duration(String) Duration(String)} constructor.
        */
  -    public void parseDate(String date) {
  +    public void parseDate(String date) throws IllegalArgumentException {
  +        if (date.length() == 0 || !date.matches("[0-9]*Y?[0-9]*M?[0-9]*D?")) {
  +            throw new IllegalArgumentException(Messages.getMessage(
  +                    "badDateDuration"));
  +        }
  +
           int start = 0;
           int end = date.indexOf("Y");
   
  @@ -138,8 +216,9 @@
   
           end = date.indexOf("D");
   
  -        if (end != -1)
  +        if (end != -1) {
               days = Integer.parseInt(date.substring(start, end));
  +        }
       }
   
       /**
  @@ -235,12 +314,24 @@
   
       /**
        * @param seconds
  +     * @deprecated use {@link #setSeconds(double) setSeconds(double)}
  +     * instead
        */
       public void setSeconds(int seconds) {
           this.seconds = seconds;
       }
   
       /**
  +     * Sets the seconds. NOTE: The fractional value of seconds is rounded up to
  +     * milliseconds.
  +     *
  +     * @param seconds double
  +     */
  +    public void setSeconds(double seconds) {
  +        this.seconds = ((double) (Math.round(seconds * 100))) / 100;
  +    }
  +
  +    /**
        * This returns the xml representation of an xsd:duration object.
        */
       public String toString() {
  @@ -248,98 +339,117 @@
   
           duration.append("P");
   
  -        if (years != 0)
  +        if (years != 0) {
               duration.append(years + "Y");
  -
  -        if (months != 0)
  +        }
  +        if (months != 0) {
               duration.append(months + "M");
  -
  -        if (days != 0)
  +        }
  +        if (days != 0) {
               duration.append(days + "D");
  -
  +        }
           if (hours != 0 || minutes != 0 || seconds != 0.0) {
               duration.append("T");
   
  -            if (hours != 0)
  +            if (hours != 0) {
                   duration.append(hours + "H");
   
  -            if (minutes != 0)
  +            }
  +            if (minutes != 0) {
                   duration.append(minutes + "M");
   
  +            }
               if (seconds != 0) {
  -                if (seconds == (int) seconds)
  +                if (seconds == (int) seconds) {
                       duration.append((int) seconds + "S");
  -                else
  +                } else {
                       duration.append(seconds + "S");
  +                }
               }
           }
   
  -        if (duration.length() == 1)
  +        if (duration.length() == 1) {
               duration.append("T0S");
  +        }
   
  -        if (isNegative)
  +        if (isNegative) {
               duration.insert(0, "-");
  +        }
   
           return duration.toString();
       }
   
       /**
  -     * This currently does a verbatim check on the object.  If you have a
  -     * duration that is 60 minutes, and one that is 1 hour, they won't
  -     * be equal.
  +     * The equals method compares the time represented by duration object, not
  +     * its string representation.
  +     * Hence, a duration object representing 65 minutes is considered equal to a
  +     * duration object representing 1 hour and 5 minutes.
        *
  -     * @todo make this more flexible
        * @param object
        */
       public boolean equals(Object object) {
  -        if (!(object instanceof Duration))
  +        if (!(object instanceof Duration)) {
               return false;
  +        }
   
  +        Calendar thisCalendar = this.getAsCalendar();
           Duration duration = (Duration) object;
   
  -        int totalMonthsInTime = this.years * 12 + this.months;
  -        int totalMonthsToCompare = duration.years * 12 + duration.months;
  -
  -        double totalSecondsInTime = ((this.days * 24 + this.hours) * 60 + this.minutes) * 60 + this.seconds;
  -        double totalSecondsToCompare = ((duration.days * 24 + duration.hours) * 60 + duration.minutes) * 60 + duration.seconds;
  -
  -        return
  -                this.isNegative == duration.isNegative &&
  -                totalMonthsInTime == totalMonthsToCompare &&
  -                totalSecondsInTime == totalSecondsToCompare;
  +        return this.isNegative == duration.isNegative &&
  +                this.getAsCalendar().equals(duration.getAsCalendar());
       }
   
  -    /**
  -     *
  -     */
       public int hashCode() {
           int hashCode = 0;
   
  -        if (isNegative)
  +        if (isNegative) {
               hashCode++;
  -
  +        }
           hashCode += years;
           hashCode += months;
           hashCode += days;
           hashCode += hours;
           hashCode += minutes;
           hashCode += seconds;
  +        // milliseconds
  +        hashCode += (seconds * 100) % 100;
   
           return hashCode;
       }
   
  +    /**
  +     * Returns duration as a calendar.  Due to the way a Calendar class works,
  +     * the values for particular fields may not be the same as obtained through
  +     * getter methods.  For example, if a duration's object getMonths
  +     * returns 20, a similar call on a calendar object will return 1 year and
  +     * 8 months.
  +     *
  +     * @return Calendar
  +     */
       public Calendar getAsCalendar() {
           return getAsCalendar(Calendar.getInstance());
       }
   
  +    /**
  +     * Returns duration as a calendar.  Due to the way a Calendar class works,
  +     * the values for particular fields may not be the same as obtained through
  +     * getter methods.  For example, if a Duration's object getMonths
  +     * returns 20, a similar call on a Calendar object will return 1 year and
  +     * 8 months.
  +     *
  +     * @param startTime Calendar
  +     * @return Calendar
  +     */
       public Calendar getAsCalendar(Calendar startTime) {
  -        Calendar ret = (Calendar)startTime.clone();
  -        ret.add(Calendar.YEAR, years);
  -        ret.add(Calendar.MONTH, months);
  -        ret.add(Calendar.DATE, days);
  -        ret.add(Calendar.HOUR, hours);
  -        ret.add(Calendar.MINUTE, minutes);
  -        ret.add(Calendar.SECOND, (int)seconds);
  +        Calendar ret = (Calendar) startTime.clone();
  +        ret.set(Calendar.YEAR, years);
  +        ret.set(Calendar.MONTH, months);
  +        ret.set(Calendar.DATE, days);
  +        ret.set(Calendar.HOUR, hours);
  +        ret.set(Calendar.MINUTE, minutes);
  +        ret.set(Calendar.SECOND, (int) seconds);
  +        ret.set(Calendar.MILLISECOND,
  +                (int) (seconds * 100 - Math.round(seconds) * 100));
           return ret;
       }
  -}
  \ No newline at end of file
  +}
  
  
  
  1.100     +11 -8     ws-axis/java/src/org/apache/axis/i18n/resource.properties
  
  Index: resource.properties
  ===================================================================
  RCS file: /home/cvs/ws-axis/java/src/org/apache/axis/i18n/resource.properties,v
  retrieving revision 1.99
  retrieving revision 1.100
  diff -u -r1.99 -r1.100
  --- resource.properties	18 Nov 2004 17:00:40 -0000	1.99
  +++ resource.properties	27 Nov 2004 21:08:12 -0000	1.100
  @@ -23,7 +23,7 @@
   adminServiceStop=Stopping service in response to admin request from {0}
   adminServiceSuspend=Starting {0} service in response to admin request from {1}
   adminServiceResume=Stopping {0} service in response to admin request from {1}
  -auth00=User ''{0}'' authenticated to server
  +auth00=User ''{0}'' authenticated to seFrver
   auth01=User ''{0}'' authorized to ''{1}''
   
   # NOTE:  in axisService00, do not translate "AXIS"
  @@ -105,7 +105,7 @@
   cantConvert01=Cannot convert form {0} to String
   cantConvert02=Could not convert {0} to bean field ''{1}'', type {2}
   cantConvert03=Could not convert value to int
  -cantConvert04=Could not set Class {0} Field {1} to value {2} - Exception {3}  
  +cantConvert04=Could not set Class {0} Field {1} to value {2} - Exception {3}
   
   cantDoNullArray00=Cannot serialize null arrays just yet...
   
  @@ -436,7 +436,7 @@
   noService06=No service is available at this URL
   # NOTE:  in noService07, do not translate "JWS"
   #UNUSED
  -noService07=There may be a JWS service here. 
  +noService07=There may be a JWS service here.
   # NOTE:  in noService08, do not translate "WSDL"
   #UNUSED
   noService08=Click here to look for the WSDL description
  @@ -476,7 +476,7 @@
   # NOTE:  in noWSDL01, do not translate "SOAP"
   noWSDL01=There is no SOAP service at this location
   # NOTE:  in noWSDL02, do not translate "WSDL"
  -noWSDL02=There is a service at this location, but Axis did not generate the WSDL for it. Perhaps it is is misconfigured. 
  +noWSDL02=There is a service at this location, but Axis did not generate the WSDL for it. Perhaps it is is misconfigured.
   
   null00={0} is null
   
  @@ -613,7 +613,7 @@
   setValueInTarget00=Set value {0} in target {1}
   somethingWrong00=Sorry, something seems to have gone wrong... here are the details:
   start00=starting up {0} on port {1}
  -start01=starting up {0} on port {1} ({2}) 
  +start01=starting up {0} on port {1} ({2})
   startElem00=Start element {0}
   startPrefix00=Start prefix mapping ''{0}'' -> ''{1}''
   stackFrame00=Stack frame marker
  @@ -823,7 +823,7 @@
   valuePresent=MessageElement.addChild called when an object value is present
   xmlPresent=MessageElement.setObjectValue called on an instance which was constructed using XML
   attachEnabled=Attachment support is enabled?
  -attachDisabled=Unable to find required classes (javax.activation.DataHandler and javax.mail.internet.MimeMultipart). Attachment support is disabled. 
  +attachDisabled=Unable to find required classes (javax.activation.DataHandler and javax.mail.internet.MimeMultipart). Attachment support is disabled.
   noEndpoint=No endpoint
   headerNotNull=Header may not be null!
   headerNotEmpty=Header may not be empty!
  @@ -927,7 +927,7 @@
   
   
   noVector00={0}:  {1} is not a vector
  -badVector00=Circular reference in Vector 
  +badVector00=Circular reference in Vector
   
   optionFactory00=name of a custom class that implements GeneratorFactory interface (for extending Java generation functions)
   optionHelper00=emits separate Helper classes for meta data
  @@ -1048,7 +1048,10 @@
   badMonth00=Invalid gMonth
   badDay00=Invalid gDay
   badMonthDay00=Invalid gMonthDay
  -badDuration=Invalid duration: must contain a P
  +badDuration=Invalid duration string does not match pattern.
  +badCalendarForDuration=Calendar must represent a positive time duration.
  +badDateDuration=Invalid date string does not match pattern.
  +badTimeDuration=Invalid time string does not match pattern.
   
   # NOTE:  in noDataHandler, do not translate DataHandler.
   noDataHandler=Could not create a DataHandler for {0}, returning {1} instead.
  
  
  
  1.4       +206 -39   ws-axis/java/test/types/TestDuration.java
  
  Index: TestDuration.java
  ===================================================================
  RCS file: /home/cvs/ws-axis/java/test/types/TestDuration.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- TestDuration.java	25 Feb 2004 14:02:59 -0000	1.3
  +++ TestDuration.java	27 Nov 2004 21:08:12 -0000	1.4
  @@ -1,12 +1,12 @@
   /*
    * Copyright 2002-2004 The Apache Software Foundation.
  - * 
  + *
    * Licensed 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.
  @@ -17,42 +17,209 @@
   
   import junit.framework.TestCase;
   import org.apache.axis.types.Duration;
  +import java.util.Calendar;
  +import java.util.GregorianCalendar;
   
  -public class TestDuration extends TestCase
  -  {
  +/**
  + * <p>Title: </p>
  + * <p>Description: </p>
  + * @author Dominik Kacprzak <do...@opentoolbox.com>
  + * @version $Id$
  + *
  + * @todo try to find original author, add more docs
  + */
  +public class TestDuration extends TestCase {
  +
  +
  +    public TestDuration(String name) {
  +        super(name);
  +    }
  +
  +    public void testValidDurations() throws Exception {
  +        // invoke the web service as if it was a local java object
  +        String[] durationStrings = new String[11];
  +        durationStrings[0] = "P2Y3M8DT8H1M3.3S";
  +        durationStrings[1] = "P2Y3M8DT8H1M3S";
  +        durationStrings[2] = "PT8H1M3.3S";
  +        durationStrings[3] = "P2Y3M8D";
  +        durationStrings[4] = "P2YT8H";
  +        durationStrings[5] = "P8DT3.3S";
  +        durationStrings[6] = "P3MT1M";
  +        durationStrings[7] = "PT0.3S";
  +        durationStrings[8] = "P1M";
  +        durationStrings[9] = "-P1M";
  +        durationStrings[10] = "-P2Y3M8DT8H1M3.3S";
  +
  +        for (int i = 0; i < durationStrings.length; i++) {
  +            String durationString = durationStrings[i];
  +            Duration duration = new Duration(durationString);
  +
  +            assertTrue("Duration string \"" + durationString +
  +                       "\" not equal to returned: " + duration.toString(),
  +                       durationString.equals(duration.toString()));
  +        }
  +    }
  +
  +    public void testInvalidDurations() throws Exception {
  +        // make sure that using invalid duration strings results in an
  +        // exception
  +        String[] invalidDurationStrings = {"P", "T", "P-2Y3M8D", "P8H1M3S",
  +                                          "P8Y1MT", "PT8Y1M3D", "PYMDTHMS"};
  +        // each of the above strings should result in an exception
  +        for (int i = 0; i < invalidDurationStrings.length; i++) {
  +            String durationString = invalidDurationStrings[i];
  +            try {
  +                Duration duration = new Duration(durationString);
  +                throw new junit.framework.AssertionFailedError(
  +                        "org.apache.axis.types.Duration constructor accepted invalid string: " +
  +                        durationString);
  +            } catch (IllegalArgumentException e) {
  +                // this is good
  +            }
  +        }
  +
  +        /* need to test setTime(String) and setDate(String) for handling
  +         * invalid time and date strings. A handling of properly formatted
  +         * time and date strings is tested as part of testValidDurations()
  +         * test case.
  +         * NOTE: the code below can be removed if/when
  +         * parseDate(String) and parseTime(String) methods are changed
  +         * to private ones.
  +         */
  +        String[] invalidTimeStrings = {"", "-8H1M3.3S", "8Y1M3D", "HMS"};
  +        Duration duration = new Duration();
  +        for (int i = 0; i < invalidTimeStrings.length; i++) {
  +            String durationString = invalidTimeStrings[i];
  +            try {
  +                duration.parseTime(durationString);
  +                throw new junit.framework.AssertionFailedError(
  +                        "parseTime(String) method accepted invalid string: " +
  +                        durationString);
  +            } catch (IllegalArgumentException e) {
  +                // this is good
  +            }
  +        }
  +
  +        String[] invalidDateStrings = {"", "-2Y3M8D", "8H1M3S", "-8Y1M"};
  +        for (int i = 0; i < invalidDateStrings.length; i++) {
  +            String durationString = invalidDateStrings[i];
  +            try {
  +                duration.parseDate(durationString);
  +                throw new junit.framework.AssertionFailedError(
  +                        "parseDate(String) method accepted invalid string: " +
  +                        durationString);
  +            } catch (IllegalArgumentException e) {
  +                // this is good
  +            }
  +        }
  +
  +    }
  +
  +    /**
  +     * Test if duration object is properly created from a Calendar.
  +     * @throws Exception
  +     */
  +    public void testDurationFromCalendar() throws Exception {
  +        // negative date
  +        Calendar calendar = new GregorianCalendar(1, 3, 20);
  +        Duration duration = new Duration(true, calendar);
  +        assertTrue("Negative flag does not match", duration.isNegative());
  +        assertTrue("Years do not match", duration.getYears() == 1);
  +        assertTrue("Months do not match", duration.getMonths() == 3);
  +        assertTrue("Days do not match", duration.getDays() == 20);
  +        assertEquals("String representation does not match", duration.toString(),
  +                     "-P1Y3M20D");
  +        // positive date and time
  +        calendar.clear();
  +        calendar.set(1, 2, 20, 10, 3, 11);
  +        duration = new Duration(false, calendar);
  +        assertTrue("Negative flag does not match", !duration.isNegative());
  +        assertTrue("Years do not match", duration.getYears() == 1);
  +        assertTrue("Months do not match", duration.getMonths() == 2);
  +        assertTrue("Days do not match", duration.getDays() == 20);
  +        assertTrue("Hours do not match", duration.getHours() == 10);
  +        assertTrue("Minutes do not match", duration.getMinutes() == 3);
  +        assertTrue("Seconds do not match", duration.getSeconds() == 11);
  +        assertEquals("String representation does not match", duration.toString(),
  +                     "P1Y2M20DT10H3M11S");
  +    }
  +
  +    public void testCalendarFromDuration() throws Exception {
  +        // Check if a calendar object used to create a duration object and
  +        // a calendar object created from the same duration are equal
  +        // that's good test for handling of miliseconds
  +        Calendar calendar = new GregorianCalendar(1, 2, 20, 10, 3, 11);
  +        calendar.set(Calendar.MILLISECOND, 15);
  +        Duration duration = new Duration(true, calendar);
  +        Calendar durationCalendar = duration.getAsCalendar();
  +        assertTrue("Negative flag does not match", duration.isNegative());
  +        assertEquals("Years do not match",
  +                     calendar.get(Calendar.YEAR),
  +                     durationCalendar.get(Calendar.YEAR));
  +        assertEquals("Months do not match",
  +                     calendar.get(Calendar.MONTH),
  +                     durationCalendar.get(Calendar.MONTH));
  +        assertEquals("Days do not match",
  +                     calendar.get(Calendar.DATE),
  +                     durationCalendar.get(Calendar.DATE));
  +        assertEquals("Hours do not match",
  +                     calendar.get(Calendar.HOUR),
  +                     durationCalendar.get(Calendar.HOUR));
  +        assertEquals("Minutes do not match",
  +                     calendar.get(Calendar.MINUTE),
  +                     durationCalendar.get(Calendar.MINUTE));
  +        assertEquals("Seconds do not match",
  +                     calendar.get(Calendar.SECOND),
  +                     durationCalendar.get(Calendar.SECOND));
  +        assertEquals("Miliseconds do not match",
  +                     calendar.get(Calendar.MILLISECOND),
  +                     durationCalendar.get(Calendar.MILLISECOND));
  +
  +        // test for overflows - Calendar class does automatic conversion
  +        // of dates
  +        duration.setMonths(20);
  +        durationCalendar = duration.getAsCalendar();
  +        assertEquals("Years do not match",
  +                     duration.getYears() + 1,
  +                     durationCalendar.get(Calendar.YEAR));
  +        assertEquals("Months do not match",
  +                     duration.getMonths() - 12,
  +                     durationCalendar.get(Calendar.MONTH));
  +
  +        // make sure that Duration enforces milliseconds precision
  +        duration.setSeconds(10.1234);
  +        assertTrue("Milliseconds precision is not enforced",
  +                     duration.getSeconds() == 10.12);
  +    }
  +
  +    public void testHash() {
  +        // test if hash is taking milliseconds in account
  +        Duration d1 = new Duration(false, 10, 1, 2, 1, 20, 2.51);
  +        Duration d2 = new Duration(false, 10, 1, 2, 1, 20, 2.51);
  +        Duration d3 = new Duration(false, 10, 1, 2, 1, 20, 2);
  +        Duration d4 = new Duration(false, 10, 1, 2, 1, 20, 2.51233);
  +        assertEquals("Hash code values do not match", d1.hashCode(),
  +                     d2.hashCode());
  +        assertFalse("Hash code values match", d1.hashCode() == d3.hashCode());
  +        // test precistion
  +        assertEquals("Hash code values do not match", d1.hashCode(),
  +                     d4.hashCode());
  +    }
   
  -  public TestDuration( String name )
  -    {
  -    super( name );
  -    }
  -
  -    
  -  public void testDurations()
  -    throws Exception
  -    {
  -    // invoke the web service as if it was a local java object
  -    String[] durationStrings = new String[ 11 ];
  -    durationStrings[ 0 ] = "P2Y3M8DT8H1M3.3S";
  -    durationStrings[ 1 ] = "P2Y3M8DT8H1M3S";
  -    durationStrings[ 2 ] = "PT8H1M3.3S";
  -    durationStrings[ 3 ] = "P2Y3M8D";
  -    durationStrings[ 4 ] = "P2YT8H";
  -    durationStrings[ 5 ] = "P8DT3.3S";
  -    durationStrings[ 6 ] = "P3MT1M";
  -    durationStrings[ 7 ] = "PT0.3S";
  -    durationStrings[ 8 ] = "P1M";
  -    durationStrings[ 9 ] = "-P1M";
  -    durationStrings[ 10 ] = "-P2Y3M8DT8H1M3.3S";
  -
  -    for( int i = 0; i < durationStrings.length; i++ )
  -      {
  -      String durationString = durationStrings[ i ];
  -      Duration duration = 
  -              new Duration( durationString );
  -
  -      assertTrue( "Duration string \"" + durationString + 
  -                  "\" not equal to returned: " + duration.toString(), 
  -                  durationString.equals( duration.toString() ) );
  -      }
  +    public void testEquals() {
  +        // test if equals is taking milliseconds in account
  +        Duration d1 = new Duration(false, 10, 1, 2, 1, 20, 2.51);
  +        Duration d2 = new Duration(false, 10, 1, 2, 1, 20, 2.51);
  +        Duration d3 = new Duration(true, 10, 1, 2, 1, 20, 2.51);
  +        Duration d4 = new Duration(false, 8, 25, 2, 0, 80, 2.51);
  +        Duration d5 = new Duration(false, 8, 25, 2, 0, 80, 2.51);
  +        // the durations are equal: testing precision
  +        assertTrue("Comparison failed", d1.equals(d2));
  +        // the durations are equal, except of the sign
  +        assertFalse("Comparison failed", d1.equals(d3));
  +        // the durations are equal, but represented differently
  +        assertTrue("Comparison failed", d1.equals(d4));
  +        // test precision
  +        assertTrue("Comparison failed", d1.equals(d5));
       }
  -  }
  +}