Rework DurationFormatUtils to have clearer API based on two types of formatting
Fix millisecond formatting bug


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137977 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/commons/lang/time/DurationFormatUtils.java b/src/java/org/apache/commons/lang/time/DurationFormatUtils.java
index 239d9c1..e1b6c07 100644
--- a/src/java/org/apache/commons/lang/time/DurationFormatUtils.java
+++ b/src/java/org/apache/commons/lang/time/DurationFormatUtils.java
@@ -42,7 +42,7 @@
  * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
  * @author Henri Yandell
  * @since 2.1
- * @version $Id: DurationFormatUtils.java,v 1.20 2004/10/02 01:40:30 bayard Exp $
+ * @version $Id: DurationFormatUtils.java,v 1.21 2004/10/15 23:11:31 scolebourne Exp $
  */
 public class DurationFormatUtils {
 
@@ -53,174 +53,182 @@
      * to operate.</p>
      */
     public DurationFormatUtils() {
+        super();
     }
 
     /**
-     * <p>Pattern used with <code>FastDateFormat</code> and <code>SimpleDateFormat</code> for the ISO8601 
-     * date time extended format used in durations.</p>
+     * <p>Pattern used with <code>FastDateFormat</code> and <code>SimpleDateFormat</code>
+     * for the ISO8601 period format used in durations.</p>
      * 
      * @see org.apache.commons.lang.time.FastDateFormat
      * @see java.text.SimpleDateFormat
      */
     public static final String ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'";
 
-    /**
-     * <p>ISO8601 formatter for the date time extended format used in durations, 
-     * with XML Schema durations particularly in mind.</p>
-     * 
-     * <p>This format represents the Gregorian year, month, day, hour, minute, and second components defined 
-     * in section 5.5.3.2 of ISO 8601, respectively. These components are ordered in their significance by their order 
-     * of appearance i.e. as year, month, day, hour, minute, and second.</p>
-     * 
-     * <p>The ISO8601 extended format P<i>n</i>Y<i>n</i>M<i>n</i>DT<i>n</i>H<i>n</i>M<i>n</i>S, where <i>n</i>Y 
-     * represents the number of years, <i>n</i>M the number of months, <i>n</i>D the number of days, 
-     * 'T' is the date/time separator, <i>n</i>H the number of hours, <i>n</i>M the number of minutes and 
-     * <i>n</i>S the number of seconds. The number of seconds can include decimal digits to arbitrary precision.</p>
-     * 
-     * @see #ISO_EXTENDED_FORMAT_PATTERN
-     * @see <a href="http://www.w3.org/TR/xmlschema-2/#duration">http://www.w3.org/TR/xmlschema-2/#duration</a>
-     */
-//    public static final FastDateFormat ISO_EXTENDED_FORMAT =
-//        FastDateFormat.getInstance(ISO_EXTENDED_FORMAT_PATTERN);
-
+    //-----------------------------------------------------------------------
     /**
      * <p>Get the time gap as a string.</p>
      * 
      * <p>The format used is ISO8601-like:
      * <i>H</i>:<i>m</i>:<i>s</i>.<i>S</i>.</p>
      * 
-     * @param millis  the duration to format
+     * @param durationMillis  the duration to format
      * @return the time as a String
      */
-    public static String formatISO(long millis) {
-        return format(millis, "H:mm:ss.SSS");
+    public static String formatDurationHMS(long durationMillis) {
+        return formatDuration(durationMillis, "H:mm:ss.SSS");
     }
 
-    public static String format(long millis) {
-        return format(millis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault() );
+    /**
+     * <p>Get the time gap as a string.</p>
+     * 
+     * <p>The format used is the ISO8601 period format.</p>
+     * 
+     * <p>This method formats durations using the days and lower fields of the
+     * ISO format pattern, such as P7D6H5M4.321S.</p>
+     * 
+     * @param durationMillis  the duration to format
+     * @return the time as a String
+     */
+    public static String formatDurationISO(long durationMillis) {
+        return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false);
     }
-    public static String format(long startMillis, long endMillis) {
-        return format(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault() );
-    }
-
 
     /**
      * <p>Get the time gap as a string, using the specified format, and padding with zeros and 
      * using the default timezone.</p>
      * 
-     * @param millis  the duration to format
+     * <p>This method formats durations using the days and lower fields of the
+     * format pattern. Months and larger are not used.</p>
+     * 
+     * @param durationMillis  the duration to format
      * @param format  the way in which to format the duration
      * @return the time as a String
      */
-    public static String format(long millis, String format) {
-        return format(millis, format, true, TimeZone.getDefault());
+    public static String formatDuration(long durationMillis, String format) {
+        return formatDuration(durationMillis, format, true);
     }
+
     /**
      * <p>Get the time gap as a string, using the specified format.
      * Padding the left hand side of numbers with zeroes is optional and 
-     * the timezone may be specified. 
+     * the timezone may be specified.</p>
      * 
-     * @param millis  the duration to format
+     * <p>This method formats durations using the days and lower fields of the
+     * format pattern. Months and larger are not used.</p>
+     * 
+     * @param durationMillis  the duration to format
      * @param format  the way in which to format the duration
-     * @param padWithZeros whether to pad the left hand side of numbers with 0's
-     * @param timezone the millis are defined in
+     * @param padWithZeros  whether to pad the left hand side of numbers with 0's
      * @return the time as a String
      */
-    public static String format(long millis, String format, boolean padWithZeros, TimeZone timezone) {
-
-        if(millis >= 28 * DateUtils.MILLIS_PER_DAY) {
-            Calendar c = Calendar.getInstance(timezone);
-            c.set(1970, 0, 1, 0, 0, 0);
-            c.set(Calendar.MILLISECOND, 0);
-            return format(c.getTime().getTime(), millis, format, padWithZeros, timezone);
-        }
+    public static String formatDuration(long durationMillis, String format, boolean padWithZeros) {
 
         Token[] tokens = lexx(format);
 
-        int years        = 0;
-        int months       = 0;
         int days         = 0;
         int hours        = 0;
         int minutes      = 0;
         int seconds      = 0;
         int milliseconds = 0;
-
-        /*  This will never be evaluated
-        if(Token.containsTokenWithValue(tokens, y) ) {
-            years = (int) (millis / DateUtils.MILLIS_PER_YEAR);
-            millis = millis - (years * DateUtils.MILLIS_PER_YEAR);
+        
+        if (Token.containsTokenWithValue(tokens, d) ) {
+            days = (int) (durationMillis / DateUtils.MILLIS_PER_DAY);
+            durationMillis = durationMillis - (days * DateUtils.MILLIS_PER_DAY);
         }
-        if(Token.containsTokenWithValue(tokens, M) ) {
-            months = (int) (millis / DateUtils.MILLIS_PER_MONTH);
-            millis = millis - (months * DateUtils.MILLIS_PER_MONTH);
-            // as MONTH * 12 != YEAR, this fixes issues
-            if(months == 12) {
-                years++;
-                months = 0;
-            }
+        if (Token.containsTokenWithValue(tokens, H) ) {
+            hours = (int) (durationMillis / DateUtils.MILLIS_PER_HOUR);
+            durationMillis = durationMillis - (hours * DateUtils.MILLIS_PER_HOUR);
         }
-        */
-        if(Token.containsTokenWithValue(tokens, d) ) {
-            days = (int) (millis / DateUtils.MILLIS_PER_DAY);
-            millis = millis - (days * DateUtils.MILLIS_PER_DAY);
+        if (Token.containsTokenWithValue(tokens, m) ) {
+            minutes = (int) (durationMillis / DateUtils.MILLIS_PER_MINUTE);
+            durationMillis = durationMillis - (minutes * DateUtils.MILLIS_PER_MINUTE);
         }
-        if(Token.containsTokenWithValue(tokens, H) ) {
-            hours = (int) (millis / DateUtils.MILLIS_PER_HOUR);
-            millis = millis - (hours * DateUtils.MILLIS_PER_HOUR);
+        if (Token.containsTokenWithValue(tokens, s) ) {
+            seconds = (int) (durationMillis / DateUtils.MILLIS_PER_SECOND);
+            durationMillis = durationMillis - (seconds * DateUtils.MILLIS_PER_SECOND);
         }
-        if(Token.containsTokenWithValue(tokens, m) ) {
-            minutes = (int) (millis / DateUtils.MILLIS_PER_MINUTE);
-            millis = millis - (minutes * DateUtils.MILLIS_PER_MINUTE);
-        }
-        if(Token.containsTokenWithValue(tokens, s) ) {
-            seconds = (int) (millis / DateUtils.MILLIS_PER_SECOND);
-            millis = millis - (seconds * DateUtils.MILLIS_PER_SECOND);
-        }
-        if(Token.containsTokenWithValue(tokens, S) ) {
-            milliseconds = (int) millis;
+        if (Token.containsTokenWithValue(tokens, S) ) {
+            milliseconds = (int) durationMillis;
         }
 
-        return formatDuration(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
+        return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros);
     }
 
+    /**
+     * <p>Format an elapsed time into a plurialization correct string.</p>
+     * 
+     * <p>This method formats durations using the days and lower fields of the
+     * format pattern. Months and larger are not used.</p>
+     * 
+     * @param durationMillis  the elapsed time to report in milliseconds
+     * @param suppressLeadingZeroElements  suppresses leading 0 elements
+     * @param suppressTrailingZeroElements  suppresses trailing 0 elements
+     * @return the formatted text in days/hours/minutes/seconds
+     */
+    public static String formatDurationWords(
+        long durationMillis,
+        boolean suppressLeadingZeroElements,
+        boolean suppressTrailingZeroElements) {
 
-    static String formatDuration(Token[] tokens, int years, int months, int days, int hours, 
-                                 int minutes, int seconds, int milliseconds, boolean padWithZeros) 
-    { 
-        StringBuffer buffer = new StringBuffer();
-        int sz = tokens.length;
-        for(int i=0; i<sz; i++) {
-            Token token = tokens[i];
-            Object value = token.getValue();
-            int count = token.getCount();
-            if(value instanceof StringBuffer) {
-                buffer.append(value.toString());
-            } else {
-                if(value == y) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+years, count, "0") : ""+years ); 
-                } else
-                if(value == M) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+months, count, "0") : ""+months ); 
-                } else
-                if(value == d) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+days, count, "0") : ""+days ); 
-                } else
-                if(value == H) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+hours, count, "0") : ""+hours ); 
-                } else
-                if(value == m) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+minutes, count, "0") : ""+minutes ); 
-                } else
-                if(value == s) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+seconds, count, "0") : ""+seconds ); 
-                } else
-                if(value == S) {
-                    buffer.append( padWithZeros ? StringUtils.leftPad(""+milliseconds, count, "0") : ""+milliseconds ); 
+        // This method is generally replacable by the format method, but 
+        // there are a series of tweaks and special cases that require 
+        // trickery to replicate.
+        String duration = formatDuration(durationMillis, "d' days 'H' hours 'm' minutes 's' seconds'");
+        if (suppressLeadingZeroElements) {
+            // this is a temporary marker on the front. Like ^ in regexp.
+            duration = " " + duration;
+            String tmp = StringUtils.replaceOnce(duration, " 0 days", "");
+            if (tmp.length() != duration.length()) {
+                duration = tmp;
+                tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
+                if (tmp.length() != duration.length()) {
+                    duration = tmp;
+                    tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
+                    duration = tmp;
+                    if (tmp.length() != duration.length()) {
+                        duration = StringUtils.replaceOnce(tmp, " 0 seconds", "");
+                    }
+                }
+            }
+            if (duration.length() != 0) {
+                // strip the space off again
+                duration = duration.substring(1);
+            }
+        }
+        if (suppressTrailingZeroElements) {
+            String tmp = StringUtils.replaceOnce(duration, " 0 seconds", "");
+            if (tmp.length() != duration.length()) {
+                duration = tmp;
+                tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
+                if (tmp.length() != duration.length()) {
+                    duration = tmp;
+                    tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
+                    if (tmp.length() != duration.length()) {
+                        duration = StringUtils.replaceOnce(tmp, " 0 days", "");
+                    }
                 }
             }
         }
-        
-        return buffer.toString();
+        // handle plurals
+        duration = StringUtils.replaceOnce(duration, "1 seconds", "1 second");
+        duration = StringUtils.replaceOnce(duration, "1 minutes", "1 minute");
+        duration = StringUtils.replaceOnce(duration, "1 hours", "1 hour");
+        duration = StringUtils.replaceOnce(duration, "1 days", "1 day");
+        return duration;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Get the time gap as a string.</p>
+     * 
+     * <p>The format used is the ISO8601 period format.</p>
+     * 
+     * @param millis  the duration to format
+     * @return the time as a String
+     */
+    public static String formatPeriodISO(long startMillis, long endMillis) {
+        return formatPeriod(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault() );
     }
 
     /**
@@ -232,9 +240,10 @@
      * @param format  the way in which to format the duration
      * @return the time as a String
      */
-    public static String format(long startMillis, long endMillis, String format) {
-        return format(startMillis, endMillis, format, true, TimeZone.getDefault());
+    public static String formatPeriod(long startMillis, long endMillis, String format) {
+        return formatPeriod(startMillis, endMillis, format, true, TimeZone.getDefault());
     }
+
     /**
      * <p>Get the time gap as a string, using the specified format.
      * Padding the left hand side of numbers with zeroes is optional and 
@@ -247,11 +256,11 @@
      * @param timezone the millis are defined in
      * @return the time as a String
      */
-    public static String format(long startMillis, long endMillis, String format, boolean padWithZeros, TimeZone timezone) {
+    public static String formatPeriod(long startMillis, long endMillis, String format, boolean padWithZeros, TimeZone timezone) {
 
         long millis = endMillis - startMillis;
-        if(millis < 28 * DateUtils.MILLIS_PER_DAY) {
-            return format(millis, format, padWithZeros, timezone);
+        if (millis < 28 * DateUtils.MILLIS_PER_DAY) {
+            return formatDuration(millis, format, padWithZeros);
         }
 
         Token[] tokens = lexx(format);
@@ -267,50 +276,50 @@
         int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
         int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
         // each initial estimate is adjusted in case it is under 0
-        while(months < 0) {
+        while (months < 0) {
             months += 12;
             years -= 1;
         }
         int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH);
-        while(days < 0) {
-            days += 31;  // such overshooting is taken care of later on
+        while (days < 0) {
+            days += 31; // such overshooting is taken care of later on
             months -= 1;
         }
         int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY);
-        while(hours < 0) {
+        while (hours < 0) {
             hours += 24;
             days -= 1;
         }
         int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
-        while(minutes < 0) {
+        while (minutes < 0) {
             minutes += 60;
             hours -= 1;
         }
         int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
-        while(seconds < 0) {
+        while (seconds < 0) {
             seconds += 60;
             minutes -= 1;
         }
         int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND);
-        while(milliseconds < 0) {
+        while (milliseconds < 0) {
             milliseconds += 1000;
             seconds -= 1;
         }
 
         // take estimates off of end to see if we can equal start, when it overshoots recalculate
-        milliseconds -= reduceAndCorrect( start, end, Calendar.MILLISECOND, milliseconds );
-        seconds -= reduceAndCorrect( start, end, Calendar.SECOND, seconds );
-        minutes -= reduceAndCorrect( start, end, Calendar.MINUTE, minutes );
-        hours -= reduceAndCorrect( start, end, Calendar.HOUR_OF_DAY, hours );
-        days -= reduceAndCorrect( start, end, Calendar.DAY_OF_MONTH, days );
-        months -= reduceAndCorrect( start, end, Calendar.MONTH, months );
-        years -= reduceAndCorrect( start, end, Calendar.YEAR, years );
+        milliseconds -= reduceAndCorrect(start, end, Calendar.MILLISECOND, milliseconds);
+        seconds -= reduceAndCorrect(start, end, Calendar.SECOND, seconds);
+        minutes -= reduceAndCorrect(start, end, Calendar.MINUTE, minutes);
+        hours -= reduceAndCorrect(start, end, Calendar.HOUR_OF_DAY, hours);
+        days -= reduceAndCorrect(start, end, Calendar.DAY_OF_MONTH, days);
+        months -= reduceAndCorrect(start, end, Calendar.MONTH, months);
+        years -= reduceAndCorrect(start, end, Calendar.YEAR, years);
 
         // This next block of code adds in values that 
         // aren't requested. This allows the user to ask for the 
         // number of months and get the real count and not just 0->11.
-        if(!Token.containsTokenWithValue(tokens, y) ) {
-            if(Token.containsTokenWithValue(tokens, M) ) {
+        if (!Token.containsTokenWithValue(tokens, y)) {
+            if (Token.containsTokenWithValue(tokens, M)) {
                 months += 12 * years;
                 years = 0;
             } else {
@@ -319,37 +328,101 @@
                 years = 0;
             }
         }
-        if(!Token.containsTokenWithValue(tokens, M) ) {
+        if (!Token.containsTokenWithValue(tokens, M)) {
             days += end.get(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR);
             months = 0;
         }
-        if(!Token.containsTokenWithValue(tokens, d) ) {
+        if (!Token.containsTokenWithValue(tokens, d)) {
             hours += 24 * days;
             days = 0;
         }
-        if(!Token.containsTokenWithValue(tokens, H) ) {
+        if (!Token.containsTokenWithValue(tokens, H)) {
             minutes += 60 * hours;
             hours = 0;
         }
-        if(!Token.containsTokenWithValue(tokens, m) ) {
+        if (!Token.containsTokenWithValue(tokens, m)) {
             seconds += 60 * minutes;
             minutes = 0;
         }
-        if(!Token.containsTokenWithValue(tokens, s) ) {
+        if (!Token.containsTokenWithValue(tokens, s)) {
             milliseconds += 1000 * seconds;
             seconds = 0;
         }
 
-        return formatDuration(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
+        return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
     }
 
-    // Reduces by difference, then if it overshot, calculates the overshot amount and 
-    // fixes and returns the amount to change by
+    //-----------------------------------------------------------------------
+    /**
+     * <p>The internal method to do the formatting.</p>
+     * 
+     * @param tokens  the tokens
+     * @param years  the number of years
+     * @param months  the number of months
+     * @param days  the number of days
+     * @param hours  the number of hours
+     * @param minutes  the number of minutes
+     * @param seconds  the number of seconds
+     * @param milliseconds  the number of millis
+     * @param padWithZeros  whether to pad
+     * @return the formetted string
+     */
+    static String format(Token[] tokens, int years, int months, int days, int hours, 
+                                 int minutes, int seconds, int milliseconds, boolean padWithZeros) 
+    { 
+        StringBuffer buffer = new StringBuffer();
+        boolean lastOutputSeconds = false;
+        int sz = tokens.length;
+        for (int i = 0; i < sz; i++) {
+            Token token = tokens[i];
+            Object value = token.getValue();
+            int count = token.getCount();
+            if(value instanceof StringBuffer) {
+                buffer.append(value.toString());
+            } else {
+                if (value == y) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(years), count, '0') : Integer.toString(years));
+                    lastOutputSeconds = false;
+                } else if (value == M) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(months), count, '0') : Integer.toString(months));
+                    lastOutputSeconds = false;
+                } else if (value == d) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(days), count, '0') : Integer.toString(days));
+                    lastOutputSeconds = false;
+                } else if (value == H) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(hours), count, '0') : Integer.toString(hours));
+                    lastOutputSeconds = false;
+                } else if (value == m) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(minutes), count, '0') : Integer.toString(minutes));
+                    lastOutputSeconds = false;
+                } else if (value == s) {
+                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(seconds), count, '0') : Integer.toString(seconds));
+                    lastOutputSeconds = true;
+                } else if (value == S) {
+                    if (lastOutputSeconds) {
+                        milliseconds += 1000;
+                        String str = padWithZeros ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0') : Integer.toString(milliseconds);
+                        buffer.append(str.substring(1));
+                    } else {
+                        buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0') : Integer.toString(milliseconds));
+                    }
+                    lastOutputSeconds = false;
+                }
+            }
+        }
+        
+        return buffer.toString();
+    }
+
+    /**
+     * Reduces by difference, then if it overshot, calculates the overshot amount and 
+     * fixes and returns the amount to change by.
+     */
     static int reduceAndCorrect(Calendar start, Calendar end, int field, int difference) {
         end.add( field, -1 * difference );
         int endValue = end.get(field);
         int startValue = start.get(field);
-        if(endValue < startValue) {
+        if (endValue < startValue) {
             int newdiff = startValue - endValue;
             end.add( field, newdiff );
             return newdiff;
@@ -358,66 +431,6 @@
         }
     }
 
-    /**
-     * <p>Format an elapsed time into a plurialization correct string.</p>
-     * 
-     * @param millis  the elapsed time to report in milliseconds
-     * @param suppressLeadingZeroElements suppresses leading 0 elements
-     * @param suppressTrailingZeroElements suppresses trailing 0 elements
-     * @return the formatted text in days/hours/minutes/seconds
-     */
-    public static String formatWords(
-        long millis,
-        boolean suppressLeadingZeroElements,
-        boolean suppressTrailingZeroElements) {
-
-        // This method is generally replacable by the format method, but 
-        // there are a series of tweaks and special cases that require 
-        // trickery to replicate.
-        String duration = format(millis, "d' days 'H' hours 'm' minutes 's' seconds'");
-        if(suppressLeadingZeroElements) {
-            // this is a temporary marker on the front. Like ^ in regexp.
-            duration = " " + duration;
-            String tmp = StringUtils.replaceOnce(duration, " 0 days", "");
-            if(tmp.length() != duration.length()) {
-                duration = tmp;
-                tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
-                if(tmp.length() != duration.length()) {
-                    duration = tmp;
-                    tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
-                    duration = tmp;
-                    if(tmp.length() != duration.length()) {
-                        duration = StringUtils.replaceOnce(tmp, " 0 seconds", "");
-                    }
-                }
-            }
-            if(duration.length() != 0) {
-                // strip the space off again
-                duration = duration.substring(1);
-            }
-        }
-        if(suppressTrailingZeroElements) {
-            String tmp = StringUtils.replaceOnce(duration, " 0 seconds", "");
-            if(tmp.length() != duration.length()) {
-                duration = tmp;
-                tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
-                if(tmp.length() != duration.length()) {
-                    duration = tmp;
-                    tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
-                    if(tmp.length() != duration.length()) {
-                        duration = StringUtils.replaceOnce(tmp, " 0 days", "");
-                    }
-                }
-            }
-        }
-        // handle plurals
-        duration = StringUtils.replaceOnce(duration, "1 seconds", "1 second");
-        duration = StringUtils.replaceOnce(duration, "1 minutes", "1 minute");
-        duration = StringUtils.replaceOnce(duration, "1 hours", "1 hour");
-        duration = StringUtils.replaceOnce(duration, "1 days", "1 day");
-        return duration;
-    }
-
     static final Object y = "y";
     static final Object M = "M";
     static final Object d = "d";
@@ -482,71 +495,71 @@
         return (Token[]) list.toArray( new Token[0] );
     }
 
