You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ch...@apache.org on 2015/07/14 06:46:59 UTC

[lang] LANG-1154 FastDateFormat APIs that use a StringBuilder

Repository: commons-lang
Updated Branches:
  refs/heads/master 03fe88ab7 -> 61579335b


LANG-1154
FastDateFormat APIs that use a StringBuilder


Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/61579335
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/61579335
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/61579335

Branch: refs/heads/master
Commit: 61579335bcb3da854fc8be82b5e61dc52b5018d3
Parents: 03fe88a
Author: Chas Honton <ch...@apache.org>
Authored: Mon Jul 13 21:46:14 2015 -0700
Committer: Chas Honton <ch...@apache.org>
Committed: Mon Jul 13 21:46:14 2015 -0700

----------------------------------------------------------------------
 src/changes/changes.xml                         |   1 +
 .../apache/commons/lang3/time/DatePrinter.java  |  44 ++++-
 .../commons/lang3/time/FastDateFormat.java      |  56 +++++-
 .../commons/lang3/time/FastDatePrinter.java     | 172 +++++++++++++------
 .../commons/lang3/time/FastDateFormatTest.java  |  68 +++++---
 .../commons/lang3/time/FastDateParserTest.java  |  14 +-
 .../commons/lang3/time/FastDatePrinterTest.java |  57 +++++-
 7 files changed, 327 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cffa0b3..e6bd030 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
   <body>
 
   <release version="3.5" date="tba" description="tba">
+    <action issue="LANG-1154" type="add" dev="chas" due-to="Gary Gregory">FastDateFormat APIs that use a StringBuilder</action>
     <action issue="LANG-1149" type="add" dev="chas" due-to="Gregory Zak">Ability to throw checked exceptions without declaring them</action>
     <action issue="LANG-1002" type="fix" dev="chas" due-to="Michael Osipov">Several predefined ISO FastDateFormats in DateFormatUtils are incorrect</action>
     <action issue="LANG-1152" type="fix" dev="chas" due-to="Pas Filip">StringIndexOutOfBoundsException or field over-write for large year fields in FastDateParser</action>

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/time/DatePrinter.java b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
index 6808628..a2e97a9 100644
--- a/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
+++ b/src/main/java/org/apache/commons/lang3/time/DatePrinter.java
@@ -59,23 +59,27 @@ public interface DatePrinter {
     String format(Calendar calendar);
 
     /**
-     * <p>Formats a milliseond {@code long} value into the
+     * <p>Formats a millisecond {@code long} value into the
      * supplied {@code StringBuffer}.</p>
+     * @deprecated Use {{@link #format(long, Appendable)}.
      *
      * @param millis  the millisecond value to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     StringBuffer format(long millis, StringBuffer buf);
 
     /**
      * <p>Formats a {@code Date} object into the
      * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
+     * @deprecated Use {{@link #format(Date, Appendable)}.
      *
      * @param date  the date to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     StringBuffer format(Date date, StringBuffer buf);
 
     /**
@@ -83,13 +87,51 @@ public interface DatePrinter {
      * The TimeZone set on the Calendar is only used to adjust the time offset.
      * The TimeZone specified during the construction of the Parser will determine the TimeZone
      * used in the formatted string.
+     * @deprecated Use {{@link #format(Calendar, Appendable)}.
      *
      * @param calendar  the calendar to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     StringBuffer format(Calendar calendar, StringBuffer buf);
 
+    /**
+     * <p>Formats a millisecond {@code long} value into the
+     * supplied {@code Appendable}.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+     */
+    <B extends Appendable> B format(long millis, B buf);
+
+    /**
+     * <p>Formats a {@code Date} object into the
+     * supplied {@code Appendable} using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+     */
+    <B extends Appendable> B format(Date date, B buf);
+
+    /**
+     * <p>Formats a {@code Calendar} object into the supplied {@code Appendable}.</p>
+     * The TimeZone set on the Calendar is only used to adjust the time offset.
+     * The TimeZone specified during the construction of the Parser will determine the TimeZone
+     * used in the formatted string.
+     *
+     * @param calendar  the calendar to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+     */
+    <B extends Appendable> B format(Calendar calendar, B buf);
+
+
     // Accessors
     //-----------------------------------------------------------------------
     /**

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
index b4cc85d..36d080f 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
@@ -399,6 +399,7 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
     /**
      * <p>Formats a {@code Date}, {@code Calendar} or
      * {@code Long} (milliseconds) object.</p>
+     * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
      *
      * @param obj  the object to format
      * @param toAppendTo  the buffer to append to
@@ -407,7 +408,7 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
      */
     @Override
     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
-        return printer.format(obj, toAppendTo, pos);
+        return toAppendTo.append(printer.format(obj));
     }
 
     /**
@@ -447,12 +448,14 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
     /**
      * <p>Formats a millisecond {@code long} value into the
      * supplied {@code StringBuffer}.</p>
+     * @deprecated Use {{@link #format(long, Appendable)}.
      *
      * @param millis  the millisecond value to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      * @since 2.1
      */
