You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ba...@apache.org on 2012/01/26 08:00:27 UTC

svn commit: r1236055 [2/2] - in /commons/proper/lang/trunk: ./ src/main/java/org/apache/commons/lang3/time/ src/test/java/org/apache/commons/lang3/time/

Added: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java?rev=1236055&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java (added)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java Thu Jan 26 07:00:26 2012
@@ -0,0 +1,1209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * <p>FastDatePrinter is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.</p>
+ *
+ * <p>This class can be used as a direct replacement to
+ * {@code SimpleDateFormat} in most formatting situations.
+ * This class is especially useful in multi-threaded server environments.
+ * {@code SimpleDateFormat} is not thread-safe in any JDK version,
+ * nor will it be as Sun have closed the bug/RFE.
+ * </p>
+ *
+ * <p>Only formatting is supported, but all patterns are compatible with
+ * SimpleDateFormat (except time zones and some year patterns - see below).</p>
+ *
+ * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
+ * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
+ * This pattern letter can be used here (on all JDK versions).</p>
+ *
+ * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
+ * ISO8601 full format time zones (eg. {@code +08:00} or {@code -11:00}).
+ * This introduces a minor incompatibility with Java 1.4, but at a gain of
+ * useful functionality.</p>
+ *
+ * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
+ * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
+ * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
+ * 'YYY' will be formatted as '2003', while it was '03' in former Java
+ * versions. FastDatePrinter implements the behavior of Java 7.</p>
+ *
+ * @since 3.2
+ */
+public class FastDatePrinter implements DatePrinter, Serializable {
+    // A lot of the speed in this class comes from caching, but some comes
+    // from the special int to StringBuffer conversion.
+    //
+    // The following produces a padded 2 digit number:
+    //   buffer.append((char)(value / 10 + '0'));
+    //   buffer.append((char)(value % 10 + '0'));
+    //
+    // Note that the fastest append to StringBuffer is a single char (used here).
+    // Note that Integer.toString() is not called, the conversion is simply
+    // taking the value and adding (mathematically) the ASCII value for '0'.
+    // So, don't change this code! It works and is very fast.
+
+    /**
+     * Required for serialization support.
+     *
+     * @see java.io.Serializable
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * FULL locale dependent date or time style.
+     */
+    public static final int FULL = DateFormat.FULL;
+    /**
+     * LONG locale dependent date or time style.
+     */
+    public static final int LONG = DateFormat.LONG;
+    /**
+     * MEDIUM locale dependent date or time style.
+     */
+    public static final int MEDIUM = DateFormat.MEDIUM;
+    /**
+     * SHORT locale dependent date or time style.
+     */
+    public static final int SHORT = DateFormat.SHORT;
+
+    /**
+     * The pattern.
+     */
+    private final String mPattern;
+    /**
+     * The time zone.
+     */
+    private final TimeZone mTimeZone;
+    /**
+     * The locale.
+     */
+    private final Locale mLocale;
+    /**
+     * The parsed rules.
+     */
+    private transient Rule[] mRules;
+    /**
+     * The estimated maximum length.
+     */
+    private transient int mMaxLengthEstimate;
+
+    // Constructor
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Constructs a new FastDatePrinter.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
+     * @param timeZone  non-null time zone to use
+     * @param locale  non-null locale to use
+     * @throws NullPointerException if pattern, timeZone, or locale is null.
+     */
+    protected FastDatePrinter(String pattern, TimeZone timeZone, Locale locale) {
+        mPattern = pattern;
+        mTimeZone = timeZone;
+        mLocale = locale;
+
+        init();
+    }
+
+    /**
+     * <p>Initializes the instance for first use.</p>
+     */
+    private void init() {
+        List<Rule> rulesList = parsePattern();
+        mRules = rulesList.toArray(new Rule[rulesList.size()]);
+
+        int len = 0;
+        for (int i=mRules.length; --i >= 0; ) {
+            len += mRules[i].estimateLength();
+        }
+
+        mMaxLengthEstimate = len;
+    }
+
+    // Parse the pattern
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Returns a list of Rules given a pattern.</p>
+     *
+     * @return a {@code List} of Rule objects
+     * @throws IllegalArgumentException if pattern is invalid
+     */
+    protected List<Rule> parsePattern() {
+        DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
+        List<Rule> rules = new ArrayList<Rule>();
+
+        String[] ERAs = symbols.getEras();
+        String[] months = symbols.getMonths();
+        String[] shortMonths = symbols.getShortMonths();
+        String[] weekdays = symbols.getWeekdays();
+        String[] shortWeekdays = symbols.getShortWeekdays();
+        String[] AmPmStrings = symbols.getAmPmStrings();
+
+        int length = mPattern.length();
+        int[] indexRef = new int[1];
+
+        for (int i = 0; i < length; i++) {
+            indexRef[0] = i;
+            String token = parseToken(mPattern, indexRef);
+            i = indexRef[0];
+
+            int tokenLen = token.length();
+            if (tokenLen == 0) {
+                break;
+            }
+
+            Rule rule;
+            char c = token.charAt(0);
+
+            switch (c) {
+            case 'G': // era designator (text)
+                rule = new TextField(Calendar.ERA, ERAs);
+                break;
+            case 'y': // year (number)
+                if (tokenLen == 2) {
+                    rule = TwoDigitYearField.INSTANCE;
+                } else {
+                    rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
+                }
+                break;
+            case 'M': // month in year (text and number)
+                if (tokenLen >= 4) {
+                    rule = new TextField(Calendar.MONTH, months);
+                } else if (tokenLen == 3) {
+                    rule = new TextField(Calendar.MONTH, shortMonths);
+                } else if (tokenLen == 2) {
+                    rule = TwoDigitMonthField.INSTANCE;
+                } else {
+                    rule = UnpaddedMonthField.INSTANCE;
+                }
+                break;
+            case 'd': // day in month (number)
+                rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
+                break;
+            case 'h': // hour in am/pm (number, 1..12)
+                rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
+                break;
+            case 'H': // hour in day (number, 0..23)
+                rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
+                break;
+            case 'm': // minute in hour (number)
+                rule = selectNumberRule(Calendar.MINUTE, tokenLen);
+                break;
+            case 's': // second in minute (number)
+                rule = selectNumberRule(Calendar.SECOND, tokenLen);
+                break;
+            case 'S': // millisecond (number)
+                rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
+                break;
+            case 'E': // day in week (text)
+                rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
+                break;
+            case 'D': // day in year (number)
+                rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
+                break;
+            case 'F': // day of week in month (number)
+                rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
+                break;
+            case 'w': // week in year (number)
+                rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
+                break;
+            case 'W': // week in month (number)
+                rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
+                break;
+            case 'a': // am/pm marker (text)
+                rule = new TextField(Calendar.AM_PM, AmPmStrings);
+                break;
+            case 'k': // hour in day (1..24)
+                rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
+                break;
+            case 'K': // hour in am/pm (0..11)
+                rule = selectNumberRule(Calendar.HOUR, tokenLen);
+                break;
+            case 'z': // time zone (text)
+                if (tokenLen >= 4) {
+                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
+                } else {
+                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
+                }
+                break;
+            case 'Z': // time zone (value)
+                if (tokenLen == 1) {
+                    rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
+                } else {
+                    rule = TimeZoneNumberRule.INSTANCE_COLON;
+                }
+                break;
+            case '\'': // literal text
+                String sub = token.substring(1);
+                if (sub.length() == 1) {
+                    rule = new CharacterLiteral(sub.charAt(0));
+                } else {
+                    rule = new StringLiteral(sub);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal pattern component: " + token);
+            }
+
+            rules.add(rule);
+        }
+
+        return rules;
+    }
+
+    /**
+     * <p>Performs the parsing of tokens.</p>
+     *
+     * @param pattern  the pattern
+     * @param indexRef  index references
+     * @return parsed token
+     */
+    protected String parseToken(String pattern, int[] indexRef) {
+        StringBuilder buf = new StringBuilder();
+
+        int i = indexRef[0];
+        int length = pattern.length();
+
+        char c = pattern.charAt(i);
+        if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
+            // Scan a run of the same character, which indicates a time
+            // pattern.
+            buf.append(c);
+
+            while (i + 1 < length) {
+                char peek = pattern.charAt(i + 1);
+                if (peek == c) {
+                    buf.append(c);
+                    i++;
+                } else {
+                    break;
+                }
+            }
+        } else {
+            // This will identify token as text.
+            buf.append('\'');
+
+            boolean inLiteral = false;
+
+            for (; i < length; i++) {
+                c = pattern.charAt(i);
+
+                if (c == '\'') {
+                    if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
+                        // '' is treated as escaped '
+                        i++;
+                        buf.append(c);
+                    } else {
+                        inLiteral = !inLiteral;
+                    }
+                } else if (!inLiteral &&
+                         (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
+                    i--;
+                    break;
+                } else {
+                    buf.append(c);
+                }
+            }
+        }
+
+        indexRef[0] = i;
+        return buf.toString();
+    }
+
+    /**
+     * <p>Gets an appropriate rule for the padding required.</p>
+     *
+     * @param field  the field to get a rule for
+     * @param padding  the padding required
+     * @return a new rule with the correct padding
+     */
+    protected NumberRule selectNumberRule(int field, int padding) {
+        switch (padding) {
+        case 1:
+            return new UnpaddedNumberField(field);
+        case 2:
+            return new TwoDigitNumberField(field);
+        default:
+            return new PaddedNumberField(field, padding);
+        }
+    }
+
+    // Format methods
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Formats a {@code Date}, {@code Calendar} or
+     * {@code Long} (milliseconds) object.</p>
+     *
+     * @param obj  the object to format
+     * @param toAppendTo  the buffer to append to
+     * @param pos  the position - ignored
+     * @return the buffer passed in
+     */
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+        if (obj instanceof Date) {
+            return format((Date) obj, toAppendTo);
+        } else if (obj instanceof Calendar) {
+            return format((Calendar) obj, toAppendTo);
+        } else if (obj instanceof Long) {
+            return format(((Long) obj).longValue(), toAppendTo);
+        } else {
+            throw new IllegalArgumentException("Unknown class: " +
+                (obj == null ? "<null>" : obj.getClass().getName()));
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(long)
+     */
+    @Override
+    public String format(long millis) {
+        return format(new Date(millis));
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date)
+     */
+    @Override
+    public String format(Date date) {
+        Calendar c = new GregorianCalendar(mTimeZone, mLocale);  // hard code GregorianCalendar
+        c.setTime(date);
+        return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar)
+     */
+    @Override
+    public String format(Calendar calendar) {
+        return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.StringBuffer)
+     */
+    @Override
+    public StringBuffer format(long millis, StringBuffer buf) {
+        return format(new Date(millis), buf);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.StringBuffer)
+     */
+    @Override
+    public StringBuffer format(Date date, StringBuffer buf) {
+        Calendar c = new GregorianCalendar(mTimeZone, mLocale);  // 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.StringBuffer)
+     */
+    @Override
+    public StringBuffer format(Calendar calendar, StringBuffer buf) {
+        return applyRules(calendar, buf);
+    }
+
+    /**
+     * <p>Performs the formatting by applying the rules to the
+     * specified calendar.</p>
+     *
+     * @param calendar  the calendar to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     */
+    protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
+        for (Rule rule : mRules) {
+            rule.appendTo(buf, calendar);
+        }
+        return buf;
+    }
+
+    // Accessors
+    //-----------------------------------------------------------------------
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#getPattern()
+     */
+    @Override
+    public String getPattern() {
+        return mPattern;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone()
+     */
+    @Override
+    public TimeZone getTimeZone() {
+        return mTimeZone;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DatePrinter#getLocale()
+     */
+    @Override
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    /**
+     * <p>Gets an estimate for the maximum string length that the
+     * formatter will produce.</p>
+     *
+     * <p>The actual formatted length will almost always be less than or
+     * equal to this amount.</p>
+     *
+     * @return the maximum formatted length
+     */
+    public int getMaxLengthEstimate() {
+        return mMaxLengthEstimate;
+    }
+
+    // Basics
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Compares two objects for equality.</p>
+     *
+     * @param obj  the object to compare to
+     * @return {@code true} if equal
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FastDatePrinter == false) {
+            return false;
+        }
+        FastDatePrinter other = (FastDatePrinter) obj;
+        return mPattern.equals(other.mPattern)
+            && mTimeZone.equals(other.mTimeZone) 
+            && mLocale.equals(other.mLocale);
+    }
+
+    /**
+     * <p>Returns a hashcode compatible with equals.</p>
+     *
+     * @return a hashcode compatible with equals
+     */
+    @Override
+    public int hashCode() {
+        return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
+    }
+
+    /**
+     * <p>Gets a debugging string version of this formatter.</p>
+     *
+     * @return a debugging string
+     */
+    @Override
+    public String toString() {
+        return "FastDatePrinter[" + mPattern + "," + mLocale + "," + mTimeZone.getID() + "]";
+    }
+
+    // Serializing
+    //-----------------------------------------------------------------------
+    /**
+     * Create the object after serialization. This implementation reinitializes the
+     * transient properties.
+     *
+     * @param in ObjectInputStream from which the object is being deserialized.
+     * @throws IOException if there is an IO issue.
+     * @throws ClassNotFoundException if a class cannot be found.
+     */
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        init();
+    }
+
+    // Rules
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Inner class defining a rule.</p>
+     */
+    private interface Rule {
+        /**
+         * Returns the estimated lentgh of the result.
+         *
+         * @return the estimated length
+         */
+        int estimateLength();
+
+        /**
+         * Appends the value of the specified calendar to the output buffer based on the rule implementation.
+         *
+         * @param buffer the output buffer
+         * @param calendar calendar to be appended
+         */
+        void appendTo(StringBuffer buffer, Calendar calendar);
+    }
+
+    /**
+     * <p>Inner class defining a numeric rule.</p>
+     */
+    private interface NumberRule extends Rule {
+        /**
+         * Appends the specified value to the output buffer based on the rule implementation.
+         *
+         * @param buffer the output buffer
+         * @param value the value to be appended
+         */
+        void appendTo(StringBuffer buffer, int value);
+    }
+
+    /**
+     * <p>Inner class to output a constant single character.</p>
+     */
+    private static class CharacterLiteral implements Rule {
+        private final char mValue;
+
+        /**
+         * Constructs a new instance of {@code CharacterLiteral}
+         * to hold the specified value.
+         *
+         * @param value the character literal
+         */
+        CharacterLiteral(char value) {
+            mValue = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 1;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            buffer.append(mValue);
+        }
+    }
+
+    /**
+     * <p>Inner class to output a constant string.</p>
+     */
+    private static class StringLiteral implements Rule {
+        private final String mValue;
+
+        /**
+         * Constructs a new instance of {@code StringLiteral}
+         * to hold the specified value.
+         *
+         * @param value the string literal
+         */
+        StringLiteral(String value) {
+            mValue = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return mValue.length();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            buffer.append(mValue);
+        }
+    }
+
+    /**
+     * <p>Inner class to output one of a set of values.</p>
+     */
+    private static class TextField implements Rule {
+        private final int mField;
+        private final String[] mValues;
+
+        /**
+         * Constructs an instance of {@code TextField}
+         * with the specified field and values.
+         *
+         * @param field the field
+         * @param values the field values
+         */
+        TextField(int field, String[] values) {
+            mField = field;
+            mValues = values;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            int max = 0;
+            for (int i=mValues.length; --i >= 0; ) {
+                int len = mValues[i].length();
+                if (len > max) {
+                    max = len;
+                }
+            }
+            return max;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            buffer.append(mValues[calendar.get(mField)]);
+        }
+    }
+
+    /**
+     * <p>Inner class to output an unpadded number.</p>
+     */
+    private static class UnpaddedNumberField implements NumberRule {
+        private final int mField;
+
+        /**
+         * Constructs an instance of {@code UnpadedNumberField} with the specified field.
+         *
+         * @param field the field
+         */
+        UnpaddedNumberField(int field) {
+            mField = field;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 4;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(mField));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            if (value < 10) {
+                buffer.append((char)(value + '0'));
+            } else if (value < 100) {
+                buffer.append((char)(value / 10 + '0'));
+                buffer.append((char)(value % 10 + '0'));
+            } else {
+                buffer.append(Integer.toString(value));
+            }
+        }
+    }
+
+    /**
+     * <p>Inner class to output an unpadded month.</p>
+     */
+    private static class UnpaddedMonthField implements NumberRule {
+        static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
+
+        /**
+         * Constructs an instance of {@code UnpaddedMonthField}.
+         *
+         */
+        UnpaddedMonthField() {
+            super();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 2;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            if (value < 10) {
+                buffer.append((char)(value + '0'));
+            } else {
+                buffer.append((char)(value / 10 + '0'));
+                buffer.append((char)(value % 10 + '0'));
+            }
+        }
+    }
+
+    /**
+     * <p>Inner class to output a padded number.</p>
+     */
+    private static class PaddedNumberField implements NumberRule {
+        private final int mField;
+        private final int mSize;
+
+        /**
+         * Constructs an instance of {@code PaddedNumberField}.
+         *
+         * @param field the field
+         * @param size size of the output field
+         */
+        PaddedNumberField(int field, int size) {
+            if (size < 3) {
+                // Should use UnpaddedNumberField or TwoDigitNumberField.
+                throw new IllegalArgumentException();
+            }
+            mField = field;
+            mSize = size;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 4;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(mField));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            if (value < 100) {
+                for (int i = mSize; --i >= 2; ) {
+                    buffer.append('0');
+                }
+                buffer.append((char)(value / 10 + '0'));
+                buffer.append((char)(value % 10 + '0'));
+            } else {
+                int digits;
+                if (value < 1000) {
+                    digits = 3;
+                } else {
+                    Validate.isTrue(value > -1, "Negative values should not be possible", value);
+                    digits = Integer.toString(value).length();
+                }
+                for (int i = mSize; --i >= digits; ) {
+                    buffer.append('0');
+                }
+                buffer.append(Integer.toString(value));
+            }
+        }
+    }
+
+    /**
+     * <p>Inner class to output a two digit number.</p>
+     */
+    private static class TwoDigitNumberField implements NumberRule {
+        private final int mField;
+
+        /**
+         * Constructs an instance of {@code TwoDigitNumberField} with the specified field.
+         *
+         * @param field the field
+         */
+        TwoDigitNumberField(int field) {
+            mField = field;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 2;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(mField));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            if (value < 100) {
+                buffer.append((char)(value / 10 + '0'));
+                buffer.append((char)(value % 10 + '0'));
+            } else {
+                buffer.append(Integer.toString(value));
+            }
+        }
+    }
+
+    /**
+     * <p>Inner class to output a two digit year.</p>
+     */
+    private static class TwoDigitYearField implements NumberRule {
+        static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
+
+        /**
+         * Constructs an instance of {@code TwoDigitYearField}.
+         */
+        TwoDigitYearField() {
+            super();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 2;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            buffer.append((char)(value / 10 + '0'));
+            buffer.append((char)(value % 10 + '0'));
+        }
+    }
+
+    /**
+     * <p>Inner class to output a two digit month.</p>
+     */
+    private static class TwoDigitMonthField implements NumberRule {
+        static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
+
+        /**
+         * Constructs an instance of {@code TwoDigitMonthField}.
+         */
+        TwoDigitMonthField() {
+            super();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 2;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public final void appendTo(StringBuffer buffer, int value) {
+            buffer.append((char)(value / 10 + '0'));
+            buffer.append((char)(value % 10 + '0'));
+        }
+    }
+
+    /**
+     * <p>Inner class to output the twelve hour field.</p>
+     */
+    private static class TwelveHourField implements NumberRule {
+        private final NumberRule mRule;
+
+        /**
+         * Constructs an instance of {@code TwelveHourField} with the specified
+         * {@code NumberRule}.
+         *
+         * @param rule the rule
+         */
+        TwelveHourField(NumberRule rule) {
+            mRule = rule;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return mRule.estimateLength();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            int value = calendar.get(Calendar.HOUR);
+            if (value == 0) {
+                value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
+            }
+            mRule.appendTo(buffer, value);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, int value) {
+            mRule.appendTo(buffer, value);
+        }
+    }
+
+    /**
+     * <p>Inner class to output the twenty four hour field.</p>
+     */
+    private static class TwentyFourHourField implements NumberRule {
+        private final NumberRule mRule;
+
+        /**
+         * Constructs an instance of {@code TwentyFourHourField} with the specified
+         * {@code NumberRule}.
+         *
+         * @param rule the rule
+         */
+        TwentyFourHourField(NumberRule rule) {
+            mRule = rule;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return mRule.estimateLength();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            int value = calendar.get(Calendar.HOUR_OF_DAY);
+            if (value == 0) {
+                value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
+            }
+            mRule.appendTo(buffer, value);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, int value) {
+            mRule.appendTo(buffer, value);
+        }
+    }
+
+    //-----------------------------------------------------------------------
+
+    private static ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
+        new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
+    /**
+     * <p>Gets the time zone display name, using a cache for performance.</p>
+     *
+     * @param tz  the zone to query
+     * @param daylight  true if daylight savings
+     * @param style  the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
+     * @param locale  the locale to use
+     * @return the textual name of the time zone
+     */
+    static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
+        TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
+        String value = cTimeZoneDisplayCache.get(key);
+        if (value == null) {
+            // This is a very slow call, so cache the results.
+            value = tz.getDisplayName(daylight, style, locale);
+            String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
+            if (prior != null) {
+                value= prior;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * <p>Inner class to output a time zone name.</p>
+     */
+    private static class TimeZoneNameRule implements Rule {
+        private final TimeZone mTimeZone;
+        private final String mStandard;
+        private final String mDaylight;
+
+        /**
+         * Constructs an instance of {@code TimeZoneNameRule} with the specified properties.
+         *
+         * @param timeZone the time zone
+         * @param locale the locale
+         * @param style the style
+         */
+        TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) {
+            mTimeZone = timeZone;
+
+            mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
+            mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return Math.max(mStandard.length(), mDaylight.length());
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
+                buffer.append(mDaylight);
+            } else {
+                buffer.append(mStandard);
+            }
+        }
+    }
+
+    /**
+     * <p>Inner class to output a time zone as a number {@code +/-HHMM}
+     * or {@code +/-HH:MM}.</p>
+     */
+    private static class TimeZoneNumberRule implements Rule {
+        static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
+        static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
+
+        final boolean mColon;
+
+        /**
+         * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties.
+         *
+         * @param colon add colon between HH and MM in the output if {@code true}
+         */
+        TimeZoneNumberRule(boolean colon) {
+            mColon = colon;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int estimateLength() {
+            return 5;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void appendTo(StringBuffer buffer, Calendar calendar) {
+            int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+
+            if (offset < 0) {
+                buffer.append('-');
+                offset = -offset;
+            } else {
+                buffer.append('+');
+            }
+
+            int hours = offset / (60 * 60 * 1000);
+            buffer.append((char)(hours / 10 + '0'));
+            buffer.append((char)(hours % 10 + '0'));
+
+            if (mColon) {
+                buffer.append(':');
+            }
+
+            int minutes = offset / (60 * 1000) - 60 * hours;
+            buffer.append((char)(minutes / 10 + '0'));
+            buffer.append((char)(minutes % 10 + '0'));
+        }
+    }
+
+    // ----------------------------------------------------------------------
+    /**
+     * <p>Inner class that acts as a compound key for time zone names.</p>
+     */
+    private static class TimeZoneDisplayKey {
+        private final TimeZone mTimeZone;
+        private final int mStyle;
+        private final Locale mLocale;
+
+        /**
+         * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties.
+         *
+         * @param timeZone the time zone
+         * @param daylight adjust the style for daylight saving time if {@code true}
+         * @param style the timezone style
+         * @param locale the timezone locale
+         */
+        TimeZoneDisplayKey(TimeZone timeZone,
+                           boolean daylight, int style, Locale locale) {
+            mTimeZone = timeZone;
+            if (daylight) {
+                style |= 0x80000000;
+            }
+            mStyle = style;
+            mLocale = locale;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int hashCode() {
+            return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof TimeZoneDisplayKey) {
+                TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
+                return
+                    mTimeZone.equals(other.mTimeZone) &&
+                    mStyle == other.mStyle &&
+                    mLocale.equals(other.mLocale);
+            }
+            return false;
+        }
+    }
+}

Propchange: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java?rev=1236055&r1=1236054&r2=1236055&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java Thu Jan 26 07:00:26 2012
@@ -41,7 +41,7 @@ abstract class FormatCache<F extends For
     private final ConcurrentMap<MultipartKey, F> cInstanceCache 
         = new ConcurrentHashMap<MultipartKey, F>(7);
     
-    private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache 
+    private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache 
         = new ConcurrentHashMap<MultipartKey, String>(7);
 
     /**
@@ -120,6 +120,20 @@ abstract class FormatCache<F extends For
         if (locale == null) {
             locale = Locale.getDefault();
         }
+        String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
+        return getInstance(pattern, timeZone, locale);
+    }
+
+    /**
+     * <p>Gets a date/time format for the specified styles and locale.</p>
+     * 
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
+     * @param locale  The non-null locale of the desired format
+     * @return a localized standard date/time format
+     * @throws IllegalArgumentException if the Locale has no date/time pattern defined
+     */
+    public static String getPatternForStyle(Integer dateStyle, Integer timeStyle, Locale locale) {
         MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
 
         String pattern = cDateTimeInstanceCache.get(key);
@@ -147,8 +161,7 @@ abstract class FormatCache<F extends For
                 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
             }
         }
-        
-        return getInstance(pattern, timeZone, locale);
+        return pattern;
     }
 
     // ----------------------------------------------------------------------
