You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2015/07/09 06:57:36 UTC

[2/3] logging-log4j2 git commit: LOG4J2-812 - Improve performance of date formatting in multi-threaded systems

LOG4J2-812 - Improve performance of date formatting in multi-threaded systems


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/051f7019
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/051f7019
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/051f7019

Branch: refs/heads/master
Commit: 051f701907497633504a7079f0bf16189ab61c22
Parents: 483d2ca
Author: Ralph Goers <rg...@nextiva.com>
Authored: Wed Jul 8 21:51:00 2015 -0700
Committer: Ralph Goers <rg...@nextiva.com>
Committed: Wed Jul 8 21:51:00 2015 -0700

----------------------------------------------------------------------
 .../core/pattern/DatePatternConverter.java      |   61 +-
 .../log4j/core/util/datetime/DateParser.java    |  102 ++
 .../log4j/core/util/datetime/DatePrinter.java   |  127 ++
 .../core/util/datetime/FastDateFormat.java      |  578 ++++++++
 .../core/util/datetime/FastDateParser.java      |  926 ++++++++++++
 .../core/util/datetime/FastDatePrinter.java     | 1330 ++++++++++++++++++
 .../log4j/core/util/datetime/Format.java        |   28 +
 .../log4j/core/util/datetime/FormatCache.java   |  265 ++++
 .../perf/jmh/SimpleDateFormatBenchmark.java     |   88 +-
 9 files changed, 3482 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index d8a6467..03e954e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -19,9 +19,11 @@ package org.apache.logging.log4j.core.pattern;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
 
 /**
  * Converts and formats the event's date in a StringBuilder.
@@ -30,6 +32,18 @@ import org.apache.logging.log4j.core.config.plugins.Plugin;
 @ConverterKeys({ "d", "date" })
 public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
 
+    private class CurrentTime {
+        public long timestamp;
+        public String formatted;
+
+        public CurrentTime(long timestamp) {
+            this.timestamp = timestamp;
+            this.formatted = formatter.format(this.timestamp);
+        }
+    }
+
+    private AtomicReference<CurrentTime> currentTime;
+
     private abstract static class Formatter {
         abstract String format(long time);
 
@@ -39,20 +53,20 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
     }
 
     private static class PatternFormatter extends Formatter {
-        private final SimpleDateFormat simpleDateFormat;
+        private final FastDateFormat fastDateFormat;
 
-        PatternFormatter(final SimpleDateFormat simpleDateFormat) {
-            this.simpleDateFormat = simpleDateFormat;
+        PatternFormatter(final FastDateFormat fastDateFormat) {
+            this.fastDateFormat = fastDateFormat;
         }
 
         @Override
         String format(final long time) {
-            return simpleDateFormat.format(Long.valueOf(time));
+            return fastDateFormat.format(time);
         }
 
         @Override
         public String toPattern() {
-            return simpleDateFormat.toPattern();
+            return fastDateFormat.toPattern();
         }
     }
 
@@ -203,25 +217,29 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         }
 
         if (pattern != null) {
-            SimpleDateFormat tempFormat;
+            FastDateFormat tempFormat;
+
+            TimeZone tz = null;
+
+            // if the option list contains a TZ option, then set it.
+            if (options != null && options.length > 1) {
+                tz = TimeZone.getTimeZone(options[1]);
+            }
 
             try {
-                tempFormat = new SimpleDateFormat(pattern);
+                tempFormat = FastDateFormat.getInstance(pattern, tz);
             } catch (final IllegalArgumentException e) {
                 LOGGER.warn("Could not instantiate SimpleDateFormat with pattern " + patternOption, e);
 
                 // default to the DEFAULT format
-                tempFormat = new SimpleDateFormat(DEFAULT_PATTERN);
+                tempFormat = FastDateFormat.getInstance(DEFAULT_PATTERN);
             }
 
-            // if the option list contains a TZ option, then set it.
-            if (options != null && options.length > 1) {
-                final TimeZone tz = TimeZone.getTimeZone(options[1]);
-                tempFormat.setTimeZone(tz);
-            }
+
             tempFormatter = new PatternFormatter(tempFormat);
         }
         formatter = tempFormatter;
+        currentTime = new AtomicReference<>(new CurrentTime(System.currentTimeMillis()));
     }
 
     /**
@@ -233,9 +251,7 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      *            buffer to which formatted date is appended.
      */
     public void format(final Date date, final StringBuilder toAppendTo) {
-        synchronized (this) {
             toAppendTo.append(formatter.format(date.getTime()));
-        }
     }
 
     /**
@@ -244,14 +260,17 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
     @Override
     public void format(final LogEvent event, final StringBuilder output) {
         final long timestamp = event.getTimeMillis();
-
-        synchronized (this) {
-            if (timestamp != lastTimestamp) {
-                lastTimestamp = timestamp;
-                cachedDateString = formatter.format(timestamp);
+        CurrentTime current = currentTime.get();
+        if (timestamp != current.timestamp) {
+            final CurrentTime newTime = new CurrentTime(timestamp);
+            if (currentTime.compareAndSet(current, newTime)) {
+                current = newTime;
+            } else {
+                current = currentTime.get();
             }
         }
-        output.append(cachedDateString);
+
+        output.append(current.formatted);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
new file mode 100644
index 0000000..666ef81
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
@@ -0,0 +1,102 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public interface DateParser {
+
+    /**
+     * Equivalent to DateFormat.parse(String). 
+     * 
+     * See {@link java.text.DateFormat#parse(String)} for more information. 
+     * @param source A <code>String</code> whose beginning should be parsed. 
+     * @return A <code>Date</code> parsed from the string
+     * @throws ParseException if the beginning of the specified string cannot be parsed.
+     */
+    Date parse(String source) throws ParseException;
+
+    /**
+     * Equivalent to DateFormat.parse(String, ParsePosition). 
+     * 
+     * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information. 
+     * 
+     * @param source A <code>String</code>, part of which should be parsed.
+     * @param pos A <code>ParsePosition</code> object with index and error index information 
+     * as described above. 
+     * @return A <code>Date</code> parsed from the string. In case of error, returns null. 
+     * @throws NullPointerException if text or pos is null.
+     */
+    Date parse(String source, ParsePosition pos);
+
+    // Accessors
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Get the pattern used by this parser.</p>
+     * 
+     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+     */
+    String getPattern();
+
+    /**
+     * <p>
+     * Get the time zone used by this parser.
+     * </p>
+     * 
+     * <p>
+     * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by
+     * the format pattern.
+     * </p>
+     * 
+     * @return the time zone
+     */
+    TimeZone getTimeZone();
+
+    /**
+     * <p>Get the locale used by this parser.</p>
+     * 
+     * @return the locale
+     */
+    Locale getLocale();
+
+    /**
+     * Parses text from a string to produce a Date.
+     * 
+     * @param source A <code>String</code> whose beginning should be parsed.
+     * @return a <code>java.util.Date</code> object
+     * @throws ParseException if the beginning of the specified string cannot be parsed.
+     * @see java.text.DateFormat#parseObject(String) 
+     */
+    Object parseObject(String source) throws ParseException;
+
+    /**
+     * Parse a date/time string according to the given parse position.
+     * 
+     * @param source A <code>String</code> whose beginning should be parsed.
+     * @param pos the parse position
+     * @return a <code>java.util.Date</code> object
+     * @see java.text.DateFormat#parseObject(String, ParsePosition) 
+     */
+    Object parseObject(String source, ParsePosition pos);
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
new file mode 100644
index 0000000..e82d9a6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.FieldPosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public interface DatePrinter {
+
+    /**
+     * <p>Formats a millisecond {@code long} value.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @return the formatted string
+     * @since 2.1
+     */
+    String format(long millis);
+
+    /**
+     * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @return the formatted string
+     */
+    String format(Date date);
+
+    /**
+     * <p>Formats a {@code Calendar} object.</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.
+     * @return the formatted string
+     */
+    String format(Calendar calendar);
+
+    /**
+     * <p>Formats a milliseond {@code long} value into the
+     * supplied {@code StringBuilder}.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     */
+    StringBuilder format(long millis, StringBuilder buf);
+
+    /**
+     * <p>Formats a {@code Date} object into the
+     * supplied {@code StringBuilder} using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     */
+    StringBuilder format(Date date, StringBuilder buf);
+
+    /**
+     * <p>Formats a {@code Calendar} object into the supplied {@code StringBuilder}.</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
+     */
+    StringBuilder format(Calendar calendar, StringBuilder buf);
+
+    // Accessors
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets the pattern used by this printer.</p>
+     *
+     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+     */
+    String getPattern();
+
+    /**
+     * <p>Gets the time zone used by this printer.</p>
+     *
+     * <p>This zone is always used for {@code Date} printing. </p>
+     *
+     * @return the time zone
+     */
+    TimeZone getTimeZone();
+
+    /**
+     * <p>Gets the locale used by this printer.</p>
+     *
+     * @return the locale
+     */
+    Locale getLocale();
+
+    /**
+     * <p>Formats a {@code Date}, {@code Calendar} or
+     * {@code Long} (milliseconds) object.</p>
+     * 
+     * See {@link java.text.DateFormat#format(Object, StringBuilder, FieldPosition)}
+     * 
+     * @param obj  the object to format
+     * @param toAppendTo  the buffer to append to
+     * @param pos  the position - ignored
+     * @return the buffer passed in
+     */
+    StringBuilder format(Object obj, StringBuilder toAppendTo, FieldPosition pos);
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
new file mode 100644
index 0000000..888014f
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
@@ -0,0 +1,578 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * This is a copy of Commons Lang's Fast Date Formatter.
+ */
+public class FastDateFormat extends Format implements DatePrinter, DateParser {
+    /**
+     * Required for serialization support.
+     *
+     * @see java.io.Serializable
+     */
+    private static final long serialVersionUID = 2L;
+
+    /**
+     * 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;
+
+    private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
+        @Override
+        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+            return new FastDateFormat(pattern, timeZone, locale);
+        }
+    };
+
+    private final FastDatePrinter printer;
+    private final FastDateParser parser;
+
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets a formatter instance using the default pattern in the
+     * default locale.</p>
+     *
+     * @return a date/time formatter
+     */
+    public static FastDateFormat getInstance() {
+        return cache.getInstance();
+    }
+
+    /**
+     * <p>Gets a formatter instance using the specified pattern in the
+     * default locale.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     */
+    public static FastDateFormat getInstance(final String pattern) {
+        return cache.getInstance(pattern, null, null);
+    }
+
+    /**
+     * <p>Gets a formatter instance using the specified pattern and
+     * time zone.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     */
+    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
+        return cache.getInstance(pattern, timeZone, null);
+    }
+
+    /**
+     * <p>Gets a formatter instance using the specified pattern and
+     * locale.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param locale  optional locale, overrides system locale
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     */
+    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
+        return cache.getInstance(pattern, null, locale);
+    }
+
+    /**
+     * <p>Gets a formatter instance using the specified pattern, time zone
+     * and locale.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @param locale  optional locale, overrides system locale
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     *  or {@code null}
+     */
+    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+        return cache.getInstance(pattern, timeZone, locale);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets a date formatter instance using the specified style in the
+     * default time zone and locale.</p>
+     *
+     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
+     * @return a localized standard date formatter
+     * @throws IllegalArgumentException if the Locale has no date
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateInstance(final int style) {
+        return cache.getDateInstance(style, null, null);
+    }
+
+    /**
+     * <p>Gets a date formatter instance using the specified style and
+     * locale in the default time zone.</p>
+     *
+     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard date formatter
+     * @throws IllegalArgumentException if the Locale has no date
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
+        return cache.getDateInstance(style, null, locale);
+    }
+
+    /**
+     * <p>Gets a date formatter instance using the specified style and
+     * time zone in the default locale.</p>
+     *
+     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @return a localized standard date formatter
+     * @throws IllegalArgumentException if the Locale has no date
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
+        return cache.getDateInstance(style, timeZone, null);
+    }
+
+    /**
+     * <p>Gets a date formatter instance using the specified style, time
+     * zone and locale.</p>
+     *
+     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard date formatter
+     * @throws IllegalArgumentException if the Locale has no date
+     *  pattern defined
+     */
+    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
+        return cache.getDateInstance(style, timeZone, locale);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets a time formatter instance using the specified style in the
+     * default time zone and locale.</p>
+     *
+     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
+     * @return a localized standard time formatter
+     * @throws IllegalArgumentException if the Locale has no time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getTimeInstance(final int style) {
+        return cache.getTimeInstance(style, null, null);
+    }
+
+    /**
+     * <p>Gets a time formatter instance using the specified style and
+     * locale in the default time zone.</p>
+     *
+     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard time formatter
+     * @throws IllegalArgumentException if the Locale has no time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
+        return cache.getTimeInstance(style, null, locale);
+    }
+
+    /**
+     * <p>Gets a time formatter instance using the specified style and
+     * time zone in the default locale.</p>
+     *
+     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted time
+     * @return a localized standard time formatter
+     * @throws IllegalArgumentException if the Locale has no time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
+        return cache.getTimeInstance(style, timeZone, null);
+    }
+
+    /**
+     * <p>Gets a time formatter instance using the specified style, time
+     * zone and locale.</p>
+     *
+     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted time
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard time formatter
+     * @throws IllegalArgumentException if the Locale has no time
+     *  pattern defined
+     */
+    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
+        return cache.getTimeInstance(style, timeZone, locale);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets a date/time formatter instance using the specified style
+     * in the default time zone and locale.</p>
+     *
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
+     * @return a localized standard date/time formatter
+     * @throws IllegalArgumentException if the Locale has no date/time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
+    }
+
+    /**
+     * <p>Gets a date/time formatter instance using the specified style and
+     * locale in the default time zone.</p>
+     *
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard date/time formatter
+     * @throws IllegalArgumentException if the Locale has no date/time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
+    }
+
+    /**
+     * <p>Gets a date/time formatter instance using the specified style and
+     * time zone in the default locale.</p>
+     *
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @return a localized standard date/time formatter
+     * @throws IllegalArgumentException if the Locale has no date/time
+     *  pattern defined
+     * @since 2.1
+     */
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
+        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
+    }
+    /**
+     * <p>Gets a date/time formatter instance using the specified style,
+     * time zone and locale.</p>
+     *
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard date/time formatter
+     * @throws IllegalArgumentException if the Locale has no date/time
+     *  pattern defined
+     */
+    public static FastDateFormat getDateTimeInstance(
+            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
+    }
+
+    // Constructor
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Constructs a new FastDateFormat.</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 FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
+        this(pattern, timeZone, locale, null);
+    }
+
+    // Constructor
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Constructs a new FastDateFormat.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
+     * @param timeZone  non-null time zone to use
+     * @param locale  non-null locale to use
+     * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing.  If centuryStart is null, defaults to now - 80 years
+     * @throws NullPointerException if pattern, timeZone, or locale is null.
+     */
+    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+        printer= new FastDatePrinter(pattern, timeZone, locale);
+        parser= new FastDateParser(pattern, timeZone, locale, centuryStart);
+    }
+
+    // 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 StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) {
+        return printer.format(obj, toAppendTo, pos);
+    }
+
+    /**
+     * <p>Formats a millisecond {@code long} value.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @return the formatted string
+     * @since 2.1
+     */
+    @Override
+    public String format(final long millis) {
+        return printer.format(millis);
+    }
+
+    /**
+     * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @return the formatted string
+     */
+    @Override
+    public String format(final Date date) {
+        return printer.format(date);
+    }
+
+    /**
+     * <p>Formats a {@code Calendar} object.</p>
+     *
+     * @param calendar  the calendar to format
+     * @return the formatted string
+     */
+    @Override
+    public String format(final Calendar calendar) {
+        return printer.format(calendar);
+    }
+
+    /**
+     * <p>Formats a millisecond {@code long} value into the
+     * supplied {@code StringBuilder}.</p>
+     *
+     * @param millis  the millisecond value to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     * @since 2.1
+     */
+    @Override
+    public StringBuilder format(final long millis, final StringBuilder buf) {
+        return printer.format(millis, buf);
+    }
+
+    /**
+     * <p>Formats a {@code Date} object into the
+     * supplied {@code StringBuilder} using a {@code GregorianCalendar}.</p>
+     *
+     * @param date  the date to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     */
+    @Override
+    public StringBuilder format(final Date date, final StringBuilder buf) {
+        return printer.format(date, buf);
+    }
+
+    /**
+     * <p>Formats a {@code Calendar} object into the
+     * supplied {@code StringBuilder}.</p>
+     *
+     * @param calendar  the calendar to format
+     * @param buf  the buffer to format into
+     * @return the specified string buffer
+     */
+    @Override
+    public StringBuilder format(final Calendar calendar, final StringBuilder buf) {
+        return printer.format(calendar, buf);
+    }
+
+    // Parsing
+    //-----------------------------------------------------------------------
+
+
+    /* (non-Javadoc)
+     * @see DateParser#parse(java.lang.String)
+     */
+    @Override
+    public Date parse(final String source) throws ParseException {
+        return parser.parse(source);
+    }
+
+    /* (non-Javadoc)
+     * @see DateParser#parse(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Date parse(final String source, final ParsePosition pos) {
+        return parser.parse(source, pos);
+    }
+
+    /* (non-Javadoc)
+     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Object parseObject(final String source, final ParsePosition pos) {
+        return parser.parseObject(source, pos);
+    }
+
+    // Accessors
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Gets the pattern used by this formatter.</p>
+     *
+     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+     */
+    @Override
+    public String getPattern() {
+        return printer.getPattern();
+    }
+
+    /**
+     * <p>Gets the time zone used by this formatter.</p>
+     *
+     * <p>This zone is always used for {@code Date} formatting. </p>
+     *
+     * @return the time zone
+     */
+    @Override
+    public TimeZone getTimeZone() {
+        return printer.getTimeZone();
+    }
+
+    /**
+     * <p>Gets the locale used by this formatter.</p>
+     *
+     * @return the locale
+     */
+    @Override
+    public Locale getLocale() {
+        return printer.getLocale();
+    }
+
+    /**
+     * <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 printer.getMaxLengthEstimate();
+    }
+
+    public String toPattern() {
+        return printer.getPattern();
+    }
+
+    // Basics
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Compares two objects for equality.</p>
+     *
+     * @param obj  the object to compare to
+     * @return {@code true} if equal
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof FastDateFormat == false) {
+            return false;
+        }
+        final FastDateFormat other = (FastDateFormat) obj;
+        // no need to check parser, as it has same invariants as printer
+        return printer.equals(other.printer);
+    }
+
+    /**
+     * <p>Returns a hashcode compatible with equals.</p>
+     *
+     * @return a hashcode compatible with equals
+     */
+    @Override
+    public int hashCode() {
+        return printer.hashCode();
+    }
+
+    /**
+     * <p>Gets a debugging string version of this formatter.</p>
+     *
+     * @return a debugging string
+     */
+    @Override
+    public String toString() {
+        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
+    }
+
+
+    /**
+     * <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 StringBuilder applyRules(final Calendar calendar, final StringBuilder buf) {
+        return printer.applyRules(calendar, buf);
+    }
+
+
+}
+

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
new file mode 100644
index 0000000..a30e6ae
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
@@ -0,0 +1,926 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public class FastDateParser implements DateParser, Serializable {
+    /**
+     * Required for serialization support.
+     *
+     * @see java.io.Serializable
+     */
+    private static final long serialVersionUID = 3L;
+
+    static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
+
+    // defining fields
+    private final String pattern;
+    private final TimeZone timeZone;
+    private final Locale locale;
+    private final int century;
+    private final int startYear;
+    private final boolean lenient;
+
+    // derived fields
+    private transient Pattern parsePattern;
+    private transient Strategy[] strategies;
+
+    // dynamic fields to communicate with Strategy
+    private transient String currentFormatField;
+    private transient Strategy nextStrategy;
+
+    /**
+     * <p>Constructs a new FastDateParser.</p>
+     * 
+     * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the 
+     * factory methods of {@link FastDateFormat} to get a cached FastDateParser instance.
+     *
+     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone non-null time zone to use
+     * @param locale non-null locale
+     */
+    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
+        this(pattern, timeZone, locale, null, true);
+    }
+
+    /**
+     * <p>Constructs a new FastDateParser.</p>
+     *
+     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone non-null time zone to use
+     * @param locale non-null locale
+     * @param centuryStart The start of the century for 2 digit year parsing
+     *
+     * @since 3.3
+     */
+    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+        this(pattern, timeZone, locale, centuryStart, true);
+    }
+
+    /**
+     * <p>Constructs a new FastDateParser.</p>
+     *
+     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone non-null time zone to use
+     * @param locale non-null locale
+     * @param centuryStart The start of the century for 2 digit year parsing
+     * @param lenient if true, non-standard values for Calendar fields should be accepted;
+     * if false, non-standard values will cause a ParseException to be thrown {@link CalendaretLenient(boolean)}
+     *
+     * @since 3.5
+     */
+    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale,
+            final Date centuryStart, final boolean lenient) {
+        this.pattern = pattern;
+        this.timeZone = timeZone;
+        this.locale = locale;
+        this.lenient = lenient;
+
+        final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
+
+        int centuryStartYear;
+        if(centuryStart!=null) {
+            definingCalendar.setTime(centuryStart);
+            centuryStartYear= definingCalendar.get(Calendar.YEAR);
+        }
+        else if(locale.equals(JAPANESE_IMPERIAL)) {
+            centuryStartYear= 0;
+        }
+        else {
+            // from 80 years ago to 20 years from now
+            definingCalendar.setTime(new Date());
+            centuryStartYear= definingCalendar.get(Calendar.YEAR)-80;
+        }
+        century= centuryStartYear / 100 * 100;
+        startYear= centuryStartYear - century;
+
+        init(definingCalendar);
+    }
+
+    /**
+     * Initialize derived fields from defining fields.
+     * This is called from constructor and from readObject (de-serialization)
+     *
+     * @param definingCalendar the {@link java.util.Calendar} instance used to initialize this FastDateParser
+     */
+    private void init(final Calendar definingCalendar) {
+
+        final StringBuilder regex= new StringBuilder();
+        final List<Strategy> collector = new ArrayList<Strategy>();
+
+        final Matcher patternMatcher= formatPattern.matcher(pattern);
+        if(!patternMatcher.lookingAt()) {
+            throw new IllegalArgumentException(
+                    "Illegal pattern character '" + pattern.charAt(patternMatcher.regionStart()) + "'");
+        }
+
+        currentFormatField= patternMatcher.group();
+        Strategy currentStrategy= getStrategy(currentFormatField, definingCalendar);
+        for(;;) {
+            patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
+            if(!patternMatcher.lookingAt()) {
+                nextStrategy = null;
+                break;
+            }
+            final String nextFormatField= patternMatcher.group();
+            nextStrategy = getStrategy(nextFormatField, definingCalendar);
+            if(currentStrategy.addRegex(this, regex)) {
+                collector.add(currentStrategy);
+            }
+            currentFormatField= nextFormatField;
+            currentStrategy= nextStrategy;
+        }
+        if (patternMatcher.regionStart() != patternMatcher.regionEnd()) {
+            throw new IllegalArgumentException("Failed to parse \""+pattern+"\" ; gave up at index "+patternMatcher.regionStart());
+        }
+        if(currentStrategy.addRegex(this, regex)) {
+            collector.add(currentStrategy);
+        }
+        currentFormatField= null;
+        strategies= collector.toArray(new Strategy[collector.size()]);
+        parsePattern= Pattern.compile(regex.toString());
+    }
+
+    // Accessors
+    //-----------------------------------------------------------------------
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#getPattern()
+     */
+    @Override
+    public String getPattern() {
+        return pattern;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#getTimeZone()
+     */
+    @Override
+    public TimeZone getTimeZone() {
+        return timeZone;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#getLocale()
+     */
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
+     * Returns the generated pattern (for testing purposes).
+     *
+     * @return the generated pattern
+     */
+    Pattern getParsePattern() {
+        return parsePattern;
+    }
+
+    // Basics
+    //-----------------------------------------------------------------------
+    /**
+     * <p>Compare another object for equality with this object.</p>
+     *
+     * @param obj  the object to compare to
+     * @return <code>true</code>if equal to this instance
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (! (obj instanceof FastDateParser) ) {
+            return false;
+        }
+        final FastDateParser other = (FastDateParser) obj;
+        return pattern.equals(other.pattern)
+            && timeZone.equals(other.timeZone)
+            && locale.equals(other.locale);
+    }
+
+    /**
+     * <p>Return a hashcode compatible with equals.</p>
+     *
+     * @return a hashcode compatible with equals
+     */
+    @Override
+    public int hashCode() {
+        return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
+    }
+
+    /**
+     * <p>Get a string version of this formatter.</p>
+     *
+     * @return a debugging string
+     */
+    @Override
+    public String toString() {
+        return "FastDateParser[" + pattern + "," + locale + "," + timeZone.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(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+
+        final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
+        init(definingCalendar);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
+     */
+    @Override
+    public Object parseObject(final String source) throws ParseException {
+        return parse(source);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
+     */
+    @Override
+    public Date parse(final String source) throws ParseException {
+        final Date date= parse(source, new ParsePosition(0));
+        if(date==null) {
+            // Add a note re supported date range
+            if (locale.equals(JAPANESE_IMPERIAL)) {
+                throw new ParseException(
+                        "(The " +locale + " locale does not support dates before 1868 AD)\n" +
+                                "Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+            }
+            throw new ParseException("Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+        }
+        return date;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Object parseObject(final String source, final ParsePosition pos) {
+        return parse(source, pos);
+    }
+
+    /**
+     * This implementation updates the ParsePosition if the parse succeeeds.
+     * However, unlike the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)}
+     * it is not able to set the error Index - i.e. {@link ParsePosition#getErrorIndex()} -  if the parse fails.
+     * <p>
+     * To determine if the parse has succeeded, the caller must check if the current parse position
+     * given by {@link ParsePosition#getIndex()} has been updated. If the input buffer has been fully
+     * parsed, then the index will point to just after the end of the input buffer.
+     *
+     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
+     * {@inheritDoc}
+     */
+    @Override
+    public Date parse(final String source, final ParsePosition pos) {
+        final int offset= pos.getIndex();
+        final Matcher matcher= parsePattern.matcher(source.substring(offset));
+        if(!matcher.lookingAt()) {
+            return null;
+        }
+        // timing tests indicate getting new instance is 19% faster than cloning
+        final Calendar cal= Calendar.getInstance(timeZone, locale);
+        cal.clear();
+        cal.setLenient(lenient);
+
+        for(int i=0; i<strategies.length;) {
+            final Strategy strategy= strategies[i++];
+            strategy.setCalendar(this, cal, matcher.group(i));
+        }
+        pos.setIndex(offset+matcher.end());
+        return cal.getTime();
+    }
+
+    // Support for strategies
+    //-----------------------------------------------------------------------
+
+    private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
+        for(int i= 0; i<value.length(); ++i) {
+            char c= value.charAt(i);
+            switch(c) {
+            case '\\':
+            case '^':
+            case '$':
+            case '.':
+            case '|':
+            case '?':
+            case '*':
+            case '+':
+            case '(':
+            case ')':
+            case '[':
+            case '{':
+                sb.append('\\');
+            default:
+                sb.append(c);
+            }
+        }
+        return sb;
+    }
+
+    /**
+     * Escape constant fields into regular expression
+     * @param regex The destination regex
+     * @param value The source field
+     * @param unquote If true, replace two success quotes ('') with single quote (')
+     * @return The <code>StringBuilder</code>
+     */
+    private static StringBuilder escapeRegex(final StringBuilder regex, final String value, final boolean unquote) {
+        regex.append("\\Q");
+        for(int i= 0; i<value.length(); ++i) {
+            char c= value.charAt(i);
+            switch(c) {
+            case '\'':
+                if(unquote) {
+                    if(++i==value.length()) {
+                        return regex;
+                    }
+                    c= value.charAt(i);
+                }
+                break;
+            case '\\':
+                if(++i==value.length()) {
+                    break;
+                }
+                /*
+                 * If we have found \E, we replace it with \E\\E\Q, i.e. we stop the quoting,
+                 * quote the \ in \E, then restart the quoting.
+                 *
+                 * Otherwise we just output the two characters.
+                 * In each case the initial \ needs to be output and the final char is done at the end
+                 */
+                regex.append(c); // we always want the original \
+                c = value.charAt(i); // Is it followed by E ?
+                if (c == 'E') { // \E detected
+                  regex.append("E\\\\E\\"); // see comment above
+                  c = 'Q'; // appended below
+                }
+                break;
+            default:
+                break;
+            }
+            regex.append(c);
+        }
+        regex.append("\\E");
+        return regex;
+    }
+
+
+    /**
+     * Get the short and long values displayed for a field
+     * @param field The field of interest
+     * @param definingCalendar The calendar to obtain the short and long values
+     * @param locale The locale of display names
+     * @return A Map of the field key / value pairs
+     */
+    private static Map<String, Integer> getDisplayNames(final int field, final Calendar definingCalendar, final Locale locale) {
+        return definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
+    }
+
+    /**
+     * Adjust dates to be within appropriate century
+     * @param twoDigitYear The year to adjust
+     * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive)
+     */
+    private int adjustYear(final int twoDigitYear) {
+        final int trial= century + twoDigitYear;
+        return twoDigitYear>=startYear ?trial :trial+100;
+    }
+
+    /**
+     * Is the next field a number?
+     * @return true, if next field will be a number
+     */
+    boolean isNextNumber() {
+        return nextStrategy!=null && nextStrategy.isNumber();
+    }
+
+    /**
+     * What is the width of the current field?
+     * @return The number of characters in the current format field
+     */
+    int getFieldWidth() {
+        return currentFormatField.length();
+    }
+
+    /**
+     * A strategy to parse a single field from the parsing pattern
+     */
+    private static abstract class Strategy {
+        
+        /**
+         * Is this field a number?
+         * The default implementation returns false.
+         *
+         * @return true, if field is a number
+         */
+        boolean isNumber() {
+            return false;
+        }
+        
+        /**
+         * Set the Calendar with the parsed field.
+         *
+         * The default implementation does nothing.
+         *
+         * @param parser The parser calling this strategy
+         * @param cal The <code>Calendar</code> to set
+         * @param value The parsed field to translate and set in cal
+         */
+        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+
+        }
+        
+        /**
+         * Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code>
+         * which will accept this field
+         * @param parser The parser calling this strategy
+         * @param regex The <code>StringBuilder</code> to append to
+         * @return true, if this field will set the calendar;
+         * false, if this field is a constant value
+         */
+        abstract boolean addRegex(FastDateParser parser, StringBuilder regex);
+
+    }
+
+    /**
+     * A <code>Pattern</code> to parse the user supplied SimpleDateFormat pattern
+     */
+    private static final Pattern formatPattern= Pattern.compile(
+            "D+|E+|F+|G+|H+|K+|M+|S+|W+|X+|Z+|a+|d+|h+|k+|m+|s+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
+
+    /**
+     * Obtain a Strategy given a field from a SimpleDateFormat pattern
+     * @param formatField A sub-sequence of the SimpleDateFormat pattern
+     * @param definingCalendar The calendar to obtain the short and long values
+     * @return The Strategy that will handle parsing for the field
+     */
+    private Strategy getStrategy(final String formatField, final Calendar definingCalendar) {
+        switch(formatField.charAt(0)) {
+        case '\'':
+            if(formatField.length()>2) {
+                return new CopyQuotedStrategy(formatField.substring(1, formatField.length()-1));
+            }
+            //$FALL-THROUGH$
+        default:
+            return new CopyQuotedStrategy(formatField);
+        case 'D':
+            return DAY_OF_YEAR_STRATEGY;
+        case 'E':
+            return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
+        case 'F':
+            return DAY_OF_WEEK_IN_MONTH_STRATEGY;
+        case 'G':
+            return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
+        case 'H':  // Hour in day (0-23)
+            return HOUR_OF_DAY_STRATEGY;
+        case 'K':  // Hour in am/pm (0-11) 
+            return HOUR_STRATEGY;
+        case 'M':
+            return formatField.length()>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
+        case 'S':
+            return MILLISECOND_STRATEGY;
+        case 'W':
+            return WEEK_OF_MONTH_STRATEGY;
+        case 'a':
+            return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
+        case 'd':
+            return DAY_OF_MONTH_STRATEGY;
+        case 'h':  // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+            return HOUR12_STRATEGY;
+        case 'k':  // Hour in day (1-24), i.e. midnight is 24, not 0
+            return HOUR24_OF_DAY_STRATEGY;
+        case 'm':
+            return MINUTE_STRATEGY;
+        case 's':
+            return SECOND_STRATEGY;
+        case 'w':
+            return WEEK_OF_YEAR_STRATEGY;
+        case 'y':
+            return formatField.length()>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
+        case 'X':
+            return ISO8601TimeZoneStrategy.getStrategy(formatField.length());
+        case 'Z':
+            if (formatField.equals("ZZ")) {
+                return ISO_8601_STRATEGY;
+            }
+            //$FALL-THROUGH$
+        case 'z':
+            return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
+        }
+    }
+
+    @SuppressWarnings("unchecked") // OK because we are creating an array with no entries
+    private static final ConcurrentMap<Locale, Strategy>[] caches = new ConcurrentMap[Calendar.FIELD_COUNT];
+
+    /**
+     * Get a cache of Strategies for a particular field
+     * @param field The Calendar field
+     * @return a cache of Locale to Strategy
+     */
+    private static ConcurrentMap<Locale, Strategy> getCache(final int field) {
+        synchronized(caches) {
+            if(caches[field]==null) {
+                caches[field]= new ConcurrentHashMap<Locale,Strategy>(3);
+            }
+            return caches[field];
+        }
+    }
+
+    /**
+     * Construct a Strategy that parses a Text field
+     * @param field The Calendar field
+     * @param definingCalendar The calendar to obtain the short and long values
+     * @return a TextStrategy for the field and Locale
+     */
+    private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
+        final ConcurrentMap<Locale,Strategy> cache = getCache(field);
+        Strategy strategy= cache.get(locale);
+        if(strategy==null) {
+            strategy= field==Calendar.ZONE_OFFSET
+                    ? new TimeZoneStrategy(locale)
+                    : new CaseInsensitiveTextStrategy(field, definingCalendar, locale);
+            final Strategy inCache= cache.putIfAbsent(locale, strategy);
+            if(inCache!=null) {
+                return inCache;
+            }
+        }
+        return strategy;
+    }
+
+    /**
+     * A strategy that copies the static or quoted field in the parsing pattern
+     */
+    private static class CopyQuotedStrategy extends Strategy {
+        private final String formatField;
+
+        /**
+         * Construct a Strategy that ensures the formatField has literal text
+         * @param formatField The literal text to match
+         */
+        CopyQuotedStrategy(final String formatField) {
+            this.formatField= formatField;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean isNumber() {
+            char c= formatField.charAt(0);
+            if(c=='\'') {
+                c= formatField.charAt(1);
+            }
+            return Character.isDigit(c);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+            escapeRegex(regex, formatField, true);
+            return false;
+        }
+    }
+
+    /**
+     * A strategy that handles a text field in the parsing pattern
+     */
+     private static class CaseInsensitiveTextStrategy extends Strategy {
+        private final int field;
+        private final Locale locale;
+        private final Map<String, Integer> lKeyValues;
+
+        /**
+         * Construct a Strategy that parses a Text field
+         * @param field  The Calendar field
+         * @param definingCalendar  The Calendar to use
+         * @param locale  The Locale to use
+         */
+        CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
+            this.field= field;
+            this.locale= locale;
+            final Map<String, Integer> keyValues = getDisplayNames(field, definingCalendar, locale);
+            this.lKeyValues= new HashMap<String,Integer>();
+
+            for(final Map.Entry<String, Integer> entry : keyValues.entrySet()) {
+                lKeyValues.put(entry.getKey().toLowerCase(locale), entry.getValue());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+            regex.append("((?iu)");
+            for(final String textKeyValue : lKeyValues.keySet()) {
+                simpleQuote(regex, textKeyValue).append('|');
+            }
+            regex.setCharAt(regex.length()-1, ')');
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+            final Integer iVal = lKeyValues.get(value.toLowerCase(locale));
+            if(iVal == null) {
+                final StringBuilder sb= new StringBuilder(value);
+                sb.append(" not in (");
+                for(final String textKeyValue : lKeyValues.keySet()) {
+                    sb.append(textKeyValue).append(' ');
+                }
+                sb.setCharAt(sb.length()-1, ')');
+                throw new IllegalArgumentException(sb.toString());
+            }
+            cal.set(field, iVal.intValue());
+        }
+    }
+
+
+    /**
+     * A strategy that handles a number field in the parsing pattern
+     */
+    private static class NumberStrategy extends Strategy {
+        private final int field;
+
+        /**
+         * Construct a Strategy that parses a Number field
+         * @param field The Calendar field
+         */
+        NumberStrategy(final int field) {
+             this.field= field;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean isNumber() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+            // See LANG-954: We use {Nd} rather than {IsNd} because Android does not support the Is prefix
+            if(parser.isNextNumber()) {
+                regex.append("(\\p{Nd}{").append(parser.getFieldWidth()).append("}+)");
+            }
+            else {
+                regex.append("(\\p{Nd}++)");
+            }
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+            cal.set(field, modify(Integer.parseInt(value)));
+        }
+
+        /**
+         * Make any modifications to parsed integer
+         * @param iValue The parsed integer
+         * @return The modified value
+         */
+        int modify(final int iValue) {
+            return iValue;
+        }
+    }
+
+    private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+            int iValue= Integer.parseInt(value);
+            if(iValue<100) {
+                iValue= parser.adjustYear(iValue);
+            }
+            cal.set(Calendar.YEAR, iValue);
+        }
+    };
+
+    /**
+     * A strategy that handles a timezone field in the parsing pattern
+     */
+    static class TimeZoneStrategy extends Strategy {
+        private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
+        private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}";
+        
+        private final Locale locale;
+        private final Map<String, TimeZone> tzNames= new HashMap<String, TimeZone>();
+        private final String validTimeZoneChars;
+
+        /**
+         * Index of zone id
+         */
+        private static final int ID = 0;
+
+        /**
+         * Construct a Strategy that parses a TimeZone
+         * @param locale The Locale
+         */
+        TimeZoneStrategy(final Locale locale) {
+            this.locale = locale;
+
+            final StringBuilder sb = new StringBuilder();
+            sb.append('(' + RFC_822_TIME_ZONE + "|(?iu)" + GMT_OPTION );
+
+            final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
+            for (final String[] zoneNames : zones) {
+                final String tzId = zoneNames[ID];
+                if (tzId.equalsIgnoreCase("GMT")) {
+                    continue;
+                }
+                final TimeZone tz = TimeZone.getTimeZone(tzId);
+                for(int i= 1; i<zoneNames.length; ++i) {
+                    String zoneName = zoneNames[i].toLowerCase(locale);
+                    if (!tzNames.containsKey(zoneName)){
+                        tzNames.put(zoneName, tz);
+                        simpleQuote(sb.append('|'), zoneName);
+                    }
+                }
+            }
+
+            sb.append(')');
+            validTimeZoneChars = sb.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+            regex.append(validTimeZoneChars);
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+            TimeZone tz;
+            if(value.charAt(0)=='+' || value.charAt(0)=='-') {
+                tz= TimeZone.getTimeZone("GMT"+value);
+            }
+            else if(value.regionMatches(true, 0, "GMT", 0, 3)) {
+                tz= TimeZone.getTimeZone(value.toUpperCase());
+            }
+            else {
+                tz= tzNames.get(value.toLowerCase(locale));
+                if(tz==null) {
+                    throw new IllegalArgumentException(value + " is not a supported timezone name");
+                }
+            }
+            cal.setTimeZone(tz);
+        }
+    }
+    
+    private static class ISO8601TimeZoneStrategy extends Strategy {
+        // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm 
+        private final String pattern;
+
+        /**
+         * Construct a Strategy that parses a TimeZone
+         * @param pattern The Pattern
+         */
+        ISO8601TimeZoneStrategy(String pattern) {
+            this.pattern = pattern;
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        boolean addRegex(FastDateParser parser, StringBuilder regex) {
+            regex.append(pattern);
+            return true;
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        void setCalendar(FastDateParser parser, Calendar cal, String value) {
+            if (value.equals("Z")) {
+                cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+            } else {
+                cal.setTimeZone(TimeZone.getTimeZone("GMT" + value));
+            }
+        }
+        
+        private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");
+        private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))");
+        private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))");
+
+        /**
+         * Factory method for ISO8601TimeZoneStrategies.
+         * 
+         * @param tokenLen a token indicating the length of the TimeZone String to be formatted.
+         * @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such
+         *          strategy exists, an IllegalArgumentException will be thrown.
+         */
+        static Strategy getStrategy(int tokenLen) {
+            switch(tokenLen) {
+            case 1:
+                return ISO_8601_1_STRATEGY;
+            case 2:
+                return ISO_8601_2_STRATEGY;
+            case 3:
+                return ISO_8601_3_STRATEGY;
+            default:
+                throw new IllegalArgumentException("invalid number of X");
+            }
+        }
+    }
+
+    private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
+        @Override
+        int modify(final int iValue) {
+            return iValue-1;
+        }
+    };
+    private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
+    private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
+    private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
+    private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
+    private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
+    private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
+    private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
+    private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
+        @Override
+        int modify(final int iValue) {
+            return iValue == 24 ? 0 : iValue;
+        }
+    };
+    private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
+        @Override
+        int modify(final int iValue) {
+            return iValue == 12 ? 0 : iValue;
+        }
+    };
+    private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
+    private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
+    private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
+    private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
+    private static final Strategy ISO_8601_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::?\\d{2})?))");
+
+
+}