+    @Deprecated
     @Override
     public StringBuffer format(final long millis, final StringBuffer buf) {
         return printer.format(millis, buf);
@@ -461,11 +464,13 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
     /**
      * <p>Formats a {@code Date} object into the
      * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
+     * @deprecated Use {{@link #format(Date, Appendable)}.
      *
      * @param date  the date to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     @Override
     public StringBuffer format(final Date date, final StringBuffer buf) {
         return printer.format(date, buf);
@@ -474,16 +479,60 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
     /**
      * <p>Formats a {@code Calendar} object into the
      * supplied {@code StringBuffer}.</p>
+     * @deprecated Use {{@link #format(Calendar, Appendable)}.
      *
      * @param calendar  the calendar to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     @Override
     public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
         return printer.format(calendar, buf);
     }
 
+    /**
+     * <p>Formats a millisecond {@code long} value into the
+     * supplied {@code StringBuffer}.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+     */
+    @Override
+    public <B extends Appendable> B format(final long millis, final B buf) {
+        return printer.format(millis, buf);
+    }
+
+    /**
+     * <p>Formats a {@code Date} object into the
+     * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+     */
+    @Override
+    public <B extends Appendable> B format(final Date date, final B buf) {
+        return printer.format(date, buf);
+    }
+
+    /**
+     * <p>Formats a {@code Calendar} object into the
+     * supplied {@code StringBuffer}.</p>
+     *
+     * @param calendar  the calendar to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 3.5
+    */
+    @Override
+    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
+        return printer.format(calendar, buf);
+    }
+
     // Parsing
     //-----------------------------------------------------------------------
 
@@ -597,18 +646,17 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
         return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
     }
 
-
     /**
      * <p>Performs the formatting by applying the rules to the
      * specified calendar.</p>
+     * @deprecated Use {{@link #format(Calendar, Appendable)
      *
      * @param calendar  the calendar to format
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
+    @Deprecated
     protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
         return printer.applyRules(calendar, buf);
     }
-
-
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
index 84bf7e2..14c6d68 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
@@ -32,6 +32,8 @@ import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
 /**
  * <p>FastDatePrinter is a fast and thread-safe version of
  * {@link java.text.SimpleDateFormat}.</p>
@@ -387,12 +389,13 @@ public class FastDatePrinter implements DatePrinter, Serializable {
     /**
      * <p>Formats a {@code Date}, {@code Calendar} or
      * {@code Long} (milliseconds) object.</p>
-     *
+     * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}, or {{@link #format(Object)}
      * @param obj  the object to format
      * @param toAppendTo  the buffer to append to
      * @param pos  the position - ignored
      * @return the buffer passed in
      */
+    @Deprecated
     @Override
     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
         if (obj instanceof Date) {
@@ -407,6 +410,26 @@ public class FastDatePrinter implements DatePrinter, Serializable {
         }
     }
 
