bayard 2004/08/26 23:45:25
Modified: lang/src/java/org/apache/commons/lang/time DateUtils.java
DurationFormatUtils.java
lang/src/test/org/apache/commons/lang/time
DurationFormatUtilsTest.java
Log:
DurationFormatUtils implemented in a java.text.Format like way with a pattern
language for specifying exactly how the format wants to appear. The two existing
methods are reimplemented in terms of the new format method but the new method
currently lacks its own unit tests.
Revision Changes Path
1.24 +5 -1
jakarta-commons/lang/src/java/org/apache/commons/lang/time/DateUtils.java
Index: DateUtils.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/lang/src/java/org/apache/commons/lang/time/DateUtils.java,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -r1.23 -r1.24
--- DateUtils.java 12 Jul 2004 00:04:32 -0000 1.23
+++ DateUtils.java 27 Aug 2004 06:45:25 -0000 1.24
@@ -56,6 +56,10 @@
*/
public static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
+ // hmm. not very accurate. used by DurationFormatUtils
+ static final long MILLIS_PER_YEAR = 365 * 24 * 60 * 60 * 1000;
+ static final long MILLIS_PER_MONTH = 365 / 12 * 24 * 60 * 60 * 1000;
+
/**
* This is half a month, so this represents whether a date is in the top
* or bottom half of the month.
1.11 +277 -65
jakarta-commons/lang/src/java/org/apache/commons/lang/time/DurationFormatUtils.java
Index: DurationFormatUtils.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/lang/src/java/org/apache/commons/lang/time/DurationFormatUtils.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- DurationFormatUtils.java 2 Aug 2004 02:16:21 -0000 1.10
+++ DurationFormatUtils.java 27 Aug 2004 06:45:25 -0000 1.11
@@ -15,6 +15,10 @@
*/
package org.apache.commons.lang.time;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.mutable.MutableInt;
+
/**
* <p>Duration formatting utilities and constants.</p>
*
@@ -26,8 +30,16 @@
* @since 2.0
* @version $Id$
*/
-class DurationFormatUtils {
- // TODO: Make class public once methods can fully select which fields to output
+public class DurationFormatUtils {
+
+ /**
+ * <p>DurationFormatUtils instances should NOT be constructed in standard
programming.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
instance
+ * to operate.</p>
+ */
+ public DurationFormatUtils() {
+ }
/**
* <p>Pattern used with <code>FastDateFormat</code> and
<code>SimpleDateFormat</code> for the ISO8601
@@ -61,37 +73,105 @@
* <p>Get the time gap as a string.</p>
*
* <p>The format used is ISO8601-like:
- * <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.</p>
+ * <i>H</i>:<i>m</i>:<i>s</i>.<i>S</i>.</p>
*
* @param millis the duration to format
* @return the time as a String
*/
public static String formatISO(long millis) {
- int hours, minutes, seconds, milliseconds;
- hours = (int) (millis / DateUtils.MILLIS_PER_HOUR);
- millis = millis - (hours * DateUtils.MILLIS_PER_HOUR);
- minutes = (int) (millis / DateUtils.MILLIS_PER_MINUTE);
- millis = millis - (minutes * DateUtils.MILLIS_PER_MINUTE);
- seconds = (int) (millis / DateUtils.MILLIS_PER_SECOND);
- millis = millis - (seconds * DateUtils.MILLIS_PER_SECOND);
- milliseconds = (int) millis;
-
- StringBuffer buf = new StringBuffer(32);
- buf.append(hours);
- buf.append(':');
- buf.append((char) (minutes / 10 + '0'));
- buf.append((char) (minutes % 10 + '0'));
- buf.append(':');
- buf.append((char) (seconds / 10 + '0'));
- buf.append((char) (seconds % 10 + '0'));
- buf.append('.');
- if (milliseconds < 10) {
- buf.append('0').append('0');
- } else if (milliseconds < 100) {
- buf.append('0');
+ return format(millis, "H:mm:ss.SSS");
+ }
+ /**
+ * <p>Get the time gap as a string, using the specified format.</p>
+ * <table border="1">
+ * <tr><th>character</th><th>duration element</th></tr>
+ * <tr><td>y</td><td>years (aka 365 days)</td></tr>
+ * <tr><td>M</td><td>months (aka year/12)</td></tr>
+ * <tr><td>d</td><td>days</td></tr>
+ * <tr><td>H</td><td>hours</td></tr>
+ * <tr><td>m</td><td>minutes</td></tr>
+ * <tr><td>s</td><td>seconds</td></tr>
+ * <tr><td>S</td><td>milliseconds</td></tr>
+ * </table>
+ *
+ * @param millis the duration to format
+ * @param format the way iin which to format the duration
+ * @return the time as a String
+ */
+ public static String format(long millis, String format) {
+ StringBuffer buffer = new StringBuffer();
+ Token[] tokens = lexx(format);
+ int sz = tokens.length;
+
+ int years = 0;
+ int months = 0;
+ int days = 0;
+ int hours = 0;
+ int minutes = 0;
+ int seconds = 0;
+ int milliseconds = 0;
+
+ if(Token.containsTokenWithValue(tokens, y) ) {
+ years = (int) (millis / DateUtils.MILLIS_PER_YEAR);
+ millis = millis - (years * DateUtils.MILLIS_PER_YEAR);
+ }
+ if(Token.containsTokenWithValue(tokens, M) ) {
+ months = (int) (millis / DateUtils.MILLIS_PER_MONTH);
+ millis = millis - (months * DateUtils.MILLIS_PER_MONTH);
+ }
+ if(Token.containsTokenWithValue(tokens, d) ) {
+ days = (int) (millis / DateUtils.MILLIS_PER_DAY);
+ millis = millis - (days * DateUtils.MILLIS_PER_DAY);
}
- buf.append(milliseconds);
- return buf.toString();
+ if(Token.containsTokenWithValue(tokens, H) ) {
+ hours = (int) (millis / DateUtils.MILLIS_PER_HOUR);
+ millis = millis - (hours * DateUtils.MILLIS_PER_HOUR);
+ }
+ 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;
+ }
+
+
+ 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( StringUtils.leftPad(""+years, count, "0") );
+ } else
+ if(value == M) {
+ buffer.append( StringUtils.leftPad(""+months, count, "0") );
+ } else
+ if(value == d) {
+ buffer.append( StringUtils.leftPad(""+days, count, "0") );
+ } else
+ if(value == H) {
+ buffer.append( StringUtils.leftPad(""+hours, count, "0") );
+ } else
+ if(value == m) {
+ buffer.append( StringUtils.leftPad(""+minutes, count, "0") );
+ } else
+ if(value == s) {
+ buffer.append( StringUtils.leftPad(""+seconds, count, "0") );
+ } else
+ if(value == S) {
+ buffer.append( StringUtils.leftPad(""+milliseconds, count, "0")
);
+ }
+ }
+ }
+
+ return buffer.toString();
}
/**
@@ -106,51 +186,183 @@
long millis,
boolean suppressLeadingZeroElements,
boolean suppressTrailingZeroElements) {
- long[] values = new long[4];
- values[0] = millis / DateUtils.MILLIS_PER_DAY;
- values[1] = (millis / DateUtils.MILLIS_PER_HOUR) % 24;
- values[2] = (millis / DateUtils.MILLIS_PER_MINUTE) % 60;
- values[3] = (millis / DateUtils.MILLIS_PER_SECOND) % 60;
- String[] fieldsOne = { " day ", " hour ", " minute ", " second" };
- String[] fieldsPlural = { " days ", " hours ", " minutes ", " seconds" };
-
- StringBuffer buf = new StringBuffer(64);
- boolean valueOutput = false;
-
- for (int i = 0; i < 4; i++) {
- long value = values[i];
- if (value == 0) {
- // handle zero
- if (valueOutput) {
- if (suppressTrailingZeroElements == false) {
- buf.append('0').append(fieldsPlural[i]);
+
+ // 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", "");
}
- } else {
- if (suppressLeadingZeroElements == false) {
- buf.append('0').append(fieldsPlural[i]);
+ }
+ }
+ 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", "");
}
}
- } else if (value == 1) {
- // one
- valueOutput = true;
- buf.append('1').append(fieldsOne[i]);
- } else {
- // other
- valueOutput = true;
- buf.append(value).append(fieldsPlural[i]);
}
}
+ // 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";
+ static final Object H = "H";
+ static final Object m = "m";
+ static final Object s = "s";
+ static final Object S = "S";
+
+ static Token[] lexx(String format) {
+ char[] array = format.toCharArray();
+ java.util.ArrayList list = new java.util.ArrayList(array.length);
- return buf.toString().trim();
+ boolean inLiteral = false;
+ StringBuffer buffer = null;
+ Token previous = null;
+ int sz = array.length;
+ for(int i=0; i<sz; i++) {
+ char ch = array[i];
+ if(inLiteral && ch != '\'') {
+ buffer.append(ch);
+ continue;
+ }
+ Object value = null;
+ switch(ch) {
+ // TODO: Need to handle escaping of '
+ case '\'' :
+ if(inLiteral) {
+ buffer = null;
+ inLiteral = false;
+ } else {
+ buffer = new StringBuffer();
+ list.add(new Token(buffer));
+ inLiteral = true;
+ }
+ break;
+ case 'y' : value = y; break;
+ case 'M' : value = M; break;
+ case 'd' : value = d; break;
+ case 'H' : value = H; break;
+ case 'm' : value = m; break;
+ case 's' : value = s; break;
+ case 'S' : value = S; break;
+ default :
+ if(buffer == null) {
+ buffer = new StringBuffer();
+ list.add(new Token(buffer));
+ }
+ buffer.append(ch);
+ }
+
+ if(value != null) {
+ if(previous != null && previous.getValue() == value) {
+ previous.increment();
+ } else {
+ Token token = new Token(value);
+ list.add(token);
+ previous = token;
+ }
+ buffer = null;
+ }
+ }
+ return (Token[]) list.toArray( new Token[0] );
}
- /**
- * <p>DurationFormatUtils instances should NOT be constructed in standard
programming.</p>
- *
- * <p>This constructor is public to permit tools that require a JavaBean
instance
- * to operate.</p>
- */
- public DurationFormatUtils() {
+}
+
+// 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;
+ }
+ }
+ 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);
}
}
1.8 +57 -1
jakarta-commons/lang/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java
Index: DurationFormatUtilsTest.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/lang/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -r1.7 -r1.8
--- DurationFormatUtilsTest.java 18 Feb 2004 23:03:03 -0000 1.7
+++ DurationFormatUtilsTest.java 27 Aug 2004 06:45:25 -0000 1.8
@@ -56,7 +56,7 @@
Constructor[] cons = DurationFormatUtils.class.getDeclaredConstructors();
assertEquals(1, cons.length);
assertEquals(true, Modifier.isPublic(cons[0].getModifiers()));
- assertEquals(false,
Modifier.isPublic(DurationFormatUtils.class.getModifiers()));
+ assertEquals(true,
Modifier.isPublic(DurationFormatUtils.class.getModifiers()));
assertEquals(false,
Modifier.isFinal(DurationFormatUtils.class.getModifiers()));
}
@@ -165,6 +165,62 @@
assertEquals("P1Y2M3DT10H30M0.0S", text);
// want a way to say 'don't print the seconds in format()' or other fields
for that matter:
//assertEquals("P1Y2M3DT10H30M", text);
+ }
+
+ 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)
+ }, 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)
+ }, 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)
+ },
+ DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN)
+ );
+ }
+ private void assertArrayEquals(Token[] obj1, 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]));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]