@@ -172,12 +185,9 @@ abstract class FormatCache<F extends For
          */
         @Override
         public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if ( obj instanceof MultipartKey == false ) {
-                return false;
-            }
+            // Eliminate the usual boilerplate because
+            // this inner static class is only used in a generic ConcurrentHashMap
+            // which will not compare against other Object types
             return Arrays.equals(keys, ((MultipartKey)obj).keys);
         }
 

Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java?rev=1236055&r1=1236054&r2=1236055&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java Thu Jan 26 07:00:26 2012
@@ -16,16 +16,25 @@
  */
 package org.apache.commons.lang3.time;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.text.Format;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Calendar;
 import java.util.Date;
-import java.util.GregorianCalendar;
 import java.util.Locale;
 import java.util.TimeZone;
+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 junit.framework.TestCase;
-
-import org.apache.commons.lang3.SerializationUtils;
+import org.junit.Test;
 
 /**
  * Unit tests {@link org.apache.commons.lang3.time.FastDateFormat}.
@@ -33,19 +42,21 @@ import org.apache.commons.lang3.Serializ
  * @since 2.0
  * @version $Id$
  */
-public class FastDateFormatTest extends TestCase {
-
-    public FastDateFormatTest(String name) {
-        super(name);
-    }
+public class FastDateFormatTest {
 
+    /*
+     * Only the cache methods need to be tested here.  
+     * The print methods are tested by {@link FastDateFormat_PrinterTest}
+     * and the parse methods are tested by {@link FastDateFormat_ParserTest}
+     */
+    @Test
     public void test_getInstance() {
         FastDateFormat format1 = FastDateFormat.getInstance();
         FastDateFormat format2 = FastDateFormat.getInstance();
         assertSame(format1, format2);
-        assertEquals(new SimpleDateFormat().toPattern(), format1.getPattern());
     }
 
+    @Test
     public void test_getInstance_String() {
         FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
         FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy");
@@ -58,6 +69,7 @@ public class FastDateFormatTest extends 
         assertEquals(TimeZone.getDefault(), format2.getTimeZone());
     }
 
+    @Test
     public void test_getInstance_String_TimeZone() {
         Locale realDefaultLocale = Locale.getDefault();
         TimeZone realDefaultZone = TimeZone.getDefault();
@@ -86,6 +98,7 @@ public class FastDateFormatTest extends 
         }
     }
 
+    @Test
     public void test_getInstance_String_Locale() {
         Locale realDefaultLocale = Locale.getDefault();
         try {
@@ -103,6 +116,7 @@ public class FastDateFormatTest extends 
         }
     }
 
+    @Test
     public void test_changeDefault_Locale_DateInstance() {
         Locale realDefaultLocale = Locale.getDefault();
         try {
@@ -123,6 +137,7 @@ public class FastDateFormatTest extends 
         }
     }
 
+    @Test
     public void test_changeDefault_Locale_DateTimeInstance() {
         Locale realDefaultLocale = Locale.getDefault();
         try {
@@ -143,6 +158,7 @@ public class FastDateFormatTest extends 
         }
     }
 
+    @Test
     public void test_getInstance_String_TimeZone_Locale() {
         Locale realDefaultLocale = Locale.getDefault();
         TimeZone realDefaultZone = TimeZone.getDefault();
@@ -168,154 +184,128 @@ public class FastDateFormatTest extends 
             Locale.setDefault(realDefaultLocale);
             TimeZone.setDefault(realDefaultZone);
         }
-    }
-
-    public void testFormat() {
-        Locale realDefaultLocale = Locale.getDefault();
-        TimeZone realDefaultZone = TimeZone.getDefault();
-        try {
-            Locale.setDefault(Locale.US);
-            TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
+    }       
 
-            GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
-            GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
-            Date date1 = cal1.getTime();
-            Date date2 = cal2.getTime();
-            long millis1 = date1.getTime();
-            long millis2 = date2.getTime();
-
-            FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-            assertEquals(sdf.format(date1), fdf.format(date1));
-            assertEquals("2003-01-10T15:33:20", fdf.format(date1));
-            assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
-            assertEquals("2003-01-10T15:33:20", fdf.format(millis1));
-            assertEquals("2003-07-10T09:00:00", fdf.format(date2));
-            assertEquals("2003-07-10T09:00:00", fdf.format(cal2));
-            assertEquals("2003-07-10T09:00:00", fdf.format(millis2));
-
-            fdf = FastDateFormat.getInstance("Z");
-            assertEquals("-0500", fdf.format(date1));
-            assertEquals("-0500", fdf.format(cal1));
-            assertEquals("-0500", fdf.format(millis1));
-
-            assertEquals("-0400", fdf.format(date2));
-            assertEquals("-0400", fdf.format(cal2));
-            assertEquals("-0400", fdf.format(millis2));
-
-            fdf = FastDateFormat.getInstance("ZZ");
-            assertEquals("-05:00", fdf.format(date1));
-            assertEquals("-05:00", fdf.format(cal1));
-            assertEquals("-05:00", fdf.format(millis1));
-
-            assertEquals("-04:00", fdf.format(date2));
-            assertEquals("-04:00", fdf.format(cal2));
-            assertEquals("-04:00", fdf.format(millis2));
-
-            String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M" +
-                " dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z";
-            fdf = FastDateFormat.getInstance(pattern);
-            sdf = new SimpleDateFormat(pattern);
-            // SDF bug fix starting with Java 7
-            assertEquals(sdf.format(date1).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date1));
-            assertEquals(sdf.format(date2).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date2));
-        } finally {
-            Locale.setDefault(realDefaultLocale);
-            TimeZone.setDefault(realDefaultZone);
+    @Test
+    public void testCheckDefaults() {
+        FastDateFormat format = FastDateFormat.getInstance();
+        FastDateFormat medium = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT);
+        assertEquals(medium, format);
+        
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        assertEquals(sdf.toPattern(), format.getPattern());
+        
+        assertEquals(Locale.getDefault(), format.getLocale());
+        assertEquals(TimeZone.getDefault(), format.getTimeZone());        
+    }
+
+    @Test
+    public void testCheckDifferingStyles() {
+        FastDateFormat shortShort = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT, Locale.US);
+        FastDateFormat shortLong = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.LONG, Locale.US);
+        FastDateFormat longShort = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT, Locale.US);
+        FastDateFormat longLong = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.LONG, Locale.US);
+        
+        assertFalse(shortShort.equals(shortLong));
+        assertFalse(shortShort.equals(longShort));
+        assertFalse(shortShort.equals(longLong));      
+        assertFalse(shortLong.equals(longShort));
+        assertFalse(shortLong.equals(longLong));
+        assertFalse(longShort.equals(longLong));
+    }
+
+    @Test
+    public void testDateDefaults() {
+        assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, Locale.CANADA), 
+                FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
+        
+        assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")), 
+                FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+        assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG), 
+                FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
+    }
+
+    @Test
+    public void testTimeDefaults() {
+        assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, Locale.CANADA),
+                FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
+
+        assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
+                FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+        assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
+                FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
+    }
+
+    @Test
+    public void testTimeDateDefaults() {
+        assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, Locale.CANADA),
+                FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.CANADA));
+
+        assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York")),
+                FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
+
+        assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM),
+                FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.getDefault()));
+    }
+
+    @Test
+    public void testParseSync() throws ParseException, InterruptedException {
+        final FastDateFormat formatter= FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
+        
+        long sdfTime= measureTime(formatter, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z") {
+                        private static final long serialVersionUID = 1L;  // because SimpleDateFormat is serializable
+
+                        @Override
+                        public Object parseObject(String formattedDate) throws ParseException {
+                            synchronized(this) {
+                                return super.parse(formattedDate);
+                            }
+                        }
+        });
+        
+        long fdfTime= measureTime(formatter, FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS Z"));
+        
+        String times= "FastDateParser:"+fdfTime+"  SimpleDateFormat:"+sdfTime;
+        System.out.println(times);
+    }
+
+    final static private int NTHREADS= 10;
+    final static private int NROUNDS= 10000;
+    
+    private long measureTime(final Format formatter, final Format parser) throws ParseException, InterruptedException {
+        final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
+        final AtomicInteger failures= new AtomicInteger(0);
+        final AtomicLong totalElapsed= new AtomicLong(0);
+        
+        for(int i= 0; i<NTHREADS; ++i) {
+            pool.submit(new Runnable() {
+                public void run() {
+                    for(int i= 0; i<NROUNDS; ++i) {
+                        try {
+                            Date date= new Date();
+                            String formattedDate= formatter.format(date);
+                            long start= System.currentTimeMillis();        
+                            Object pd= parser.parseObject(formattedDate);
+                            totalElapsed.addAndGet(System.currentTimeMillis()-start);
+                            if(!date.equals(pd)) {
+                                failures.incrementAndGet();
+                            }
+                        } catch (Exception e) {
+                            failures.incrementAndGet();
+                            e.printStackTrace();
+                        }
+                    }
+                }                
+            });
         }
-    }
-
-    /**
-     * Test case for {@link FastDateFormat#getDateInstance(int, java.util.Locale)}.
-     */
-    public void testShortDateStyleWithLocales() {
-        Locale usLocale = Locale.US;
-        Locale swedishLocale = new Locale("sv", "SE");
-        Calendar cal = Calendar.getInstance();
-        cal.set(2004, 1, 3);
-        FastDateFormat fdf = FastDateFormat.getDateInstance(FastDateFormat.SHORT, usLocale);
-        assertEquals("2/3/04", fdf.format(cal));
-
-        fdf = FastDateFormat.getDateInstance(FastDateFormat.SHORT, swedishLocale);
-        assertEquals("2004-02-03", fdf.format(cal));
-
-    }
-
-    /**
-     * Tests that pre-1000AD years get padded with yyyy
-     */
-    public void testLowYearPadding() {
-        Calendar cal = Calendar.getInstance();
-        FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/DD");
-
-        cal.set(1,0,1);
-        assertEquals("0001/01/01", format.format(cal));
-        cal.set(10,0,1);
-        assertEquals("0010/01/01", format.format(cal));
-        cal.set(100,0,1);
-        assertEquals("0100/01/01", format.format(cal));
-        cal.set(999,0,1);
-        assertEquals("0999/01/01", format.format(cal));
-    }
-    /**
-     * Show Bug #39410 is solved
-     */
-    public void testMilleniumBug() {
-        Calendar cal = Calendar.getInstance();
-        FastDateFormat format = FastDateFormat.getInstance("dd.MM.yyyy");
-
-        cal.set(1000,0,1);
-        assertEquals("01.01.1000", format.format(cal));
-    }
-
-    /**
-     * testLowYearPadding showed that the date was buggy
-     * This test confirms it, getting 366 back as a date
-     */
-    public void testSimpleDate() {
-        Calendar cal = Calendar.getInstance();
-        FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
-
-        cal.set(2004,11,31);
-        assertEquals("2004/12/31", format.format(cal));
-        cal.set(999,11,31);
-        assertEquals("0999/12/31", format.format(cal));
-        cal.set(1,2,2);
-        assertEquals("0001/03/02", format.format(cal));
-    }
-
-    public void testLang303() {
-        Calendar cal = Calendar.getInstance();
-        cal.set(2004,11,31);
-
-        FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
-        String output = format.format(cal);
-
-        format = (FastDateFormat) SerializationUtils.deserialize( SerializationUtils.serialize( format ) );
-        assertEquals(output, format.format(cal));
-    }
-
-    public void testLang538() {
-        // more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
-        // for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
-        GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
-        cal.clear();
-        cal.set(2009, 9, 16, 8, 42, 16);
-
-        FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
-        assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
-        assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
-    }
-
-    public void testLang645() {
-        Locale locale = new Locale("sv", "SE");
-
-        Calendar cal = Calendar.getInstance();
-        cal.set(2010, 0, 1, 12, 0, 0);
-        Date d = cal.getTime();
-
-        FastDateFormat fdf = FastDateFormat.getInstance("EEEE', week 'ww", locale);
-
-        assertEquals("fredag, week 53", fdf.format(d));
+        pool.shutdown();                        
+        if(!pool.awaitTermination(20, TimeUnit.SECONDS)) {
+            pool.shutdownNow();
+            fail("did not complete tasks");
+        }
+        assertEquals(0, failures.get());
+        return totalElapsed.get();
     }
 }