+    /**
+     * <p>Formats a {@code Date}, {@code Calendar} or
+     * {@code Long} (milliseconds) object.</p>
+     * @since 3.5
+     * @param obj  the object to format
+     * @return The formatted value.
+     */
+    String format(Object obj) {
+        if (obj instanceof Date) {
+            return format((Date) obj);
+        } else if (obj instanceof Calendar) {
+            return format((Calendar) obj);
+        } else if (obj instanceof Long) {
+            return format(((Long) obj).longValue());
+        } else {
+            throw new IllegalArgumentException("Unknown class: " +
+                (obj == null ? "<null>" : obj.getClass().getName()));
+        }
+    }
+
     /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DatePrinter#format(long)
      */
@@ -423,7 +446,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
      * @return a String representation of the given Calendar.
      */
     private String applyRulesToString(final Calendar c) {
-        return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
+        return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString();
     }
 
     /**
@@ -450,7 +473,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
      */
     @Override
     public String format(final Calendar calendar) {
-        return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
+        return format(calendar, new StringBuilder(mMaxLengthEstimate)).toString();
     }
 
     /* (non-Javadoc)
@@ -480,6 +503,33 @@ public class FastDatePrinter implements DatePrinter, Serializable {
         return format(calendar.getTime(), buf);
     }
 
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.Appendable)
+     */
+    @Override
+    public <B extends Appendable> B format(final long millis, final B buf) {
+        return format(new Date(millis), buf);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.Appendable)
+     */
+    @Override
+    public <B extends Appendable> B format(final Date date, final B buf) {
+        final Calendar c = newCalendar();  // hard code GregorianCalendar
+        c.setTime(date);
+        return applyRules(c, buf);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.Appendable)
+     */
+    @Override
+    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
+        // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
+        return format(calendar.getTime(), buf);
+    }
+
     /**
      * <p>Performs the formatting by applying the rules to the
      * specified calendar.</p>
@@ -488,9 +538,13 @@ public class FastDatePrinter implements DatePrinter, Serializable {
      * @param buf  the buffer to format into
      * @return the specified string buffer
      */
-    protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
-        for (final Rule rule : mRules) {
-            rule.appendTo(buf, calendar);
+    protected <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
+        try {
+            for (final Rule rule : mRules) {
+                rule.appendTo(buf, calendar);
+            }
+        } catch (IOException ioe) {
+            ExceptionUtils.rethrow(ioe);
         }
         return buf;
     }
@@ -589,16 +643,45 @@ public class FastDatePrinter implements DatePrinter, Serializable {
     }
 
     /**
-     * Appends digits to the given buffer.
-     * 
+     * Appends two digits to the given buffer.
+     *
      * @param buffer the buffer to append to.
      * @param value the value to append digits from.
      */
-    private static void appendDigits(final StringBuffer buffer, final int value) {
+    private static void appendDigits(final Appendable buffer, final int value) throws IOException {
         buffer.append((char)(value / 10 + '0'));
         buffer.append((char)(value % 10 + '0'));
     }
 
+    private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3
+
+    /**
+     * Appends all digits to the given buffer.
+     *
+     * @param buffer the buffer to append to.
+     * @param value the value to append digits from.
+     */
+    private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException {
+        // build up decimal representation in reverse
+        char[] work = new char[MAX_DIGITS];
+        int digit = 0;
+        while(value!=0) {
+            work[digit++] = (char)(value % 10 + '0');
+            value = value / 10;
+        }
+
+        // pad with zeros
+        while(digit<minFieldWidth) {
+            buffer.append('0');
+            --minFieldWidth;
+        }
+
+        // reverse
+        while(--digit>=0) {
+            buffer.append(work[digit]);
+        }
+    }
+
     // Rules
     //-----------------------------------------------------------------------
     /**
@@ -615,10 +698,10 @@ public class FastDatePrinter implements DatePrinter, Serializable {
         /**
          * Appends the value of the specified calendar to the output buffer based on the rule implementation.
          *
-         * @param buffer the output buffer
+         * @param buf the output buffer
          * @param calendar calendar to be appended
          */
-        void appendTo(StringBuffer buffer, Calendar calendar);
+        void appendTo(Appendable buf, Calendar calendar) throws IOException;
     }
 
     /**
@@ -631,7 +714,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * @param buffer the output buffer
          * @param value the value to be appended
          */