-}
+    /**
+     * Element that is parsed from the format pattern.
+     */
+    static class Token {
 
-// Represents an element of the format-mini-language.
-class Token {
-
-    // will only work for the tokens, not for stringbuffers/numbers
-    static boolean containsTokenWithValue(Token[] tokens, Object value) {
-        int sz = tokens.length;
-        for(int i=0; i<sz; i++) {
-            if(tokens[i].getValue() == value) {
-                return true;
+        // will only work for the tokens, not for stringbuffers/numbers
+        static boolean containsTokenWithValue(Token[] tokens, Object value) {
+            int sz = tokens.length;
+            for (int i = 0; i < sz; i++) {
+                if (tokens[i].getValue() == value) {
+                    return true;
+                }
             }
-        }
-        return false;
-    }
-
-    private Object value;
-    private int count;
-
-    public Token(Object value) {
-        this.value = value;
-        this.count = 1;
-    }
-
-    Token(Object value, int count) {
-        this.value = value;
-        this.count = count;
-    }
-
-    public void increment() { 
-        count++;
-    }
-
-    public int getCount() {
-        return count;
-    }
-
-    public Object getValue() {
-        return value;
-    }
-
-    public boolean equals(Object obj2) {
-        if(obj2 instanceof Token) {
-            Token tok2 = (Token) obj2;
-            if(this.value.getClass() != tok2.value.getClass()) {
-                return false;
-            }
-            if(this.count != tok2.count) {
-                return false;
-            }
-            if(this.value instanceof StringBuffer) {
-                return this.value.toString().equals(tok2.value.toString());
-            } else
-            if(this.value instanceof Number) {
-                return this.value.equals(tok2.value);
-            } else {
-                return this.value == tok2.value;
-            }
-        } else {
             return false;
         }
-    }
 
-    public String toString() {
-        return StringUtils.repeat(this.value.toString(), this.count);
+        private Object value;
+        private int count;
+
+        Token(Object value) {
+            this.value = value;
+            this.count = 1;
+        }
+
+        Token(Object value, int count) {
+            this.value = value;
+            this.count = count;
+        }
+
+        void increment() { 
+            count++;
+        }
+
+        int getCount() {
+            return count;
+        }
+
+        Object getValue() {
+            return value;
+        }
+
+        public boolean equals(Object obj2) {
+            if (obj2 instanceof Token) {
+                Token tok2 = (Token) obj2;
+                if (this.value.getClass() != tok2.value.getClass()) {
+                    return false;
+                }
+                if (this.count != tok2.count) {
+                    return false;
+                }
+                if (this.value instanceof StringBuffer) {
+                    return this.value.toString().equals(tok2.value.toString());
+                } else if (this.value instanceof Number) {
+                    return this.value.equals(tok2.value);
+                } else {
+                    return this.value == tok2.value;
+                }
+            } else {
+                return false;
+            }
+        }
+
+        public String toString() {
+            return StringUtils.repeat(this.value.toString(), this.count);
+        }
     }
 
 }
diff --git a/src/java/org/apache/commons/lang/time/StopWatch.java b/src/java/org/apache/commons/lang/time/StopWatch.java
index 38447e6..749c8f8 100644
--- a/src/java/org/apache/commons/lang/time/StopWatch.java
+++ b/src/java/org/apache/commons/lang/time/StopWatch.java
@@ -46,7 +46,7 @@
  * @author Henri Yandell
  * @author Stephen Colebourne
  * @since 2.0
- * @version $Id: StopWatch.java,v 1.10 2004/10/08 00:09:01 scolebourne Exp $
+ * @version $Id: StopWatch.java,v 1.11 2004/10/15 23:11:31 scolebourne Exp $
  */
 public class StopWatch {
 
@@ -244,7 +244,7 @@
      * @return the time as a String
      */
     public String toString() {
-        return DurationFormatUtils.formatISO(getTime());
+        return DurationFormatUtils.formatDurationHMS(getTime());
     }
 
     /**
@@ -257,7 +257,7 @@
      * @since 2.1
      */
     public String toSplitString() {
-        return DurationFormatUtils.formatISO(getSplitTime());
+        return DurationFormatUtils.formatDurationHMS(getSplitTime());
     }
 
 }
diff --git a/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java b/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java
index 49e81fa..3cffa60 100644
--- a/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java
+++ b/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java
@@ -62,93 +62,142 @@
     }
     
     //-----------------------------------------------------------------------
-    public void testFormatWords(){
+    public void testFormatDurationWords(){
         String text = null;
         
-        text = DurationFormatUtils.formatWords(50*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(50*1000, true, false);
         assertEquals("50 seconds", text);
-        text = DurationFormatUtils.formatWords(65*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(65*1000, true, false);
         assertEquals("1 minute 5 seconds", text);
-        text = DurationFormatUtils.formatWords(120*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(120*1000, true, false);
         assertEquals("2 minutes 0 seconds", text);
-        text = DurationFormatUtils.formatWords(121*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(121*1000, true, false);
         assertEquals("2 minutes 1 second", text);
-        text = DurationFormatUtils.formatWords(72*60*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(72*60*1000, true, false);
         assertEquals("1 hour 12 minutes 0 seconds", text);
-        text = DurationFormatUtils.formatWords(24*60*60*1000, true, false);
+        text = DurationFormatUtils.formatDurationWords(24*60*60*1000, true, false);
         assertEquals("1 day 0 hours 0 minutes 0 seconds", text);
         
-        text = DurationFormatUtils.formatWords(50*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(50*1000, true, true);
         assertEquals("50 seconds", text);
-        text = DurationFormatUtils.formatWords(65*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(65*1000, true, true);
         assertEquals("1 minute 5 seconds", text);
-        text = DurationFormatUtils.formatWords(120*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(120*1000, true, true);
         assertEquals("2 minutes", text);
-        text = DurationFormatUtils.formatWords(121*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(121*1000, true, true);
         assertEquals("2 minutes 1 second", text);
-        text = DurationFormatUtils.formatWords(72*60*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(72*60*1000, true, true);
         assertEquals("1 hour 12 minutes", text);
-        text = DurationFormatUtils.formatWords(24*60*60*1000, true, true);
+        text = DurationFormatUtils.formatDurationWords(24*60*60*1000, true, true);
         assertEquals("1 day", text);
         
-        text = DurationFormatUtils.formatWords(50*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(50*1000, false, true);
         assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
-        text = DurationFormatUtils.formatWords(65*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(65*1000, false, true);
         assertEquals("0 days 0 hours 1 minute 5 seconds", text);
-        text = DurationFormatUtils.formatWords(120*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(120*1000, false, true);
         assertEquals("0 days 0 hours 2 minutes", text);
-        text = DurationFormatUtils.formatWords(121*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(121*1000, false, true);
         assertEquals("0 days 0 hours 2 minutes 1 second", text);
-        text = DurationFormatUtils.formatWords(72*60*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(72*60*1000, false, true);
         assertEquals("0 days 1 hour 12 minutes", text);
-        text = DurationFormatUtils.formatWords(24*60*60*1000, false, true);
+        text = DurationFormatUtils.formatDurationWords(24*60*60*1000, false, true);
         assertEquals("1 day", text);
         
-        text = DurationFormatUtils.formatWords(50*1000, false, false);
+        text = DurationFormatUtils.formatDurationWords(50*1000, false, false);
         assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
-        text = DurationFormatUtils.formatWords(65*1000, false, false);
+        text = DurationFormatUtils.formatDurationWords(65*1000, false, false);
         assertEquals("0 days 0 hours 1 minute 5 seconds", text);
-        text = DurationFormatUtils.formatWords(120*1000, false, false);
+        text = DurationFormatUtils.formatDurationWords(120*1000, false, false);
         assertEquals("0 days 0 hours 2 minutes 0 seconds", text);
-        text = DurationFormatUtils.formatWords(121*1000, false, false);
+        text = DurationFormatUtils.formatDurationWords(121*1000, false, false);
         assertEquals("0 days 0 hours 2 minutes 1 second", text);
-        text = DurationFormatUtils.formatWords(72*60*1000, false, false);
+        text = DurationFormatUtils.formatDurationWords(72*60*1000, false, false);
         assertEquals("0 days 1 hour 12 minutes 0 seconds", text);
-        text = DurationFormatUtils.formatWords(48*60*60*1000 + 72*60*1000 , false, false);
+        text = DurationFormatUtils.formatDurationWords(48*60*60*1000 + 72*60*1000 , false, false);
         assertEquals("2 days 1 hour 12 minutes 0 seconds", text);
     }
 
-    public void testFormatISOStyle(){
+    public void testFormatDurationHMS(){
         long time = 0;
-        assertEquals("0:00:00.000", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:00.000", DurationFormatUtils.formatDurationHMS(time));
         
         time = 1;
-        assertEquals("0:00:00.001", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:00.001", DurationFormatUtils.formatDurationHMS(time));
         
         time = 15;
-        assertEquals("0:00:00.015", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:00.015", DurationFormatUtils.formatDurationHMS(time));
         
         time = 165;
-        assertEquals("0:00:00.165", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:00.165", DurationFormatUtils.formatDurationHMS(time));
         
         time = 1675;
-        assertEquals("0:00:01.675", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:01.675", DurationFormatUtils.formatDurationHMS(time));
         
         time = 13465;
-        assertEquals("0:00:13.465", DurationFormatUtils.formatISO(time));
+        assertEquals("0:00:13.465", DurationFormatUtils.formatDurationHMS(time));
         
         time = 72789;
-        assertEquals("0:01:12.789", DurationFormatUtils.formatISO(time));
+        assertEquals("0:01:12.789", DurationFormatUtils.formatDurationHMS(time));
         
         time = 12789 + 32 * 60000;
-        assertEquals("0:32:12.789", DurationFormatUtils.formatISO(time));
+        assertEquals("0:32:12.789", DurationFormatUtils.formatDurationHMS(time));
         
         time = 12789 + 62 * 60000;
-        assertEquals("1:02:12.789", DurationFormatUtils.formatISO(time));
+        assertEquals("1:02:12.789", DurationFormatUtils.formatDurationHMS(time));
     }
 
-    public void testISODurationFormat(){
+    public void testFormatDurationISO() {
+        assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatDurationISO(0L));
+        assertEquals("P0Y0M0DT0H0M0.001S", DurationFormatUtils.formatDurationISO(1L));
+        assertEquals("P0Y0M0DT0H0M0.010S", DurationFormatUtils.formatDurationISO(10L));
+        assertEquals("P0Y0M0DT0H0M0.100S", DurationFormatUtils.formatDurationISO(100L));
+        assertEquals("P0Y0M0DT0H1M15.321S", DurationFormatUtils.formatDurationISO(75321L));
+    }
+
+    public void testFormatDuration() {
+        long duration = 0;
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "y") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "M") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "d") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "H") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "m") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "s") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "S") );
+        assertEquals( "0000", DurationFormatUtils.formatDuration(duration, "SSSS") );
+        assertEquals( "0000", DurationFormatUtils.formatDuration(duration, "yyyy") );
+        assertEquals( "0000", DurationFormatUtils.formatDuration(duration, "yyMM") );
+
+        duration = 60 * 1000;
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "y") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "M") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "d") );
+        assertEquals( "0", DurationFormatUtils.formatDuration(duration, "H") );
+        assertEquals( "1", DurationFormatUtils.formatDuration(duration, "m") );
+        assertEquals( "60", DurationFormatUtils.formatDuration(duration, "s") );
+        assertEquals( "60000", DurationFormatUtils.formatDuration(duration, "S") );
+        assertEquals( "01:00", DurationFormatUtils.formatDuration(duration, "mm:ss") );
+
+        Calendar base = Calendar.getInstance();
+        base.set(2000, 0, 1, 0, 0, 0);
+        base.set(Calendar.MILLISECOND, 0);
+        
+        Calendar cal = Calendar.getInstance();
+        cal.set(2003, 1, 1, 0, 0, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        duration = cal.getTime().getTime() - base.getTime().getTime(); // duration from 2000-01-01 to cal
+        // don't use 1970 in test as time zones were less reliable in 1970 than now
+        // remember that duration formatting ignores time zones, working on strict hour lengths
+        int days = 366 + 365 + 365 + 31;
+        assertEquals( "0 0 " + days, DurationFormatUtils.formatDuration(duration, "y M d") );
+    }
+
+    public void testFormatPeriodISO(){
         TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
+        Calendar base = Calendar.getInstance(timeZone);
+        base.set(1970, 0, 1, 0, 0, 0);
+        base.set(Calendar.MILLISECOND, 0);
+        
         Calendar cal = Calendar.getInstance(timeZone);
         cal.set(2002, 1, 23, 9, 11, 12);
         cal.set(Calendar.MILLISECOND, 1);
@@ -157,121 +206,124 @@
         text = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(cal);
         assertEquals("2002-02-23T09:11:12-03:00", text);
         // test fixture is the same as above, but now with extended format.
-        text = DurationFormatUtils.format(cal.getTime().getTime(), DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
-        assertEquals("P32Y1M22DT9H11M12.1S", text);
+        text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(), DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
+        assertEquals("P32Y1M22DT9H11M12.001S", text);
         // test fixture from example in http://www.w3.org/TR/xmlschema-2/#duration
         cal.set(1971, 1, 3, 10, 30, 0);
         cal.set(Calendar.MILLISECOND, 0);
-        text = DurationFormatUtils.format(cal.getTime().getTime(), DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
-        assertEquals("P1Y1M2DT10H30M0.0S", text);
+        text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(), DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
+        assertEquals("P1Y1M2DT10H30M0.000S", text);
         // want a way to say 'don't print the seconds in format()' or other fields for that matter:
         //assertEquals("P1Y2M3DT10H30M", text);
     }
 
-    public void testFormat() {
-        long time = 0;
-        assertEquals( "0", DurationFormatUtils.format(time, "y") );
-        assertEquals( "0", DurationFormatUtils.format(time, "M") );
-        assertEquals( "0", DurationFormatUtils.format(time, "d") );
-        assertEquals( "0", DurationFormatUtils.format(time, "H") );
-        assertEquals( "0", DurationFormatUtils.format(time, "m") );
-        assertEquals( "0", DurationFormatUtils.format(time, "s") );
-        assertEquals( "0", DurationFormatUtils.format(time, "S") );
-        assertEquals( "0000", DurationFormatUtils.format(time, "SSSS") );
-        assertEquals( "0000", DurationFormatUtils.format(time, "yyyy") );
-        assertEquals( "0000", DurationFormatUtils.format(time, "yyMM") );
+    public void testFormatPeriod() {
+        Calendar cal1970 = Calendar.getInstance();
+        cal1970.set(1970, 0, 1, 0, 0, 0);
+        cal1970.set(Calendar.MILLISECOND, 0);
+        long time1970 = cal1970.getTime().getTime();
+        
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "y") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "M") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "d") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "H") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "m") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "s") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time1970, "S") );
+        assertEquals( "0000", DurationFormatUtils.formatPeriod(time1970, time1970, "SSSS") );
+        assertEquals( "0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyyy") );
+        assertEquals( "0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyMM") );
 
-        time = 60 * 1000;
-        assertEquals( "0", DurationFormatUtils.format(time, "y") );
-        assertEquals( "0", DurationFormatUtils.format(time, "M") );
-        assertEquals( "0", DurationFormatUtils.format(time, "d") );
-        assertEquals( "0", DurationFormatUtils.format(time, "H") );
-        assertEquals( "1", DurationFormatUtils.format(time, "m") );
-        assertEquals( "60", DurationFormatUtils.format(time, "s") );
-        assertEquals( "60000", DurationFormatUtils.format(time, "S") );
-        assertEquals( "01:00", DurationFormatUtils.format(time, "mm:ss") );
+        long time = time1970 + 60 * 1000;
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time, "y") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time, "M") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time, "d") );
+        assertEquals( "0", DurationFormatUtils.formatPeriod(time1970, time, "H") );
+        assertEquals( "1", DurationFormatUtils.formatPeriod(time1970, time, "m") );
+        assertEquals( "60", DurationFormatUtils.formatPeriod(time1970, time, "s") );
+        assertEquals( "60000", DurationFormatUtils.formatPeriod(time1970, time, "S") );
+        assertEquals( "01:00", DurationFormatUtils.formatPeriod(time1970, time, "mm:ss") );
 
         Calendar cal = Calendar.getInstance();
         cal.set(1973, 6, 1, 0, 0, 0);
         cal.set(Calendar.MILLISECOND, 0);
         time = cal.getTime().getTime();
-        assertEquals( "36", DurationFormatUtils.format(time, "yM") );
-        assertEquals( "3 years 6 months", DurationFormatUtils.format(time, "y' years 'M' months'") );
-        assertEquals( "03/06", DurationFormatUtils.format(time, "yy/MM") );
+        assertEquals( "36", DurationFormatUtils.formatPeriod(time1970, time, "yM") );
+        assertEquals( "3 years 6 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'") );
+        assertEquals( "03/06", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM") );
 
         cal.set(1973, 10, 1, 0, 0, 0);
         cal.set(Calendar.MILLISECOND, 0);
         time = cal.getTime().getTime();
-        assertEquals( "310", DurationFormatUtils.format(time, "yM") );
-        assertEquals( "3 years 10 months", DurationFormatUtils.format(time, "y' years 'M' months'") );
-        assertEquals( "03/10", DurationFormatUtils.format(time, "yy/MM") );
+        assertEquals( "310", DurationFormatUtils.formatPeriod(time1970, time, "yM") );
+        assertEquals( "3 years 10 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'") );
+        assertEquals( "03/10", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM") );
 
         cal.set(1974, 0, 1, 0, 0, 0);
         cal.set(Calendar.MILLISECOND, 0);
         time = cal.getTime().getTime();
-        assertEquals( "40", DurationFormatUtils.format(time, "yM") );
-        assertEquals( "4 years 0 months", DurationFormatUtils.format(time, "y' years 'M' months'") );
-        assertEquals( "04/00", DurationFormatUtils.format(time, "yy/MM") );
-        assertEquals( "48", DurationFormatUtils.format(time, "M") );
-        assertEquals( "48", DurationFormatUtils.format(time, "MM") );
-        assertEquals( "048", DurationFormatUtils.format(time, "MMM") );
+        assertEquals( "40", DurationFormatUtils.formatPeriod(time1970, time, "yM") );
+        assertEquals( "4 years 0 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'") );
+        assertEquals( "04/00", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM") );
+        assertEquals( "48", DurationFormatUtils.formatPeriod(time1970, time, "M") );
+        assertEquals( "48", DurationFormatUtils.formatPeriod(time1970, time, "MM") );
+        assertEquals( "048", DurationFormatUtils.formatPeriod(time1970, time, "MMM") );
     }
 
     public void testLexx() {
         // tests each constant
         assertArrayEquals( 
-          new Token[] { 
-            new Token( DurationFormatUtils.y, 1),
-            new Token( DurationFormatUtils.M, 1),
-            new Token( DurationFormatUtils.d, 1),
-            new Token( DurationFormatUtils.H, 1),
-            new Token( DurationFormatUtils.m, 1),
-            new Token( DurationFormatUtils.s, 1),
-            new Token( DurationFormatUtils.S, 1)
+          new DurationFormatUtils.Token[] { 
+            new DurationFormatUtils.Token( DurationFormatUtils.y, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.M, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.d, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.H, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.m, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.s, 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.S, 1)
           }, DurationFormatUtils.lexx("yMdHmsS") 
         );
 
         // tests the ISO8601-like
         assertArrayEquals( 
-          new Token[] { 
-            new Token( DurationFormatUtils.H, 1),
-            new Token( new StringBuffer(":"), 1),
-            new Token( DurationFormatUtils.m, 2),
-            new Token( new StringBuffer(":"), 1),
-            new Token( DurationFormatUtils.s, 2),
-            new Token( new StringBuffer("."), 1),
-            new Token( DurationFormatUtils.S, 3)
+          new DurationFormatUtils.Token[] { 
+            new DurationFormatUtils.Token( DurationFormatUtils.H, 1),
+            new DurationFormatUtils.Token( new StringBuffer(":"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.m, 2),
+            new DurationFormatUtils.Token( new StringBuffer(":"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.s, 2),
+            new DurationFormatUtils.Token( new StringBuffer("."), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.S, 3)
           }, DurationFormatUtils.lexx("H:mm:ss.SSS")
         );
 
         // test the iso extended format
         assertArrayEquals( 
-          new Token[] { 
-            new Token( new StringBuffer("P"), 1),
-            new Token( DurationFormatUtils.y, 4),
-            new Token( new StringBuffer("Y"), 1),
-            new Token( DurationFormatUtils.M, 1),
-            new Token( new StringBuffer("M"), 1),
-            new Token( DurationFormatUtils.d, 1),
-            new Token( new StringBuffer("DT"), 1),
-            new Token( DurationFormatUtils.H, 1),
-            new Token( new StringBuffer("H"), 1),
-            new Token( DurationFormatUtils.m, 1),
-            new Token( new StringBuffer("M"), 1),
-            new Token( DurationFormatUtils.s, 1),
-            new Token( new StringBuffer("."), 1),
-            new Token( DurationFormatUtils.S, 1),
-            new Token( new StringBuffer("S"), 1)
+          new DurationFormatUtils.Token[] { 
+            new DurationFormatUtils.Token( new StringBuffer("P"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.y, 4),
+            new DurationFormatUtils.Token( new StringBuffer("Y"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.M, 1),
+            new DurationFormatUtils.Token( new StringBuffer("M"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.d, 1),
+            new DurationFormatUtils.Token( new StringBuffer("DT"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.H, 1),
+            new DurationFormatUtils.Token( new StringBuffer("H"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.m, 1),
+            new DurationFormatUtils.Token( new StringBuffer("M"), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.s, 1),
+            new DurationFormatUtils.Token( new StringBuffer("."), 1),
+            new DurationFormatUtils.Token( DurationFormatUtils.S, 1),
+            new DurationFormatUtils.Token( new StringBuffer("S"), 1)
           }, 
           DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN)
         );
     }
-    private void assertArrayEquals(Token[] obj1, Token[] obj2) {
+    private void assertArrayEquals(DurationFormatUtils.Token[] obj1, DurationFormatUtils.Token[] obj2) {
         assertEquals( "Arrays are unequal length. ", obj1.length, obj2.length );
         for(int i=0; i<obj1.length; i++) {
             assertTrue( "Index " + i + " not equal, " + obj1[i] + " vs " + obj2, obj1[i].equals(obj2[i]));
         }
     }
 
-    
 }