Added: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_ParserTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_ParserTest.java?rev=1236055&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_ParserTest.java (added)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_ParserTest.java Thu Jan 26 07:00:26 2012
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Unit tests for the parse methods of FastDateFormat
+ *
+ * @since 3.2
+ */
+public class FastDateFormat_ParserTest extends FastDateParserTest {
+
+    @Override
+    protected DateParser getInstance(String format, TimeZone timeZone, Locale locale) {
+        return FastDateFormat.getInstance(format, timeZone, locale);
+    }
+}

Propchange: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_ParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java?rev=1236055&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java (added)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java Thu Jan 26 07:00:26 2012
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Unit tests for the print methods of FastDateFormat
+ *
+ * @since 3.2
+ */
+public class FastDateFormat_PrinterTest extends FastDatePrinterTest {
+
+    @Override
+    protected DatePrinter getInstance(String format, TimeZone timeZone, Locale locale) {
+        return FastDateFormat.getInstance(format, timeZone, locale);
+    }
+}

Propchange: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormat_PrinterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java?rev=1236055&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java (added)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java Thu Jan 26 07:00:26 2012
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional inparserion regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDateParser}.
+ *
+ * @since 3.2
+ */
+public class FastDateParserTest {
+    private static final String yMdHmsSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS Z";
+    private static final String DMY_DOT = "dd.MM.yyyy";
+    private static final String YMD_SLASH = "yyyy/MM/dd";
+    private static final String MDY_DASH = "MM-DD-yyyy";
+    private static final String MDY_SLASH = "MM/DD/yyyy";
+    private static final TimeZone REYKJAVIK = TimeZone.getTimeZone("Atlantic/Reykjavik");
+    private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+    private static final Locale SWEDEN = new Locale("sv", "SE");
+
+    DateParser getInstance(String format) {
+        return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DateParser getDateInstance(int dateStyle, Locale locale) {
+        return getInstance(FormatCache.getPatternForStyle(dateStyle, null, locale), TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DateParser getInstance(String format, Locale locale) {
+        return getInstance(format, TimeZone.getDefault(), locale);
+    }
+
+    private DateParser getInstance(String format, TimeZone timeZone) {
+        return getInstance(format, timeZone, Locale.getDefault());
+    }
+
+    /**
+     * Override this method in derived tests to change the construction of instances
+     * @param format
+     * @param timeZone
+     * @param locale
+     * @return
+     */
+    protected DateParser getInstance(String format, TimeZone timeZone, Locale locale) {
+        return new FastDateParser(format, timeZone, locale);
+    }
+
+    @Test
+    public void test_Equality_Hash() {        
+        DateParser[] parsers= {
+            getInstance(yMdHmsSZ, NEW_YORK, Locale.US),
+            getInstance(DMY_DOT, NEW_YORK, Locale.US),
+            getInstance(YMD_SLASH, NEW_YORK, Locale.US),
+            getInstance(MDY_DASH, NEW_YORK, Locale.US),
+            getInstance(MDY_SLASH, NEW_YORK, Locale.US),
+            getInstance(MDY_SLASH, REYKJAVIK, Locale.US),
+            getInstance(MDY_SLASH, REYKJAVIK, SWEDEN)
+        };
+        
+        Map<DateParser,Integer> map= new HashMap<DateParser,Integer>();
+        int i= 0;
+        for(DateParser parser:parsers) {
+            map.put(parser, i++);            
+        }
+
+        i= 0;
+        for(DateParser parser:parsers) {
+            assertEquals(i++, (int)map.get(parser));
+        }        
+    }
+
+    @Test
+    public void testParseZone() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, 6, 10, 16, 33, 20);
+        
+        DateParser fdf = getInstance(yMdHmsSZ, NEW_YORK, Locale.US);
+        
+        assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 -0500"));
+        assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 GMT-05:00"));
+        assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 Eastern Daylight Time"));
+        assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 EDT"));
+        
+        cal.setTimeZone(TimeZone.getTimeZone("GMT-3"));
+        cal.set(2003, 1, 10, 9, 0, 0);
+        
+        assertEquals(cal.getTime(), fdf.parse("2003-02-10T09:00:00.000 -0300"));
+        
+        cal.setTimeZone(TimeZone.getTimeZone("GMT+5"));
+        cal.set(2003, 1, 10, 15, 5, 6);
+       
+        assertEquals(cal.getTime(), fdf.parse("2003-02-10T15:05:06.000 +0500"));
+    }
+    
+    @Test
+    public void testParseLongShort() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);        
+        cal.clear();
+        cal.set(2003, 1, 10, 15, 33, 20);
+        cal.set(Calendar.MILLISECOND, 989);
+        cal.setTimeZone(NEW_YORK);
+        
+        DateParser fdf = getInstance("yyyy GGGG MMMM dddd aaaa EEEE HHHH mmmm ssss SSSS ZZZZ", NEW_YORK, Locale.US);
+        
+        assertEquals(cal.getTime(), fdf.parse("2003 AD February 0010 PM Monday 0015 0033 0020 0989 GMT-05:00"));
+        cal.set(Calendar.ERA, GregorianCalendar.BC);
+        
+        Date parse = fdf.parse("2003 BC February 0010 PM Saturday 0015 0033 0020 0989 GMT-05:00");
+                assertEquals(cal.getTime(), parse);
+                
+        fdf = getInstance("y G M d a E H m s S Z");
+        assertEquals(cal.getTime(), fdf.parse("03 BC 2 10 PM Sat 15 33 20 989 -0500"));
+        
+        cal.set(Calendar.ERA, GregorianCalendar.AD);
+        assertEquals(cal.getTime(), fdf.parse("03 AD 2 10 PM Saturday 15 33 20 989 -0500"));
+    }
+    
+    @Test
+    public void testAmPm() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        
+        DateParser h = getInstance("yyyy-MM-dd hh a mm:ss", NEW_YORK, Locale.US);        
+        DateParser K = getInstance("yyyy-MM-dd KK a mm:ss", NEW_YORK, Locale.US);        
+        DateParser k = getInstance("yyyy-MM-dd kk:mm:ss", NEW_YORK, Locale.US);        
+        DateParser H = getInstance("yyyy-MM-dd HH:mm:ss", NEW_YORK, Locale.US);        
+
+        cal.set(2010, 7, 1, 0, 33, 20);
+        assertEquals(cal.getTime(), h.parse("2010-08-01 12 AM 33:20"));
+        assertEquals(cal.getTime(), K.parse("2010-08-01 0 AM 33:20"));
+        assertEquals(cal.getTime(), k.parse("2010-08-01 00:33:20"));
+        assertEquals(cal.getTime(), H.parse("2010-08-01 00:33:20"));
+        
+        cal.set(2010, 7, 1, 3, 33, 20);
+        assertEquals(cal.getTime(), h.parse("2010-08-01 3 AM 33:20"));
+        assertEquals(cal.getTime(), K.parse("2010-08-01 3 AM 33:20"));
+        assertEquals(cal.getTime(), k.parse("2010-08-01 03:33:20"));
+        assertEquals(cal.getTime(), H.parse("2010-08-01 03:33:20"));
+
+        cal.set(2010, 7, 1, 15, 33, 20);
+        assertEquals(cal.getTime(), h.parse("2010-08-01 3 PM 33:20"));
+        assertEquals(cal.getTime(), K.parse("2010-08-01 3 PM 33:20"));
+        assertEquals(cal.getTime(), k.parse("2010-08-01 15:33:20"));
+        assertEquals(cal.getTime(), H.parse("2010-08-01 15:33:20"));
+
+        cal.set(2010, 7, 1, 12, 33, 20);
+        assertEquals(cal.getTime(), h.parse("2010-08-01 12 PM 33:20"));
+        assertEquals(cal.getTime(), K.parse("2010-08-01 0 PM 33:20"));
+        assertEquals(cal.getTime(), k.parse("2010-08-01 12:33:20"));
+        assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20"));
+    }
+    
+    @Test
+    public void testLocales() throws ParseException {
+                
+        for(Locale locale : Locale.getAvailableLocales()) {
+            Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+            cal.clear();
+            cal.set(2003, 1, 10);
+
+            try {
+                String longFormat= "GGGG/yyyy/MMMM/dddd/aaaa/EEEE/ZZZZ";
+                SimpleDateFormat sdf = new SimpleDateFormat(longFormat, locale);
+                DateParser fdf = getInstance(longFormat, locale);
+                
+                                checkParse(cal, sdf, fdf);
+                
+                cal.set(Calendar.ERA, GregorianCalendar.BC);
+                                checkParse(cal, sdf, fdf);
+                        
+                String shortFormat= "G/y/M/d/a/E/Z";
+                sdf = new SimpleDateFormat(shortFormat, locale);
+                fdf = getInstance(shortFormat, locale);
+                                checkParse(cal, sdf, fdf);
+                
+                cal.set(Calendar.ERA, GregorianCalendar.AD);
+                                checkParse(cal, sdf, fdf);
+            }
+            catch(ParseException ex) {
+                // TODO: why do ja_JP_JP, hi_IN, th_TH, and th_TH_TH fail?
+                System.out.println("Locale "+locale+ " failed");
+            }
+        }
+    }
+
+    private void checkParse(Calendar cal, SimpleDateFormat sdf, DateParser fdf) throws ParseException {
+        String formattedDate= sdf.format(cal.getTime());                
+        Date expectedTime = sdf.parse(formattedDate);
+        Date actualTime = fdf.parse(formattedDate);
+        assertEquals(expectedTime, actualTime);
+    }
+    
+    @Test
+    public void testParseNumerics() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, 1, 10, 15, 33, 20);
+        cal.set(Calendar.MILLISECOND, 989);
+        
+        DateParser fdf = getInstance("yyyyMMddHHmmssSSS", NEW_YORK, Locale.US);
+        assertEquals(cal.getTime(), fdf.parse("20030210153320989"));
+    }
+    
+    @Test
+    public void testQuotes() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, 1, 10, 15, 33, 20);
+        cal.set(Calendar.MILLISECOND, 989);
+        
+        DateParser fdf = getInstance("''yyyyMMdd'A''B'HHmmssSSS''", NEW_YORK, Locale.US);
+        assertEquals(cal.getTime(), fdf.parse("'20030210A'B153320989'"));
+    }
+    
+    @Test
+    public void testDayOf() throws ParseException {
+        Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, 1, 10);
+        
+        DateParser fdf = getInstance("W w F D y", NEW_YORK, Locale.US);
+        assertEquals(cal.getTime(), fdf.parse("3 7 2 41 03"));
+    }
+    
+    /**
+     * Test case for {@link FastDateParser#getDateInstance(int, java.util.Locale)}.
+     * @throws ParseException 
+     */
+    @Test
+    public void testShortDateStyleWithLocales() throws ParseException {
+        DateParser fdf = getDateInstance(FastDateFormat.SHORT, Locale.US);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        
+        cal.set(2004, 1, 3);
+        assertEquals(cal.getTime(), fdf.parse("2/3/04"));
+
+        fdf = getDateInstance(FastDateFormat.SHORT, SWEDEN);
+        assertEquals(cal.getTime(), fdf.parse("2004-02-03"));
+    }
+
+    /**
+     * Tests that pre-1000AD years get padded with yyyy
+     * @throws ParseException 
+     */
+    @Test
+    public void testLowYearPadding() throws ParseException {
+        DateParser parser = getInstance(YMD_SLASH);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+
+        cal.set(1,0,1);
+        assertEquals(cal.getTime(), parser.parse("0001/01/01"));
+        cal.set(10,0,1);
+        assertEquals(cal.getTime(), parser.parse("0010/01/01"));
+        cal.set(100,0,1);
+        assertEquals(cal.getTime(), parser.parse("0100/01/01"));
+        cal.set(999,0,1);
+        assertEquals(cal.getTime(), parser.parse("0999/01/01"));
+    }
+    
+    /**
+     * @throws ParseException 
+     */
+    @Test
+    public void testMilleniumBug() throws ParseException {
+        DateParser parser = getInstance(DMY_DOT);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        
+        cal.set(1000,0,1);
+        assertEquals(cal.getTime(), parser.parse("01.01.1000"));
+    }
+
+    @Test
+    public void testLang303() throws ParseException {
+        DateParser parser = getInstance(YMD_SLASH);
+        Calendar cal = Calendar.getInstance();
+        cal.set(2004,11,31);
+
+        Date date = parser.parse("2004/11/31");
+
+        parser = (DateParser) SerializationUtils.deserialize( SerializationUtils.serialize( (Serializable)parser ) );
+        assertEquals(date, parser.parse("2004/11/31"));
+    }
+
+    @Test
+    public void testLang538() throws ParseException {
+        DateParser parser = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
+        
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-8"));
+        cal.clear();
+        cal.set(2009, 9, 16, 8, 42, 16);
+
+        assertEquals(cal.getTime(), parser.parse("2009-10-16T16:42:16.000Z"));
+    }
+    
+    @Test
+    public void testEquals() {
+        DateParser parser1= getInstance(YMD_SLASH);
+        DateParser parser2= getInstance(YMD_SLASH);
+
+        assertEquals(parser1, parser2);        
+        assertEquals(parser1.hashCode(), parser2.hashCode());
+        
+        assertFalse(parser1.equals(new Object()));
+    }
+
+    @Test
+    public void testToStringContainsName() {
+        DateParser parser= getInstance(YMD_SLASH);
+        assertTrue(parser.toString().startsWith("FastDate"));
+    }
+    
+    @Test
+    public void testPatternMatches() {
+        DateParser parser= getInstance(yMdHmsSZ);
+        assertEquals(yMdHmsSZ, parser.getPattern());
+    }
+    
+    @Test
+    public void testLocaleMatches() {
+        DateParser parser= getInstance(yMdHmsSZ, SWEDEN);
+        assertEquals(SWEDEN, parser.getLocale());
+    }
+    
+    @Test
+    public void testTimeZoneMatches() {
+        DateParser parser= getInstance(yMdHmsSZ, REYKJAVIK);
+        assertEquals(REYKJAVIK, parser.getTimeZone());
+    }
+}