-        void appendTo(StringBuffer buffer, int value);
+        void appendTo(Appendable buffer, int value) throws IOException;
     }
 
     /**
@@ -662,7 +745,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             buffer.append(mValue);
         }
     }
@@ -695,7 +778,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             buffer.append(mValue);
         }
     }
@@ -738,7 +821,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             buffer.append(mValues[calendar.get(mField)]);
         }
     }
@@ -770,7 +853,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(mField));
         }
 
@@ -778,13 +861,13 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, final int value) {
+        public final void appendTo(final Appendable buffer, final int value) throws IOException {
             if (value < 10) {
                 buffer.append((char)(value + '0'));
             } else if (value < 100) {
                 appendDigits(buffer, value);
             } else {
-                buffer.append(value);
+               appendFullDigits(buffer, value, 1);
             }
         }
     }
@@ -815,7 +898,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
         }
 
@@ -823,7 +906,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, final int value) {
+        public final void appendTo(final Appendable buffer, final int value) throws IOException {
             if (value < 10) {
                 buffer.append((char)(value + '0'));
             } else {
@@ -866,7 +949,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(mField));
         }
 
@@ -874,23 +957,8 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, int value) {
-            int first = buffer.length();
-            // pad the buffer with adequate zeros
-            for(int digit = 0; digit<mSize; ++digit) {
-                buffer.append('0');
-            }
-            // backfill the buffer with non-zero digits
-            int index = buffer.length();
-            for( ; value>0; value /= 10) {
-                char c= (char)('0' + value % 10);
-                if(--index<first) {
-                    buffer.insert(first, c);
-                }
-                else {
-                    buffer.setCharAt(index, c);
-                }
-            }
+        public final void appendTo(final Appendable buffer, int value) throws IOException {
+            appendFullDigits(buffer, value, mSize);
         }
     }
 
@@ -921,7 +989,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(mField));
         }
 
@@ -929,11 +997,11 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, final int value) {
+        public final void appendTo(final Appendable buffer, final int value) throws IOException {
             if (value < 100) {
                 appendDigits(buffer, value);
             } else {
-                buffer.append(value);
+                appendFullDigits(buffer, value, 2);
             }
         }
     }
@@ -963,7 +1031,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
         }
 
@@ -971,7 +1039,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, final int value) {
+        public final void appendTo(final Appendable buffer, final int value) throws IOException {
             appendDigits(buffer, value);
         }
     }
@@ -1001,7 +1069,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
         }
 
@@ -1009,7 +1077,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public final void appendTo(final StringBuffer buffer, final int value) {
+        public final void appendTo(final Appendable buffer, final int value) throws IOException {
             appendDigits(buffer, value);
         }
     }
@@ -1042,7 +1110,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             int value = calendar.get(Calendar.HOUR);
             if (value == 0) {
                 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
@@ -1054,7 +1122,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final int value) {
+        public void appendTo(final Appendable buffer, final int value) throws IOException {
             mRule.appendTo(buffer, value);
         }
     }
@@ -1087,7 +1155,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             int value = calendar.get(Calendar.HOUR_OF_DAY);
             if (value == 0) {
                 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
@@ -1099,7 +1167,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final int value) {
+        public void appendTo(final Appendable buffer, final int value) throws IOException {
             mRule.appendTo(buffer, value);
         }
     }
@@ -1170,7 +1238,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             final TimeZone zone = calendar.getTimeZone();
             if (calendar.get(Calendar.DST_OFFSET) != 0) {
                 buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale));
@@ -1211,7 +1279,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             
             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
 
@@ -1290,7 +1358,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
          * {@inheritDoc}
          */
         @Override