Propchange: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java?rev=1236055&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java (added)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java Thu Jan 26 07:00:26 2012
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDatePrinter}.
+ *
+ * @since 3.0
+ */
+public class FastDatePrinterTest {
+    
+    private static final String YYYY_MM_DD = "yyyy/MM/dd";
+    private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+    private static final Locale SWEDEN = new Locale("sv", "SE");
+
+        DatePrinter getInstance(String format) {
+        return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DatePrinter getDateInstance(int dateStyle, Locale locale) {
+        return getInstance(FormatCache.getPatternForStyle(dateStyle, null, locale), TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DatePrinter getInstance(String format, Locale locale) {
+        return getInstance(format, TimeZone.getDefault(), locale);
+    }
+
+    private DatePrinter getInstance(String format, TimeZone timeZone) {
+        return getInstance(format, timeZone, Locale.getDefault());
+    }
+
+    /**
+     * Override this method in derived tests to change the construction of instances
+     * @param format
+     * @param timeZone
+     * @param locale
+     * @return
+     */
+    protected DatePrinter getInstance(String format, TimeZone timeZone, Locale locale) {
+        return new FastDatePrinter(format, timeZone, locale);
+    }
+
+    @Test
+    public void testFormat() {
+        Locale realDefaultLocale = Locale.getDefault();
+        TimeZone realDefaultZone = TimeZone.getDefault();
+        try {
+            Locale.setDefault(Locale.US);
+            TimeZone.setDefault(NEW_YORK);
+
+            GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
+            GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
+            Date date1 = cal1.getTime();
+            Date date2 = cal2.getTime();
+            long millis1 = date1.getTime();
+            long millis2 = date2.getTime();
+
+            DatePrinter fdf = getInstance("yyyy-MM-dd'T'HH:mm:ss");
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+            assertEquals(sdf.format(date1), fdf.format(date1));
+            assertEquals("2003-01-10T15:33:20", fdf.format(date1));
+            assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
+            assertEquals("2003-01-10T15:33:20", fdf.format(millis1));
+            assertEquals("2003-07-10T09:00:00", fdf.format(date2));
+            assertEquals("2003-07-10T09:00:00", fdf.format(cal2));
+            assertEquals("2003-07-10T09:00:00", fdf.format(millis2));
+
+            fdf = getInstance("Z");
+            assertEquals("-0500", fdf.format(date1));
+            assertEquals("-0500", fdf.format(cal1));
+            assertEquals("-0500", fdf.format(millis1));
+
+            assertEquals("-0400", fdf.format(date2));
+            assertEquals("-0400", fdf.format(cal2));
+            assertEquals("-0400", fdf.format(millis2));
+
+            fdf = getInstance("ZZ");
+            assertEquals("-05:00", fdf.format(date1));
+            assertEquals("-05:00", fdf.format(cal1));
+            assertEquals("-05:00", fdf.format(millis1));
+
+            assertEquals("-04:00", fdf.format(date2));
+            assertEquals("-04:00", fdf.format(cal2));
+            assertEquals("-04:00", fdf.format(millis2));
+
+            String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M" +
+                " dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z";
+            fdf = getInstance(pattern);
+            sdf = new SimpleDateFormat(pattern);
+            // SDF bug fix starting with Java 7
+            assertEquals(sdf.format(date1).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date1));
+            assertEquals(sdf.format(date2).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date2));
+        } finally {
+            Locale.setDefault(realDefaultLocale);
+            TimeZone.setDefault(realDefaultZone);
+        }
+    }
+
+    /**
+     * Test case for {@link FastDatePrinter#getDateInstance(int, java.util.Locale)}.
+     */
+    @Test
+    public void testShortDateStyleWithLocales() {
+        Locale usLocale = Locale.US;
+        Locale swedishLocale = new Locale("sv", "SE");
+        Calendar cal = Calendar.getInstance();
+        cal.set(2004, 1, 3);
+        DatePrinter fdf = getDateInstance(FastDateFormat.SHORT, usLocale);
+        assertEquals("2/3/04", fdf.format(cal));
+
+        fdf = getDateInstance(FastDateFormat.SHORT, swedishLocale);
+        assertEquals("2004-02-03", fdf.format(cal));
+
+    }
+
+    /**
+     * Tests that pre-1000AD years get padded with yyyy
+     */
+    @Test
+    public void testLowYearPadding() {
+        Calendar cal = Calendar.getInstance();
+        DatePrinter format = getInstance(YYYY_MM_DD);
+
+        cal.set(1,0,1);
+        assertEquals("0001/01/01", format.format(cal));
+        cal.set(10,0,1);
+        assertEquals("0010/01/01", format.format(cal));
+        cal.set(100,0,1);
+        assertEquals("0100/01/01", format.format(cal));
+        cal.set(999,0,1);
+        assertEquals("0999/01/01", format.format(cal));
+    }
+    /**
+     * Show Bug #39410 is solved
+     */
+    @Test
+    public void testMilleniumBug() {
+        Calendar cal = Calendar.getInstance();
+        DatePrinter format = getInstance("dd.MM.yyyy");
+
+        cal.set(1000,0,1);
+        assertEquals("01.01.1000", format.format(cal));
+    }
+
+    /**
+     * testLowYearPadding showed that the date was buggy
+     * This test confirms it, getting 366 back as a date
+     */
+    @Test
+    public void testSimpleDate() {
+        Calendar cal = Calendar.getInstance();
+        DatePrinter format = getInstance(YYYY_MM_DD);
+
+        cal.set(2004,11,31);
+        assertEquals("2004/12/31", format.format(cal));
+        cal.set(999,11,31);
+        assertEquals("0999/12/31", format.format(cal));
+        cal.set(1,2,2);
+        assertEquals("0001/03/02", format.format(cal));
+    }
+
+    @Test
+    public void testLang303() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(2004,11,31);
+
+        DatePrinter format = getInstance(YYYY_MM_DD);
+        String output = format.format(cal);
+
+        format = (DatePrinter) SerializationUtils.deserialize( SerializationUtils.serialize( (Serializable)format ) );
+        assertEquals(output, format.format(cal));
+    }
+
+    @Test
+    public void testLang538() {
+        // more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
+        // for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
+        GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
+        cal.clear();
+        cal.set(2009, 9, 16, 8, 42, 16);
+
+        DatePrinter format = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
+        assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
+        assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
+    }
+
+    @Test
+    public void testLang645() {
+        Locale locale = new Locale("sv", "SE");
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(2010, 0, 1, 12, 0, 0);
+        Date d = cal.getTime();
+
+        DatePrinter fdf = getInstance("EEEE', week 'ww", locale);
+
+        assertEquals("fredag, week 53", fdf.format(d));
+    }
+    
+    @Test
+    public void testEquals() {
+        DatePrinter printer1= getInstance(YYYY_MM_DD);
+        DatePrinter printer2= getInstance(YYYY_MM_DD);
+
+        assertEquals(printer1, printer2);
+        assertEquals(printer1.hashCode(), printer2.hashCode());        
+
+        assertFalse(printer1.equals(new Object()));
+    }
+    
+    @Test
+    public void testToStringContainsName() {
+        DatePrinter printer= getInstance(YYYY_MM_DD);
+        assertTrue(printer.toString().startsWith("FastDate"));
+    }
+    
+    @Test
+    public void testPatternMatches() {
+        DatePrinter printer= getInstance(YYYY_MM_DD);
+        assertEquals(YYYY_MM_DD, printer.getPattern());
+    }
+    
+    @Test
+    public void testLocaleMatches() {
+        DatePrinter printer= getInstance(YYYY_MM_DD, SWEDEN);
+        assertEquals(SWEDEN, printer.getLocale());
+    }
+    
+    @Test
+    public void testTimeZoneMatches() {
+        DatePrinter printer= getInstance(YYYY_MM_DD, NEW_YORK);
+        assertEquals(NEW_YORK, printer.getTimeZone());
+    }
+}

Propchange: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native