-        public void appendTo(final StringBuffer buffer, final Calendar calendar) {
+        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
             if (offset == 0) {
                 buffer.append("Z");

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
index 1b69403..564c10c 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
@@ -23,8 +23,9 @@ import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.text.FieldPosition;
 import java.text.Format;
-import java.text.ParseException;
+import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
@@ -33,7 +34,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
 
 import org.apache.commons.lang3.test.SystemDefaults;
 import org.apache.commons.lang3.test.SystemDefaultsSwitch;
@@ -232,33 +233,43 @@ public class FastDateFormatTest {
     @Test
     public void testParseSync() throws InterruptedException {
         final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
-        final FastDateFormat formatter= FastDateFormat.getInstance(pattern);
-        
-        final long sdfTime= measureTime(formatter, new SimpleDateFormat(pattern) {
-                        private static final long serialVersionUID = 1L;  // because SimpleDateFormat is serializable
+        final SimpleDateFormat inner = new SimpleDateFormat(pattern);
+        final Format sdf= new Format() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public StringBuffer format(Object obj,
+                    StringBuffer toAppendTo,
+                    FieldPosition fieldPosition) {
+                synchronized(this) {
+                    return inner.format(obj, toAppendTo, fieldPosition);
+                }
+            }
 
-                        @Override
-                        public Object parseObject(final String formattedDate) throws ParseException {
-                            synchronized(this) {
-                                return super.parse(formattedDate);
-                            }
-                        }
-        });
-        
-        final long fdfTime= measureTime(formatter, FastDateFormat.getInstance(pattern));
-        
-        final String times= ">>FastDateFormatTest: FastDateParser:"+fdfTime+"  SimpleDateFormat:"+sdfTime;
-        System.out.println(times);
+            @Override
+            public Object parseObject(String source, ParsePosition pos) {
+                synchronized(this) {
+                    return inner.parseObject(source, pos);
+                }
+            }
+        };
+        final AtomicLongArray sdfTime= measureTime(sdf, sdf);
+
+        Format fdf = FastDateFormat.getInstance(pattern);
+        final AtomicLongArray fdfTime= measureTime(fdf, fdf);
+
+        System.out.println(">>FastDateFormatTest: FastDatePrinter:"+fdfTime.get(0)+"  SimpleDateFormat:"+sdfTime.get(0));
+        System.out.println(">>FastDateFormatTest: FastDateParser:"+fdfTime.get(1)+"  SimpleDateFormat:"+sdfTime.get(1));
     }
 
     final static private int NTHREADS= 10;
     final static private int NROUNDS= 10000;
     
-    private long measureTime(final Format formatter, final Format parser) throws InterruptedException {
+    private AtomicLongArray measureTime(final Format printer, final Format parser) throws InterruptedException {
         final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
         final AtomicInteger failures= new AtomicInteger(0);
-        final AtomicLong totalElapsed= new AtomicLong(0);
-        
+        final AtomicLongArray totalElapsed= new AtomicLongArray(2);
+
         for(int i= 0; i<NTHREADS; ++i) {
             pool.submit(new Runnable() {
                 @Override
@@ -266,10 +277,15 @@ public class FastDateFormatTest {
                     for(int j= 0; j<NROUNDS; ++j) {
                         try {
                             final Date date= new Date();
-                            final String formattedDate= formatter.format(date);
-                            final long start= System.currentTimeMillis();        
+
+                            final long t0= System.currentTimeMillis();
+                            final String formattedDate= printer.format(date);
+                            totalElapsed.addAndGet(0, System.currentTimeMillis() - t0);
+
+                            final long t1 = System.currentTimeMillis();
                             final Object pd= parser.parseObject(formattedDate);
-                            totalElapsed.addAndGet(System.currentTimeMillis()-start);
+                            totalElapsed.addAndGet(1, System.currentTimeMillis() - t1);
+
                             if(!date.equals(pd)) {
                                 failures.incrementAndGet();
                             }
@@ -290,9 +306,9 @@ public class FastDateFormatTest {
             fail("did not complete tasks");
         }
         assertEquals(0, failures.get());
-        return totalElapsed.get();
+        return totalElapsed;
     }
-    
+
     @Test
     public void testLANG_1152() {
         TimeZone utc = TimeZone.getTimeZone("UTC");

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
index 330935c..4c18e64 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.Serializable;
 import java.text.ParseException;
+import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -666,7 +667,7 @@ public class FastDateParserTest {
     @Test
     public void testLang1121() throws ParseException {
         TimeZone kst = TimeZone.getTimeZone("KST");
-        final DateParser fdp = FastDateFormat.getInstance("yyyyMMdd", kst, Locale.KOREA);
+        final DateParser fdp = getInstance("yyyyMMdd", kst, Locale.KOREA);
 
         try {
             fdp.parse("2015");
@@ -690,4 +691,15 @@ public class FastDateParserTest {
         actual = fdp.parse("20150429113100");
         Assert.assertEquals(expected, actual);
     }
+
+    @Test
+    public void testParseOffset() throws ParseException {
+        DateParser parser = getInstance(YMD_SLASH);
+        final Date date = parser.parse("Today is 2015/07/04", new ParsePosition(9));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2015, Calendar.JULY, 4);
+        Assert.assertEquals(cal.getTime(), date);
+    }
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/61579335/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
index 74938e8..0727486 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.Serializable;
+import java.text.FieldPosition;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
@@ -30,8 +31,8 @@ import java.util.Locale;
 import java.util.TimeZone;
 
 import org.apache.commons.lang3.SerializationUtils;
-import org.apache.commons.lang3.test.SystemDefaultsSwitch;
 import org.apache.commons.lang3.test.SystemDefaults;
+import org.apache.commons.lang3.test.SystemDefaultsSwitch;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -364,4 +365,58 @@ public class FastDatePrinterTest {
             assertEquals("calendar", "2009-10-16T07:42:16 +0100", value);
         }
     }
+
+    @Test
+    public void testHourFormats() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.clear();
+        DatePrinter printer = getInstance("K k H h");
+
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        assertEquals("0 24 0 12", printer.format(calendar));
+
+        calendar.set(Calendar.HOUR_OF_DAY, 12);
+        assertEquals("0 12 12 12", printer.format(calendar));
+
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        assertEquals("11 23 23 11", printer.format(calendar));
+    }
+
+    @Test
+    public void testStringBufferOptions() {
+        final DatePrinter format = getInstance("yyyy-MM-dd HH:mm:ss.SSS Z", TimeZone.getTimeZone("GMT"));
+        Calendar calendar = Calendar.getInstance();
+        StringBuffer sb = new StringBuffer();
+        String expected = format.format(calendar, sb, new FieldPosition(0)).toString();
+        sb.setLength(0);
+        assertEquals(expected, format.format(calendar, sb).toString());
+        sb.setLength(0);
+
+        Date date = calendar.getTime();
+        assertEquals(expected, format.format(date, sb, new FieldPosition(0)).toString());
+        sb.setLength(0);
+        assertEquals(expected, format.format(date, sb).toString());
+        sb.setLength(0);
+
+        long epoch = date.getTime();
+        assertEquals(expected, format.format(epoch, sb, new FieldPosition(0)).toString());
+        sb.setLength(0);
+        assertEquals(expected, format.format(epoch, sb).toString());
+    }
+
+    @Test
+    public void testAppendableOptions() {
+        final DatePrinter format = getInstance("yyyy-MM-dd HH:mm:ss.SSS Z", TimeZone.getTimeZone("GMT"));
+        Calendar calendar = Calendar.getInstance();
+        StringBuilder sb = new StringBuilder();
+        String expected = format.format(calendar, sb).toString();
+        sb.setLength(0);
+
+        Date date = calendar.getTime();
+        assertEquals(expected, format.format(date, sb).toString());
+        sb.setLength(0);
+
+        long epoch = date.getTime();
+        assertEquals(expected, format.format(epoch, sb).toString());
+    }
 }