You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2016/11/17 17:58:50 UTC

[1/3] logging-log4j2 git commit: [LOG4J2-1712] Pick up bug fixes from Apache Commons Lang's org.apache.commons.lang3.time package.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 220aeb7ef -> 3ef6a9e9c


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java
new file mode 100644
index 0000000..f8a5a0b
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java
@@ -0,0 +1,720 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+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.ParsePosition;
+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.LocaleUtils;
+import org.apache.commons.lang3.SerializationUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests {@link org.apache.commons.lang3.time.FastDateParser}.
+ * 
+ * Copied from Apache Commons Lang 3 on 2016-11-16.
+ */
+public class FastDateParserTest {
+    private static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/s/E";
+    private static final String LONG_FORMAT_NOERA = "yyyy/MMMM/dddd/hhhh/mmmm/ss/aaaa/EEEE";
+    private static final String SHORT_FORMAT = "G/" + SHORT_FORMAT_NOERA;
+    private static final String LONG_FORMAT = "GGGG/" + LONG_FORMAT_NOERA;
+
+    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 TimeZone GMT = TimeZone.getTimeZone("GMT");
+    private static final TimeZone INDIA = TimeZone.getTimeZone("Asia/Calcutta");
+
+    private static final Locale SWEDEN = new Locale("sv", "SE");
+
+    DateParser getInstance(final String format) {
+        return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DateParser getDateInstance(final int dateStyle, final Locale locale) {
+        return getInstance(FormatCache.getPatternForStyle(Integer.valueOf(dateStyle), null, locale), TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    private DateParser getInstance(final String format, final Locale locale) {
+        return getInstance(format, TimeZone.getDefault(), locale);
+    }
+
+    private DateParser getInstance(final String format, final TimeZone timeZone) {
+        return getInstance(format, timeZone, Locale.getDefault());
+    }
+
+    /**
+     * Override this method in derived tests to change the construction of instances
+     *
+     * @param format the format string to use
+     * @param timeZone the time zone to use
+     * @param locale the locale to use
+     *
+     * @return the DateParser instance to use for testing
+     */
+    protected DateParser getInstance(final String format, final TimeZone timeZone, final Locale locale) {
+        return new FastDateParser(format, timeZone, locale, null);
+    }
+
+    @Test
+    public void test_Equality_Hash() {
+        final 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)
+        };
+
+        final Map<DateParser,Integer> map= new HashMap<>();
+        int i= 0;
+        for(final DateParser parser:parsers) {
+            map.put(parser, Integer.valueOf(i++));
+        }
+
+        i= 0;
+        for(final DateParser parser:parsers) {
+            assertEquals(i++, map.get(parser).intValue());
+        }
+    }
+
+    @Test
+    public void testParseZone() throws ParseException {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, Calendar.JULY, 10, 16, 33, 20);
+
+        final 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, Calendar.FEBRUARY, 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, Calendar.FEBRUARY, 10, 15, 5, 6);
+
+        assertEquals(cal.getTime(), fdf.parse("2003-02-10T15:05:06.000 +0500"));
+    }
+
+    @Test
+    public void testParseLongShort() throws ParseException {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 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);
+
+        final 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", NEW_YORK, Locale.US);
+        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 {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+
+        final DateParser h = getInstance("yyyy-MM-dd hh a mm:ss", NEW_YORK, Locale.US);
+        final DateParser K = getInstance("yyyy-MM-dd KK a mm:ss", NEW_YORK, Locale.US);
+        final DateParser k = getInstance("yyyy-MM-dd kk:mm:ss", NEW_YORK, Locale.US);
+        final DateParser H = getInstance("yyyy-MM-dd HH:mm:ss", NEW_YORK, Locale.US);
+
+        cal.set(2010, Calendar.AUGUST, 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, Calendar.AUGUST, 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, Calendar.AUGUST, 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, Calendar.AUGUST, 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"));
+    }
+
+    private Calendar getEraStart(int year, final TimeZone zone, final Locale locale) {
+        final Calendar cal = Calendar.getInstance(zone, locale);
+        cal.clear();
+
+        // http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
+        if (locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
+            if(year < 1868) {
+                cal.set(Calendar.ERA, 0);
+                cal.set(Calendar.YEAR, 1868-year);
+            }
+        }
+        else {
+            if (year < 0) {
+                cal.set(Calendar.ERA, GregorianCalendar.BC);
+                year= -year;
+            }
+            cal.set(Calendar.YEAR, year/100 * 100);
+        }
+        return cal;
+    }
+
+    private void validateSdfFormatFdpParseEquality(final String format, final Locale locale, final TimeZone tz, final DateParser fdp, final Date in, final int year, final Date cs) throws ParseException {
+        final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+        sdf.setTimeZone(tz);
+        if (format.equals(SHORT_FORMAT)) {
+            sdf.set2DigitYearStart( cs );
+        }
+        final String fmt = sdf.format(in);
+        try {
+            final Date out = fdp.parse(fmt);
+            assertEquals(locale.toString()+" "+in+" "+ format+ " "+tz.getID(), in, out);
+        } catch (final ParseException pe) {
+            if (year >= 1868 || !locale.getCountry().equals("JP")) {// LANG-978
+                throw pe;
+            }
+        }
+    }
+
+    @Test
+    // Check that all Locales can parse the formats we use
+    public void testParses() throws Exception {
+        for(final String format : new String[]{LONG_FORMAT, SHORT_FORMAT}) {
+            for(final Locale locale : Locale.getAvailableLocales()) {
+                for(final TimeZone tz :  new TimeZone[]{NEW_YORK, REYKJAVIK, GMT}) {
+                     for(final int year : new int[]{2003, 1940, 1868, 1867, 1, -1, -1940}) {
+                        final Calendar cal= getEraStart(year, tz, locale);
+                        final Date centuryStart= cal.getTime();
+
+                        cal.set(Calendar.MONTH, 1);
+                        cal.set(Calendar.DAY_OF_MONTH, 10);
+                        final Date in= cal.getTime();
+
+                        final FastDateParser fdp= new FastDateParser(format, tz, locale, centuryStart);
+                        validateSdfFormatFdpParseEquality(format, locale, tz, fdp, in, year, centuryStart);
+                    }
+                }
+            }
+        }
+    }
+
+    // we cannot use historic dates to test timezone parsing, some timezones have second offsets
+    // as well as hours and minutes which makes the z formats a low fidelity round trip
+    @Test
+    public void testTzParses() throws Exception {
+        // Check that all Locales can parse the time formats we use
+        for(final Locale locale : Locale.getAvailableLocales()) {
+            final FastDateParser fdp= new FastDateParser("yyyy/MM/dd z", TimeZone.getDefault(), locale);
+
+            for(final TimeZone tz :  new TimeZone[]{NEW_YORK, REYKJAVIK, GMT}) {
+                final Calendar cal= Calendar.getInstance(tz, locale);
+                cal.clear();
+                cal.set(Calendar.YEAR, 2000);
+                cal.set(Calendar.MONTH, 1);
+                cal.set(Calendar.DAY_OF_MONTH, 10);
+                final Date expected= cal.getTime();
+
+                final Date actual = fdp.parse("2000/02/10 "+tz.getDisplayName(locale));
+                Assert.assertEquals("tz:"+tz.getID()+" locale:"+locale.getDisplayName(), expected, actual);
+            }
+        }
+    }
+
+
+    @Test
+    public void testLocales_Long_AD() throws Exception {
+        testLocales(LONG_FORMAT, false);
+    }
+
+    @Test
+    public void testLocales_Long_BC() throws Exception {
+        testLocales(LONG_FORMAT, true);
+    }
+
+    @Test
+    public void testLocales_Short_AD() throws Exception {
+        testLocales(SHORT_FORMAT, false);
+    }
+
+    @Test
+    public void testLocales_Short_BC() throws Exception {
+        testLocales(SHORT_FORMAT, true);
+    }
+
+    @Test
+    public void testLocales_LongNoEra_AD() throws Exception {
+        testLocales(LONG_FORMAT_NOERA, false);
+    }
+
+    @Test
+    public void testLocales_LongNoEra_BC() throws Exception {
+        testLocales(LONG_FORMAT_NOERA, true);
+    }
+
+    @Test
+    public void testLocales_ShortNoEra_AD() throws Exception {
+        testLocales(SHORT_FORMAT_NOERA, false);
+    }
+
+    @Test
+    public void testLocales_ShortNoEra_BC() throws Exception {
+        testLocales(SHORT_FORMAT_NOERA, true);
+    }
+
+    private void testLocales(final String format, final boolean eraBC) throws Exception {
+
+        final Calendar cal= Calendar.getInstance(GMT);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10);
+        if (eraBC) {
+            cal.set(Calendar.ERA, GregorianCalendar.BC);
+        }
+
+        for(final Locale locale : Locale.getAvailableLocales() ) {
+            // ja_JP_JP cannot handle dates before 1868 properly
+            if (eraBC && locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
+                continue;
+            }
+            final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+            final DateParser fdf = getInstance(format, locale);
+
+            try {
+                checkParse(locale, cal, sdf, fdf);
+            } catch(final ParseException ex) {
+                Assert.fail("Locale "+locale+ " failed with "+format+" era "+(eraBC?"BC":"AD")+"\n" + trimMessage(ex.toString()));
+            }
+        }
+    }
+    
+    @Test
+    public void testJpLocales() {
+
+        final Calendar cal= Calendar.getInstance(GMT);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10);
+        cal.set(Calendar.ERA, GregorianCalendar.BC);
+
+        final Locale locale = LocaleUtils.toLocale("zh"); {
+            // ja_JP_JP cannot handle dates before 1868 properly
+
+            final SimpleDateFormat sdf = new SimpleDateFormat(LONG_FORMAT, locale);
+            final DateParser fdf = getInstance(LONG_FORMAT, locale);
+
+            try {
+                checkParse(locale, cal, sdf, fdf);
+            } catch(final ParseException ex) {
+                Assert.fail("Locale "+locale+ " failed with "+LONG_FORMAT+"\n" + trimMessage(ex.toString()));
+            }
+        }
+    }
+
+    private String trimMessage(final String msg) {
+        if (msg.length() < 100) {
+            return msg;
+        }
+        final int gmt = msg.indexOf("(GMT");
+        if (gmt > 0) {
+            return msg.substring(0, gmt+4)+"...)";
+        }
+        return msg.substring(0, 100)+"...";
+    }
+
+    private void checkParse(final Locale locale, final Calendar cal, final SimpleDateFormat sdf, final DateParser fdf) throws ParseException {
+        final String formattedDate= sdf.format(cal.getTime());
+        checkParse(locale, sdf, fdf, formattedDate);
+        checkParse(locale, sdf, fdf, formattedDate.toLowerCase(locale));
+        checkParse(locale, sdf, fdf, formattedDate.toUpperCase(locale));
+    }
+
+    private void checkParse(final Locale locale, final SimpleDateFormat sdf, final DateParser fdf, final String formattedDate) throws ParseException {
+        final Date expectedTime = sdf.parse(formattedDate);
+        final Date actualTime = fdf.parse(formattedDate);
+        assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime);
+    }
+
+    @Test
+    public void testParseNumerics() throws ParseException {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
+        cal.set(Calendar.MILLISECOND, 989);
+
+        final DateParser fdf = getInstance("yyyyMMddHHmmssSSS", NEW_YORK, Locale.US);
+        assertEquals(cal.getTime(), fdf.parse("20030210153320989"));
+    }
+
+    @Test
+    public void testQuotes() throws ParseException {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
+        cal.set(Calendar.MILLISECOND, 989);
+
+        final DateParser fdf = getInstance("''yyyyMMdd'A''B'HHmmssSSS''", NEW_YORK, Locale.US);
+        assertEquals(cal.getTime(), fdf.parse("'20030210A'B153320989'"));
+    }
+
+    @Test
+    public void testSpecialCharacters() throws Exception {
+        testSdfAndFdp("q" ,"", true); // bad pattern character (at present)
+        testSdfAndFdp("Q" ,"", true); // bad pattern character
+        testSdfAndFdp("$" ,"$", false); // OK
+        testSdfAndFdp("?.d" ,"?.12", false); // OK
+        testSdfAndFdp("''yyyyMMdd'A''B'HHmmssSSS''", "'20030210A'B153320989'", false); // OK
+        testSdfAndFdp("''''yyyyMMdd'A''B'HHmmssSSS''", "''20030210A'B153320989'", false); // OK
+        testSdfAndFdp("'$\\Ed'" ,"$\\Ed", false); // OK
+        
+        // quoted charaters are case sensitive
+        testSdfAndFdp("'QED'", "QED", false);
+        testSdfAndFdp("'QED'", "qed", true);
+        // case sensitive after insensitive Month field
+        testSdfAndFdp("yyyy-MM-dd 'QED'", "2003-02-10 QED", false);
+        testSdfAndFdp("yyyy-MM-dd 'QED'", "2003-02-10 qed", true);
+    }
+    
+    @Test
+    public void testLANG_832() throws Exception {
+        testSdfAndFdp("'d'd" ,"d3", false); // OK
+        testSdfAndFdp("'d'd'","d3", true); // should fail (unterminated quote)
+    }
+
+    @Test
+    public void testLANG_831() throws Exception {
+        testSdfAndFdp("M E","3  Tue", true);
+    }
+
+    private void testSdfAndFdp(final String format, final String date, final boolean shouldFail)
+            throws Exception {
+        Date dfdp = null;
+        Date dsdf = null;
+        Throwable f = null;
+        Throwable s = null;
+
+        try {
+            final SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
+            sdf.setTimeZone(NEW_YORK);
+            dsdf = sdf.parse(date);
+            if (shouldFail) {
+                Assert.fail("Expected SDF failure, but got " + dsdf + " for ["+format+","+date+"]");
+            }
+        } catch (final Exception e) {
+            s = e;
+            if (!shouldFail) {
+                throw e;
+            }
+        }
+
+        try {
+            final DateParser fdp = getInstance(format, NEW_YORK, Locale.US);
+            dfdp = fdp.parse(date);
+            if (shouldFail) {
+                Assert.fail("Expected FDF failure, but got " + dfdp + " for ["+format+","+date+"]");
+            }
+        } catch (final Exception e) {
+            f = e;
+            if (!shouldFail) {
+                throw e;
+            }
+        }
+        // SDF and FDF should produce equivalent results
+        assertTrue("Should both or neither throw Exceptions", (f==null)==(s==null));
+        assertEquals("Parsed dates should be equal", dsdf, dfdp);
+    }
+
+    @Test
+    public void testDayOf() throws ParseException {
+        final Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10);
+
+        final 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#FastDateParser(String, TimeZone, Locale)}.
+     * @throws ParseException so we don't have to catch it
+     */
+    @Test
+    public void testShortDateStyleWithLocales() throws ParseException {
+        DateParser fdf = getDateInstance(FastDateFormat.SHORT, Locale.US);
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+
+        cal.set(2004, Calendar.FEBRUARY, 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 so we don't have to catch it
+     */
+    @Test
+    public void testLowYearPadding() throws ParseException {
+        final DateParser parser = getInstance(YMD_SLASH);
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+
+        cal.set(1, Calendar.JANUARY, 1);
+        assertEquals(cal.getTime(), parser.parse("0001/01/01"));
+        cal.set(10, Calendar.JANUARY, 1);
+        assertEquals(cal.getTime(), parser.parse("0010/01/01"));
+        cal.set(100, Calendar.JANUARY, 1);
+        assertEquals(cal.getTime(), parser.parse("0100/01/01"));
+        cal.set(999, Calendar.JANUARY, 1);
+        assertEquals(cal.getTime(), parser.parse("0999/01/01"));
+    }
+
+    @Test
+    public void testMilleniumBug() throws ParseException {
+        final DateParser parser = getInstance(DMY_DOT);
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+
+        cal.set(1000, Calendar.JANUARY, 1);
+        assertEquals(cal.getTime(), parser.parse("01.01.1000"));
+    }
+
+    @Test
+    public void testLang303() throws ParseException {
+        DateParser parser = getInstance(YMD_SLASH);
+        final Calendar cal = Calendar.getInstance();
+        cal.set(2004, Calendar.DECEMBER, 31);
+
+        final Date date = parser.parse("2004/11/31");
+
+        parser = SerializationUtils.deserialize(SerializationUtils.serialize((Serializable) parser));
+        assertEquals(date, parser.parse("2004/11/31"));
+    }
+
+    @Test
+    public void testLang538() throws ParseException {
+        final DateParser parser = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", GMT);
+
+        final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-8"));
+        cal.clear();
+        cal.set(2009, Calendar.OCTOBER, 16, 8, 42, 16);
+
+        assertEquals(cal.getTime(), parser.parse("2009-10-16T16:42:16.000Z"));
+    }
+
+    @Test
+    public void testEquals() {
+        final DateParser parser1= getInstance(YMD_SLASH);
+        final DateParser parser2= getInstance(YMD_SLASH);
+
+        assertEquals(parser1, parser2);
+        assertEquals(parser1.hashCode(), parser2.hashCode());
+
+        assertFalse(parser1.equals(new Object()));
+    }
+
+    @Test
+    public void testToStringContainsName() {
+        final DateParser parser= getInstance(YMD_SLASH);
+        assertTrue(parser.toString().startsWith("FastDate"));
+    }
+
+    @Test
+    public void testPatternMatches() {
+        final DateParser parser= getInstance(yMdHmsSZ);
+        assertEquals(yMdHmsSZ, parser.getPattern());
+    }
+
+    @Test
+    public void testLocaleMatches() {
+        final DateParser parser= getInstance(yMdHmsSZ, SWEDEN);
+        assertEquals(SWEDEN, parser.getLocale());
+    }
+
+    @Test
+    public void testTimeZoneMatches() {
+        final DateParser parser= getInstance(yMdHmsSZ, REYKJAVIK);
+        assertEquals(REYKJAVIK, parser.getTimeZone());
+    }
+    
+    @Test
+    public void testLang996() throws ParseException {
+        final Calendar expected = Calendar.getInstance(NEW_YORK, Locale.US);
+        expected.clear();
+        expected.set(2014, Calendar.MAY, 14);
+
+        final DateParser fdp = getInstance("ddMMMyyyy", NEW_YORK, Locale.US);        
+        assertEquals(expected.getTime(), fdp.parse("14may2014"));
+        assertEquals(expected.getTime(), fdp.parse("14MAY2014"));
+        assertEquals(expected.getTime(), fdp.parse("14May2014"));
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void test1806Argument() {
+        getInstance("XXXX");
+    }
+
+    private static Calendar initializeCalendar(final TimeZone tz) {
+        final Calendar cal = Calendar.getInstance(tz);
+        cal.set(Calendar.YEAR, 2001);
+        cal.set(Calendar.MONTH, 1); // not daylight savings
+        cal.set(Calendar.DAY_OF_MONTH, 4);
+        cal.set(Calendar.HOUR_OF_DAY, 12);
+        cal.set(Calendar.MINUTE, 8);
+        cal.set(Calendar.SECOND, 56);
+        cal.set(Calendar.MILLISECOND, 235);
+        return cal;
+    }
+
+    private static enum Expected1806 {
+        India(INDIA, "+05", "+0530", "+05:30", true), 
+        Greenwich(GMT, "Z", "Z", "Z", false), 
+        NewYork(NEW_YORK, "-05", "-0500", "-05:00", false);
+
+        private Expected1806(final TimeZone zone, final String one, final String two, final String three, final boolean hasHalfHourOffset) {
+            this.zone = zone;
+            this.one = one;
+            this.two = two;
+            this.three = three;
+            this.offset = hasHalfHourOffset ?30*60*1000 :0;
+        }
+
+        final TimeZone zone;
+        final String one;
+        final String two;
+        final String three;
+        final long offset;
+    }
+    
+    @Test
+    public void test1806() throws ParseException {
+        final String formatStub = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+        final String dateStub = "2001-02-04T12:08:56.235";
+        
+        for (final Expected1806 trial : Expected1806.values()) {
+            final Calendar cal = initializeCalendar(trial.zone);
+
+            final String message = trial.zone.getDisplayName()+";";
+            
+            DateParser parser = getInstance(formatStub+"X", trial.zone);
+            assertEquals(message+trial.one, cal.getTime().getTime(), parser.parse(dateStub+trial.one).getTime()-trial.offset);
+
+            parser = getInstance(formatStub+"XX", trial.zone);
+            assertEquals(message+trial.two, cal.getTime(), parser.parse(dateStub+trial.two));
+
+            parser = getInstance(formatStub+"XXX", trial.zone);
+            assertEquals(message+trial.three, cal.getTime(), parser.parse(dateStub+trial.three));
+        }
+    }
+
+    @Test
+    public void testLang1121() throws ParseException {
+        final TimeZone kst = TimeZone.getTimeZone("KST");
+        final DateParser fdp = getInstance("yyyyMMdd", kst, Locale.KOREA);
+
+        try {
+            fdp.parse("2015");
+            Assert.fail("expected parse exception");
+        } catch (final ParseException pe) {
+        }
+
+        // Wed Apr 29 00:00:00 KST 2015
+        Date actual = fdp.parse("20150429");
+        final Calendar cal = Calendar.getInstance(kst, Locale.KOREA);
+        cal.clear();
+        cal.set(2015, 3, 29);
+        Date expected = cal.getTime();
+        Assert.assertEquals(expected, actual);
+
+        final SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);
+        df.setTimeZone(kst);
+        expected = df.parse("20150429113100");
+
+        // Thu Mar 16 00:00:00 KST 81724
+        actual = fdp.parse("20150429113100");
+        Assert.assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testParseOffset() {
+        final DateParser parser = getInstance(YMD_SLASH);
+        final Date date = parser.parse("Today is 2015/07/04", new ParsePosition(9));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2015, Calendar.JULY, 4);
+        Assert.assertEquals(cal.getTime(), date);
+    }
+
+    @Test
+    public void testDayNumberOfWeek() throws ParseException {
+        final DateParser parser = getInstance("u");
+        final Calendar calendar = Calendar.getInstance();
+
+        calendar.setTime(parser.parse("1"));
+        Assert.assertEquals(Calendar.MONDAY, calendar.get(Calendar.DAY_OF_WEEK));
+
+        calendar.setTime(parser.parse("6"));
+        Assert.assertEquals(Calendar.SATURDAY, calendar.get(Calendar.DAY_OF_WEEK));
+
+        calendar.setTime(parser.parse("7"));
+        Assert.assertEquals(Calendar.SUNDAY, calendar.get(Calendar.DAY_OF_WEEK));
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java
new file mode 100644
index 0000000..0caa8be
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Copied from Apache Commons Lang 3 on 2016-11-16.
+ */
+public class FastDateParser_MoreOrLessTest {
+
+    private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+    
+    @Test
+    public void testInputHasPrecedingCharacters() {
+        final FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final Date date = parser.parse("A 3/23/61", parsePosition);
+        Assert.assertNull(date);
+        Assert.assertEquals(0, parsePosition.getIndex());      
+        Assert.assertEquals(0, parsePosition.getErrorIndex());        
+    }
+
+    @Test
+    public void testInputHasWhitespace() {
+        final FastDateParser parser = new FastDateParser("M/d/y", TimeZone.getDefault(), Locale.getDefault());
+        //SimpleDateFormat parser = new SimpleDateFormat("M/d/y");
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final Date date = parser.parse(" 3/ 23/ 1961", parsePosition);
+        Assert.assertEquals(12, parsePosition.getIndex());
+
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        Assert.assertEquals(1961, calendar.get(Calendar.YEAR));
+        Assert.assertEquals(2, calendar.get(Calendar.MONTH));
+        Assert.assertEquals(23, calendar.get(Calendar.DATE));       
+    }
+
+    @Test
+    public void testInputHasMoreCharacters() {
+        final FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final Date date = parser.parse("3/23/61", parsePosition);
+        Assert.assertEquals(4, parsePosition.getIndex());
+
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        Assert.assertEquals(2, calendar.get(Calendar.MONTH));
+        Assert.assertEquals(23, calendar.get(Calendar.DATE));       
+    }
+    
+    @Test
+    public void testInputHasWrongCharacters() {
+        final FastDateParser parser = new FastDateParser("MM-dd-yyy", TimeZone.getDefault(), Locale.getDefault());
+        final ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNull(parser.parse("03/23/1961", parsePosition));
+        Assert.assertEquals(2, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasLessCharacters() {
+        final FastDateParser parser = new FastDateParser("MM/dd/yyy", TimeZone.getDefault(), Locale.getDefault());
+        final ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNull(parser.parse("03/23", parsePosition));
+        Assert.assertEquals(5, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasWrongTimeZone() {
+        final FastDateParser parser = new FastDateParser("mm:ss z", NEW_YORK, Locale.US);
+        
+        final String input = "11:23 Pacific Standard Time";
+        final ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNotNull(parser.parse(input, parsePosition));
+        Assert.assertEquals(input.length(), parsePosition.getIndex());
+        
+        parsePosition.setIndex(0);
+        Assert.assertNull(parser.parse( "11:23 Pacific Standard ", parsePosition));
+        Assert.assertEquals(6, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasWrongDay() {
+        final FastDateParser parser = new FastDateParser("EEEE, MM/dd/yyy", NEW_YORK, Locale.US);
+        final String input = "Thursday, 03/23/61";
+        final ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNotNull(parser.parse(input, parsePosition));
+        Assert.assertEquals(input.length(), parsePosition.getIndex());
+        
+        parsePosition.setIndex(0);
+        Assert.assertNull(parser.parse( "Thorsday, 03/23/61", parsePosition));
+        Assert.assertEquals(0, parsePosition.getErrorIndex());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java
new file mode 100644
index 0000000..64d5ed3
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.DateFormatSymbols;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Copied from Apache Commons Lang 3 on 2016-11-16.
+ */
+public class FastDateParser_TimeZoneStrategyTest {
+
+    @Test
+    public void testTimeZoneStrategyPattern() {
+        for(final Locale locale : Locale.getAvailableLocales()) {
+            final FastDateParser parser = new FastDateParser("z", TimeZone.getDefault(), locale);
+            final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
+            for(final String[] zone :  zones) {
+                for(int t = 1; t<zone.length; ++t) {
+                    final String tzDisplay = zone[t];
+                    if (tzDisplay == null) {
+                        break;
+                    }
+                    try {
+                        parser.parse(tzDisplay);
+                    }
+                    catch(final Exception ex) {
+                        Assert.fail("'" + tzDisplay + "'"
+                                + " Locale: '" + locale.getDisplayName() + "'"
+                                + " TimeZone: " + zone[0]
+                                + " offset: " + t
+                                + " defaultLocale: " + Locale.getDefault()
+                                + " defaultTimeZone: " + TimeZone.getDefault().getDisplayName()
+                                );
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testLang1219() throws ParseException {
+        final FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", TimeZone.getDefault(), Locale.GERMAN);
+
+        final Date summer = parser.parse("26.10.2014 02:00:00 MESZ");
+        final Date standard = parser.parse("26.10.2014 02:00:00 MEZ");
+        Assert.assertNotEquals(summer.getTime(), standard.getTime());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b9e12e3..bea8e83 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -99,6 +99,9 @@
       <action issue="LOG4J2-1623" dev="mikes" type="fix">
         Configurable JVM shutdown hook timeout.
       </action>
+      <action issue="LOG4J2-1712" dev="ggregory" type="fix">
+        Pick up bug fixes from Apache Commons Lang's org.apache.commons.lang3.time package.
+      </action>
       <action issue="LOG4J2-1636" dev="ggregory" type="fix" due-to="Eldar Gabdullin">
         Console Appender does not pick up Oracle Java 8's sun.stdout.encoding and sun.stderr.encoding.
       </action>


[3/3] logging-log4j2 git commit: When writing about the hash code, use 'hash code', the compound 'hashcode' is not a work in English. The text 'hashCode()' is left as in of course.

Posted by gg...@apache.org.
When writing about the hash code, use 'hash code', the compound
'hashcode' is not a work in English. The text 'hashCode()' is left as in
of course.

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

Branch: refs/heads/master
Commit: 3ef6a9e9c2a84c581b433d34829c8f6c33023def
Parents: 8af687f
Author: Gary Gregory <gg...@apache.org>
Authored: Thu Nov 17 09:58:45 2016 -0800
Committer: Gary Gregory <gg...@apache.org>
Committed: Thu Nov 17 09:58:45 2016 -0800

----------------------------------------------------------------------
 .../apache/logging/log4j/core/util/datetime/FastDateFormat.java  | 4 ++--
 .../apache/logging/log4j/core/util/datetime/FastDateParser.java  | 4 ++--
 .../apache/logging/log4j/core/util/datetime/FastDatePrinter.java | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3ef6a9e9/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
index b6ff2c1..6f68a6e 100644
--- 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
@@ -571,10 +571,10 @@ public class FastDateFormat extends Format implements DatePrinter, DateParser, S
 
     /**
      * <p>
-     * Returns a hashcode compatible with equals.
+     * Returns a hash code compatible with equals.
      * </p>
      *
-     * @return a hashcode compatible with equals
+     * @return a hash code compatible with equals
      */
     @Override
     public int hashCode() {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3ef6a9e9/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
index 13b00fa..19821a2 100644
--- 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
@@ -308,9 +308,9 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * <p>Return a hashcode compatible with equals.</p>
+     * <p>Return a hash code compatible with equals.</p>
      *
-     * @return a hashcode compatible with equals
+     * @return a hash code compatible with equals
      */
     @Override
     public int hashCode() {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3ef6a9e9/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
index 3bb6574..1a439be 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
@@ -549,10 +549,10 @@ public class FastDatePrinter implements DatePrinter, Serializable {
 
     /**
      * <p>
-     * Returns a hashcode compatible with equals.
+     * Returns a hash code compatible with equals.
      * </p>
      *
-     * @return a hashcode compatible with equals
+     * @return a hash code compatible with equals
      */
     @Override
     public int hashCode() {


[2/3] logging-log4j2 git commit: [LOG4J2-1712] Pick up bug fixes from Apache Commons Lang's org.apache.commons.lang3.time package.

Posted by gg...@apache.org.
[LOG4J2-1712] Pick up bug fixes from Apache Commons Lang's
org.apache.commons.lang3.time package.

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

Branch: refs/heads/master
Commit: 8af687f48297950be8d29bcd143a4ba622732b7d
Parents: 220aeb7
Author: Gary Gregory <gg...@apache.org>
Authored: Thu Nov 17 09:43:14 2016 -0800
Committer: Gary Gregory <gg...@apache.org>
Committed: Thu Nov 17 09:43:14 2016 -0800

----------------------------------------------------------------------
 .../log4j/core/util/datetime/DateParser.java    |  25 +-
 .../core/util/datetime/FastDateFormat.java      |   5 +
 .../core/util/datetime/FastDateParser.java      | 898 ++++++++++---------
 .../util/datetime/FastDateParserSDFTest.java    | 232 +++++
 .../core/util/datetime/FastDateParserTest.java  | 720 +++++++++++++++
 .../datetime/FastDateParser_MoreOrLessTest.java | 115 +++
 .../FastDateParser_TimeZoneStrategyTest.java    |  69 ++
 src/changes/changes.xml                         |   3 +
 8 files changed, 1627 insertions(+), 440 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/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
index 1823bab..f21a527 100644
--- 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
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.util.datetime;
 
 import java.text.ParseException;
 import java.text.ParsePosition;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -50,17 +51,32 @@ public interface DateParser {
      */
     Date parse(String source, ParsePosition pos);
 
+    /**
+     * Parses a formatted date string according to the format.  Updates the Calendar with parsed fields.
+     * Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed.
+     * Not all source text needs to be consumed.  Upon parse failure, ParsePosition error index is updated to
+     * the offset of the source text which does not match the supplied format.
+     *
+     * @param source The text to parse.
+     * @param pos On input, the position in the source to start parsing, on output, updated position.
+     * @param calendar The calendar into which to set parsed fields.
+     * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated)
+     * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is
+     * out of range.
+     */
+    boolean parse(String source, ParsePosition pos, Calendar calendar);
+
     // Accessors
     // -----------------------------------------------------------------------
     /**
-     * Get the pattern used by this parser.
+     * Gets the pattern used by this parser.
      * 
      * @return the pattern, {@link java.text.SimpleDateFormat} compatible
      */
     String getPattern();
 
     /**
-     * Get the time zone used by this parser.
+     * Gets the time zone used by this parser.
      * 
      * <p>
      * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by the
@@ -72,7 +88,7 @@ public interface DateParser {
     TimeZone getTimeZone();
 
     /**
-     * Get the locale used by this parser.
+     * Gets the locale used by this parser.
      * 
      * @return the locale
      */
@@ -89,7 +105,7 @@ public interface DateParser {
     Object parseObject(String source) throws ParseException;
 
     /**
-     * Parse a date/time string according to the given parse position.
+     * Parses 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
@@ -97,4 +113,5 @@ public interface DateParser {
      * @see java.text.DateFormat#parseObject(String, ParsePosition)
      */
     Object parseObject(String source, ParsePosition pos);
+
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/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
index 848715e..b6ff2c1 100644
--- 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
@@ -607,4 +607,9 @@ public class FastDateFormat extends Format implements DatePrinter, DateParser, S
         return printer.applyRules(calendar, buf);
     }
 
+    @Override
+    public boolean parse(String source, ParsePosition pos, Calendar calendar) {
+        return parser.parse(source, pos, calendar);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/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
index 7c21aed..13b00fa 100644
--- 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
@@ -24,80 +24,62 @@ import java.text.ParseException;
 import java.text.ParsePosition;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
+import java.util.TreeSet;
 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.
+ * <p>FastDateParser is a fast and thread-safe version of
+ * {@link java.text.SimpleDateFormat}.</p>
+ *
+ * <p>To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} 
+ * or another variation of the factory methods of {@link FastDateFormat}.</p>
+ * 
+ * <p>Since FastDateParser is thread safe, you can use a static member instance:</p>
+ * <code>
+ *     private static final DateParser DATE_PARSER = FastDateFormat.getInstance("yyyy-MM-dd");
+ * </code>
+ * 
+ * <p>This class can be used as a direct replacement for
+ * <code>SimpleDateFormat</code> in most parsing situations.
+ * This class is especially useful in multi-threaded server environments.
+ * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
+ * nor will it be as Sun has closed the
+ * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
+ * </p>
+ *
+ * <p>Only parsing is supported by this class, but all patterns are compatible with
+ * SimpleDateFormat.</p>
+ *
+ * <p>The class operates in lenient mode, so for example a time of 90 minutes is treated as 1 hour 30 minutes.</p>
+ *
+ * <p>Timing tests indicate this class is as about as fast as SimpleDateFormat
+ * in single thread applications and about 25% faster in multi-thread applications.</p>
+ *
+ * @since 3.2
+ * @see FastDatePrinter
  */
 public class FastDateParser implements DateParser, Serializable {
 
     /**
-     * Japanese locale support.
-     */
-    static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP");
-
-    /**
      * Required for serialization support.
      *
      * @see java.io.Serializable
      */
     private static final long serialVersionUID = 3L;
 
-    private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
-        @Override
-        int modify(final int iValue) {
-            return iValue - 1;
-        }
-    };
-
-    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);
-        }
-    };
-
-    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 DAY_OF_WEEK_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK);
-    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})?))");
+    static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
 
     // defining fields
     private final String pattern;
@@ -105,138 +87,183 @@ public class FastDateParser implements DateParser, Serializable {
     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;
+    private transient List<StrategyAndWidth> patterns;
 
-    // dynamic fields to communicate with Strategy
-    private transient String currentFormatField;
-    private transient Strategy nextStrategy;
+    // comparator used to sort regex alternatives
+    // alternatives should be ordered longer first, and shorter last. ('february' before 'feb')
+    // all entries must be lowercase by locale.
+    private static final Comparator<String> LONGER_FIRST_LOWERCASE = new Comparator<String>() {
+        @Override
+        public int compare(final String left, final String right) {
+            return right.compareTo(left);
+        }
+    };
 
     /**
-     * <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.
+     * <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 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);
+        this(pattern, timeZone, locale, null);
     }
 
     /**
-     * <p>
-     * Constructs a new FastDateParser.
-     * </p>
+     * <p>Constructs a new FastDateParser.</p>
      *
-     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern
+     * @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 Calendar#setLenient(boolean)}
      *
      * @since 3.5
      */
-    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale,
-            final Date centuryStart, final boolean lenient) {
+    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
         this.pattern = pattern;
         this.timeZone = timeZone;
         this.locale = locale;
-        this.lenient = lenient;
 
         final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
 
         int centuryStartYear;
-        if (centuryStart != null) {
+        if(centuryStart!=null) {
             definingCalendar.setTime(centuryStart);
-            centuryStartYear = definingCalendar.get(Calendar.YEAR);
-        } else if (locale.equals(JAPANESE_IMPERIAL)) {
-            centuryStartYear = 0;
-        } else {
+            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;
+            centuryStartYear= definingCalendar.get(Calendar.YEAR)-80;
         }
-        century = centuryStartYear / 100 * 100;
-        startYear = centuryStartYear - century;
+        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)
+     * 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) {
+        patterns = new ArrayList<>();
 
-        final StringBuilder regex = new StringBuilder();
-        final List<Strategy> collector = new ArrayList<>();
+        final StrategyParser fm = new StrategyParser(definingCalendar);
+        for(;;) {
+            final StrategyAndWidth field = fm.getNextStrategy();
+            if(field==null) {
+                break;
+            }
+            patterns.add(field);
+        }
+    }
 
-        final Matcher patternMatcher = formatPattern.matcher(pattern);
-        if (!patternMatcher.lookingAt()) {
-            throw new IllegalArgumentException("Illegal pattern character '"
-                    + pattern.charAt(patternMatcher.regionStart()) + "'");
+    // helper classes to parse the format string
+    //-----------------------------------------------------------------------
+
+    /**
+     * Holds strategy and field width
+     */
+    private static class StrategyAndWidth {
+        final Strategy strategy;
+        final int width;
+
+        StrategyAndWidth(final Strategy strategy, final int width) {
+            this.strategy = strategy;
+            this.width = width;
         }
 
-        currentFormatField = patternMatcher.group();
-        Strategy currentStrategy = getStrategy(currentFormatField, definingCalendar);
-        for (;;) {
-            patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
-            if (!patternMatcher.lookingAt()) {
-                nextStrategy = null;
-                break;
+        int getMaxWidth(final ListIterator<StrategyAndWidth> lt) {
+            if(!strategy.isNumber() || !lt.hasNext()) {
+                return 0;
+            }
+            final Strategy nextStrategy = lt.next().strategy;
+            lt.previous();
+            return nextStrategy.isNumber() ?width :0;
+       }
+    }
+
+    /**
+     * Parse format into Strategies
+     */
+    private class StrategyParser {
+        final private Calendar definingCalendar;
+        private int currentIdx;
+
+        StrategyParser(final Calendar definingCalendar) {
+            this.definingCalendar = definingCalendar;
+        }
+
+        StrategyAndWidth getNextStrategy() {
+            if (currentIdx >= pattern.length()) {
+                return null;
             }
-            final String nextFormatField = patternMatcher.group();
-            nextStrategy = getStrategy(nextFormatField, definingCalendar);
-            if (currentStrategy.addRegex(this, regex)) {
-                collector.add(currentStrategy);
+
+            final char c = pattern.charAt(currentIdx);
+            if (isFormatLetter(c)) {
+                return letterPattern(c);
             }
-            currentFormatField = nextFormatField;
-            currentStrategy = nextStrategy;
+            return literal();
         }
-        if (patternMatcher.regionStart() != patternMatcher.regionEnd()) {
-            throw new IllegalArgumentException("Failed to parse \"" + pattern + "\" ; gave up at index "
-                    + patternMatcher.regionStart());
+
+        private StrategyAndWidth letterPattern(final char c) {
+            final int begin = currentIdx;
+            while (++currentIdx < pattern.length()) {
+                if (pattern.charAt(currentIdx) != c) {
+                    break;
+                }
+            }
+
+            final int width = currentIdx - begin;
+            return new StrategyAndWidth(getStrategy(c, width, definingCalendar), width);
         }
-        if (currentStrategy.addRegex(this, regex)) {
-            collector.add(currentStrategy);
+
+        private StrategyAndWidth literal() {
+            boolean activeQuote = false;
+
+            final StringBuilder sb = new StringBuilder();
+            while (currentIdx < pattern.length()) {
+                final char c = pattern.charAt(currentIdx);
+                if (!activeQuote && isFormatLetter(c)) {
+                    break;
+                } else if (c == '\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\'')) {
+                    activeQuote = !activeQuote;
+                    continue;
+                }
+                ++currentIdx;
+                sb.append(c);
+            }
+
+            if (activeQuote) {
+                throw new IllegalArgumentException("Unterminated quote");
+            }
+
+            final String formatField = sb.toString();
+            return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
         }
-        currentFormatField = null;
-        strategies = collector.toArray(new Strategy[collector.size()]);
-        parsePattern = Pattern.compile(regex.toString());
+    }
+
+    private static boolean isFormatLetter(final char c) {
+        return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
     }
 
     // Accessors
-    // -----------------------------------------------------------------------
-    /*
-     * (non-Javadoc)
-     *
+    //-----------------------------------------------------------------------
+    /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DateParser#getPattern()
      */
     @Override
@@ -244,9 +271,7 @@ public class FastDateParser implements DateParser, Serializable {
         return pattern;
     }
 
-    /*
-     * (non-Javadoc)
-     *
+    /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DateParser#getTimeZone()
      */
     @Override
@@ -254,9 +279,7 @@ public class FastDateParser implements DateParser, Serializable {
         return timeZone;
     }
 
-    /*
-     * (non-Javadoc)
-     *
+    /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DateParser#getLocale()
      */
     @Override
@@ -264,23 +287,13 @@ public class FastDateParser implements DateParser, Serializable {
         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>
+     * <p>Compare another object for equality with this object.</p>
      *
-     * @param obj the object to compare to
+     * @param obj  the object to compare to
      * @return <code>true</code>if equal to this instance
      */
     @Override
@@ -289,13 +302,13 @@ public class FastDateParser implements DateParser, Serializable {
             return false;
         }
         final FastDateParser other = (FastDateParser) obj;
-        return pattern.equals(other.pattern) && timeZone.equals(other.timeZone) && locale.equals(other.locale);
+        return pattern.equals(other.pattern)
+            && timeZone.equals(other.timeZone)
+            && locale.equals(other.locale);
     }
 
     /**
-     * <p>
-     * Return a hashcode compatible with equals.
-     * </p>
+     * <p>Return a hashcode compatible with equals.</p>
      *
      * @return a hashcode compatible with equals
      */
@@ -305,9 +318,7 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * <p>
-     * Get a string version of this formatter.
-     * </p>
+     * <p>Get a string version of this formatter.</p>
      *
      * @return a debugging string
      */
@@ -317,9 +328,10 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     // Serializing
-    // -----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
     /**
-     * Create the object after serialization. This implementation reinitializes the transient properties.
+     * 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.
@@ -332,9 +344,7 @@ public class FastDateParser implements DateParser, Serializable {
         init(definingCalendar);
     }
 
-    /*
-     * (non-Javadoc)
-     *
+    /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
      */
     @Override
@@ -342,28 +352,26 @@ public class FastDateParser implements DateParser, Serializable {
         return parse(source);
     }
 
-    /*
-     * (non-Javadoc)
-     *
+    /* (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));
+        final ParsePosition pp = new ParsePosition(0);
+        final Date date= parse(source, pp);
         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(
+                        "(The " +locale + " locale does not support dates before 1868 AD)\n" +
+                                "Unparseable date: \""+source, pp.getErrorIndex());
             }
-            throw new ParseException("Unparseable date: \"" + source + "\" does not match " + parsePattern.pattern(), 0);
+            throw new ParseException("Unparseable date: "+source, pp.getErrorIndex());
         }
         return date;
     }
 
-    /*
-     * (non-Javadoc)
-     *
+    /* (non-Javadoc)
      * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
      */
     @Override
@@ -372,38 +380,54 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * 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.
+     * This implementation updates the ParsePosition if the parse succeeds.
+     * However, it sets the error index to the position before the failed field unlike 
+     * the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets 
+     * the error index to after the failed field.
      * <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.
+     * 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.
      *
-     * {@inheritDoc}
+     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
      */
     @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);
+        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));
+        return parse(source, pos, cal) ? cal.getTime() : null;
+    }
+
+    /**
+     * Parse a formatted date string according to the format.  Updates the Calendar with parsed fields.
+     * Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed.
+     * Not all source text needs to be consumed.  Upon parse failure, ParsePosition error index is updated to
+     * the offset of the source text which does not match the supplied format.
+     * 
+     * @param source The text to parse.
+     * @param pos On input, the position in the source to start parsing, on output, updated position.
+     * @param calendar The calendar into which to set parsed fields.
+     * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated)
+     * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is
+     * out of range.
+     */
+    @Override
+    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
+        final ListIterator<StrategyAndWidth> lt = patterns.listIterator();
+        while (lt.hasNext()) {
+            final StrategyAndWidth strategyAndWidth = lt.next();
+            final int maxWidth = strategyAndWidth.getMaxWidth(lt);
+            if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) {
+                return false;
+            }
         }
-        pos.setIndex(offset + matcher.end());
-        return cal.getTime();
+        return true;
     }
 
     // Support for strategies
-    // -----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
 
     private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
         for (int i = 0; i < value.length(); ++i) {
@@ -430,69 +454,32 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * 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 cal 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);
+     * @param field The field of interest
+     * @param regex The regular expression to build
+     * @return The map of string display names to field values
+     */
+    private static Map<String, Integer> appendDisplayNames(final Calendar cal, final Locale locale, final int field, final StringBuilder regex) {
+        final Map<String, Integer> values = new HashMap<>();
+
+        final Map<String, Integer> displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale);
+        final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
+        for (final Map.Entry<String, Integer> displayName : displayNames.entrySet()) {
+            final String key = displayName.getKey().toLowerCase(locale);
+            if (sorted.add(key)) {
+                values.put(key, displayName.getValue());
+            }
+        }
+        for (final String symbol : sorted) {
+            simpleQuote(regex, symbol).append('|');
+        }
+        return values;
     }
 
     /**
      * Adjust dates to be within appropriate century
-     *
      * @param twoDigitYear The year to adjust
      * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive)
      */
@@ -502,30 +489,12 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * 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.
+         * Is this field a number?
+         * The default implementation returns false.
          *
          * @return true, if field is a number
          */
@@ -533,53 +502,60 @@ public class FastDateParser implements DateParser, Serializable {
             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) {
+        abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth);
+    }
+
+    /**
+     * A strategy to parse a single field from the parsing pattern
+     */
+    private static abstract class PatternStrategy extends Strategy {
 
+        private Pattern pattern;
+
+        void createPattern(final StringBuilder regex) {
+            createPattern(regex.toString());
+        }
+
+        void createPattern(final String regex) {
+            this.pattern = Pattern.compile(regex);
         }
 
         /**
-         * Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code> which will accept this
-         * field
+         * Is this field a number?
+         * The default implementation returns false.
          *
-         * @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
+         * @return true, if field is a number
          */
-        abstract boolean addRegex(FastDateParser parser, StringBuilder regex);
+        @Override
+        boolean isNumber() {
+            return false;
+        }
 
-    }
+        @Override
+        boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
+            final Matcher matcher = pattern.matcher(source.substring(pos.getIndex()));
+            if (!matcher.lookingAt()) {
+                pos.setErrorIndex(pos.getIndex());
+                return false;
+            }
+            pos.setIndex(pos.getIndex() + matcher.end(1));
+            setCalendar(parser, calendar, matcher.group(1));
+            return true;
+        }
 
-    /**
-     * 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+|u+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
+        abstract void setCalendar(FastDateParser parser, Calendar cal, String value);
+    }
 
     /**
      * 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$
+    private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) {
+        switch(f) {
         default:
-            return new CopyQuotedStrategy(formatField);
+            throw new IllegalArgumentException("Format '"+f+"' not supported");
         case 'D':
             return DAY_OF_YEAR_STRATEGY;
         case 'E':
@@ -588,13 +564,12 @@ public class FastDateParser implements DateParser, Serializable {
             return DAY_OF_WEEK_IN_MONTH_STRATEGY;
         case 'G':
             return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
-        case 'H': // Hour in day (0-23)
+        case 'H':  // Hour in day (0-23)
             return HOUR_OF_DAY_STRATEGY;
-        case 'K': // Hour in am/pm (0-11)
+        case 'K':  // Hour in am/pm (0-11) 
             return HOUR_STRATEGY;
         case 'M':
-            return formatField.length() >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar)
-                    : NUMBER_MONTH_STRATEGY;
+            return width>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
         case 'S':
             return MILLISECOND_STRATEGY;
         case 'W':
@@ -603,9 +578,9 @@ public class FastDateParser implements DateParser, Serializable {
             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
+        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
+        case 'k':  // Hour in day (1-24), i.e. midnight is 24, not 0
             return HOUR24_OF_DAY_STRATEGY;
         case 'm':
             return MINUTE_STRATEGY;
@@ -616,12 +591,13 @@ public class FastDateParser implements DateParser, Serializable {
         case 'w':
             return WEEK_OF_YEAR_STRATEGY;
         case 'y':
-            return formatField.length() > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY;
+        case 'Y':
+            return width>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
         case 'X':
-            return ISO8601TimeZoneStrategy.getStrategy(formatField.length());
+            return ISO8601TimeZoneStrategy.getStrategy(width);
         case 'Z':
-            if (formatField.equals("ZZ")) {
-                return ISO_8601_STRATEGY;
+            if (width==2) {
+                return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY;
             }
             //$FALL-THROUGH$
         case 'z':
@@ -629,13 +605,11 @@ public class FastDateParser implements DateParser, Serializable {
         }
     }
 
-    @SuppressWarnings("unchecked")
-    // OK because we are creating an array with no entries
+    @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
      */
@@ -650,7 +624,6 @@ public class FastDateParser implements DateParser, Serializable {
 
     /**
      * 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
@@ -659,8 +632,9 @@ public class FastDateParser implements DateParser, Serializable {
         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);
+            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;
@@ -673,11 +647,11 @@ public class FastDateParser implements DateParser, Serializable {
      * A strategy that copies the static or quoted field in the parsing pattern
      */
     private static class CopyQuotedStrategy extends Strategy {
-        private final String formatField;
+
+        final private String formatField;
 
         /**
          * Construct a Strategy that ensures the formatField has literal text
-         *
          * @param formatField The literal text to match
          */
         CopyQuotedStrategy(final String formatField) {
@@ -689,60 +663,51 @@ public class FastDateParser implements DateParser, Serializable {
          */
         @Override
         boolean isNumber() {
-            char c = formatField.charAt(0);
-            if (c == '\'') {
-                c = formatField.charAt(1);
-            }
-            return Character.isDigit(c);
+            return false;
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            escapeRegex(regex, formatField, true);
-            return false;
+        boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
+            for (int idx = 0; idx < formatField.length(); ++idx) {
+                final int sIdx = idx + pos.getIndex();
+                if (sIdx == source.length()) {
+                    pos.setErrorIndex(sIdx);
+                    return false;
+                }
+                if (formatField.charAt(idx) != source.charAt(sIdx)) {
+                    pos.setErrorIndex(sIdx);
+                    return false;
+                }
+            }
+            pos.setIndex(formatField.length() + pos.getIndex());
+            return true;
         }
     }
 
     /**
      * A strategy that handles a text field in the parsing pattern
      */
-    private static class CaseInsensitiveTextStrategy extends Strategy {
+     private static class CaseInsensitiveTextStrategy extends PatternStrategy {
         private final int field;
-        private final Locale locale;
+        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
+         * @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<>();
-
-            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) {
+            
+            final StringBuilder regex = new StringBuilder();
             regex.append("((?iu)");
-            for (final String textKeyValue : lKeyValues.keySet()) {
-                simpleQuote(regex, textKeyValue).append('|');
-            }
-            regex.setCharAt(regex.length() - 1, ')');
-            return true;
+            lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex);
+            regex.setLength(regex.length()-1);
+            regex.append(")");
+            createPattern(regex);
         }
 
         /**
@@ -751,19 +716,11 @@ public class FastDateParser implements DateParser, Serializable {
         @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
      */
@@ -772,11 +729,10 @@ public class FastDateParser implements DateParser, Serializable {
 
         /**
          * Construct a Strategy that parses a Number field
-         *
          * @param field The Calendar field
          */
         NumberStrategy(final int field) {
-            this.field = field;
+             this.field= field;
         }
 
         /**
@@ -787,49 +743,87 @@ public class FastDateParser implements DateParser, Serializable {
             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("}+)");
+        boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
+            int idx = pos.getIndex();
+            int last = source.length();
+
+            if (maxWidth == 0) {
+                // if no maxWidth, strip leading white space
+                for (; idx < last; ++idx) {
+                    final char c = source.charAt(idx);
+                    if (!Character.isWhitespace(c)) {
+                        break;
+                    }
+                }
+                pos.setIndex(idx);
             } else {
-                regex.append("(\\p{Nd}++)");
+                final int end = idx + maxWidth;
+                if (last > end) {
+                    last = end;
+                }
             }
-            return true;
-        }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
-            cal.set(field, modify(Integer.parseInt(value)));
+            for (; idx < last; ++idx) {
+                final char c = source.charAt(idx);
+                if (!Character.isDigit(c)) {
+                    break;
+                }
+            }
+
+            if (pos.getIndex() == idx) {
+                pos.setErrorIndex(idx);
+                return false;
+            }
+
+            final int value = Integer.parseInt(source.substring(pos.getIndex(), idx));
+            pos.setIndex(idx);
+
+            calendar.set(field, modify(parser, value));
+            return true;
         }
 
         /**
          * Make any modifications to parsed integer
-         *
+         * @param parser The parser
          * @param iValue The parsed integer
          * @return The modified value
          */
-        int modify(final int iValue) {
+        int modify(final FastDateParser parser, final int iValue) {
             return iValue;
         }
+
     }
 
+    private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        int modify(final FastDateParser parser, final int iValue) {
+            return iValue < 100 ? parser.adjustYear(iValue) : iValue;
+        }
+    };
+
     /**
      * A strategy that handles a timezone field in the parsing pattern
      */
-    static class TimeZoneStrategy extends Strategy {
+    static class TimeZoneStrategy extends PatternStrategy {
         private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
-        private static final String GMT_OPTION = "GMT[+-]\\d{1,2}:\\d{2}";
+        private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}";
 
         private final Locale locale;
-        private final Map<String, TimeZone> tzNames = new HashMap<>();
-        private final String validTimeZoneChars;
+        private final Map<String, TzInfo> tzNames= new HashMap<>();
+
+        private static class TzInfo {
+            TimeZone zone;
+            int dstOffset;
+
+            TzInfo(final TimeZone tz, final boolean useDst) {
+                zone = tz;
+                dstOffset = useDst ?tz.getDSTSavings() :0;
+            }
+        }
 
         /**
          * Index of zone id
@@ -838,46 +832,55 @@ public class FastDateParser implements DateParser, Serializable {
 
         /**
          * 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);
+            sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION );
+
+            final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
 
             final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
             for (final String[] zoneNames : zones) {
+                // offset 0 is the time zone ID and is not localized
                 final String tzId = zoneNames[ID];
                 if (tzId.equalsIgnoreCase("GMT")) {
                     continue;
                 }
                 final TimeZone tz = TimeZone.getTimeZone(tzId);
+                // offset 1 is long standard name
+                // offset 2 is short standard name
+                final TzInfo standard = new TzInfo(tz, false);
+                TzInfo tzInfo = standard;
                 for (int i = 1; i < zoneNames.length; ++i) {
-                    final String currentZoneName = zoneNames[i];
-                    if (currentZoneName == null) {
-                        continue;
+                    switch (i) {
+                    case 3: // offset 3 is long daylight savings (or summertime) name
+                            // offset 4 is the short summertime name
+                        tzInfo = new TzInfo(tz, true);
+                        break;
+                    case 5: // offset 5 starts additional names, probably standard time
+                        tzInfo = standard;
+                        break;
                     }
-                    final String zoneName = currentZoneName.toLowerCase(locale);
-                    if (!tzNames.containsKey(zoneName)) {
-                        tzNames.put(zoneName, tz);
-                        simpleQuote(sb.append('|'), zoneName);
+                    if (zoneNames[i] != null) {
+                        final String key = zoneNames[i].toLowerCase(locale);
+                        // ignore the data associated with duplicates supplied in
+                        // the additional names
+                        if (sorted.add(key)) {
+                            tzNames.put(key, tzInfo);
+                        }
                     }
                 }
             }
-
-            sb.append(')');
-            validTimeZoneChars = sb.toString();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            regex.append(validTimeZoneChars);
-            return true;
+            // order the regex alternatives with longer strings first, greedy
+            // match will ensure longest string will be consumed
+            for (final String zoneName : sorted) {
+                simpleQuote(sb.append('|'), zoneName);
+            }
+            sb.append(")");
+            createPattern(sb);
         }
 
         /**
@@ -885,43 +888,31 @@ public class FastDateParser implements DateParser, Serializable {
          */
         @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);
+                final TimeZone tz = TimeZone.getTimeZone("GMT" + value);
+                cal.setTimeZone(tz);
             } else if (value.regionMatches(true, 0, "GMT", 0, 3)) {
-                tz = TimeZone.getTimeZone(value.toUpperCase());
+                final TimeZone tz = TimeZone.getTimeZone(value.toUpperCase());
+                cal.setTimeZone(tz);
             } else {
-                tz = tzNames.get(value.toLowerCase(locale));
-                if (tz == null) {
-                    throw new IllegalArgumentException(value + " is not a supported timezone name");
-                }
+                final TzInfo tzInfo = tzNames.get(value.toLowerCase(locale));
+                cal.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
+                cal.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
             }
-            cal.setTimeZone(tz);
         }
     }
-
-    private static class ISO8601TimeZoneStrategy extends Strategy {
-        // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
-        private final String pattern;
+    
+    private static class ISO8601TimeZoneStrategy extends PatternStrategy {
+        // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm 
 
         /**
          * Construct a Strategy that parses a TimeZone
-         *
          * @param pattern The Pattern
          */
         ISO8601TimeZoneStrategy(final String pattern) {
-            this.pattern = pattern;
+            createPattern(pattern);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            regex.append(pattern);
-            return true;
-        }
-
+        
         /**
          * {@inheritDoc}
          */
@@ -933,20 +924,20 @@ public class FastDateParser implements DateParser, Serializable {
                 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.
+         *          strategy exists, an IllegalArgumentException will be thrown.
          */
         static Strategy getStrategy(final int tokenLen) {
-            switch (tokenLen) {
+            switch(tokenLen) {
             case 1:
                 return ISO_8601_1_STRATEGY;
             case 2:
@@ -959,4 +950,39 @@ public class FastDateParser implements DateParser, Serializable {
         }
     }
 
+    private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
+        @Override
+        int modify(final FastDateParser parser, 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_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK) {
+        @Override
+        int modify(final FastDateParser parser, final int iValue) {
+            return iValue != 7 ? iValue + 1 : Calendar.SUNDAY;
+        }
+    };
+    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 FastDateParser parser, final int iValue) {
+            return iValue == 24 ? 0 : iValue;
+        }
+    };
+    private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
+        @Override
+        int modify(final FastDateParser parser, 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);
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/8af687f4/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java
new file mode 100644
index 0000000..247dad8
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Compare FastDateParser with SimpleDateFormat 
+ * 
+ * Copied from Apache Commons Lang 3 on 2016-11-16.
+ */
+@RunWith(Parameterized.class)
+public class FastDateParserSDFTest {
+
+    @Parameters(name= "{index}: {0} {1} {2}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object [][]{
+                // General Time zone tests
+                {"z yyyy", "GMT 2010",       Locale.UK, true}, // no offset specified, but this is allowed as a TimeZone name
+                {"z yyyy", "GMT-123 2010",   Locale.UK, false},
+                {"z yyyy", "GMT-1234 2010",  Locale.UK, false},
+                {"z yyyy", "GMT-12:34 2010", Locale.UK, true},
+                {"z yyyy", "GMT-1:23 2010",  Locale.UK, true},
+                // RFC 822 tests
+                {"z yyyy", "-1234 2010",     Locale.UK, true},
+                {"z yyyy", "-12:34 2010",    Locale.UK, false},
+                {"z yyyy", "-123 2010",      Locale.UK, false},
+                // year tests
+                { "MM/dd/yyyy", "01/11/12",  Locale.UK, true},
+                { "MM/dd/yy", "01/11/12",    Locale.UK, true},
+
+                // LANG-1089
+                { "HH", "00",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "00",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "00",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "00",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "01",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "01",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "01",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "01",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "11",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "11",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "11",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "11",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "12",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "12",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "12",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "12",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "13",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "13",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "13",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "13",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "23",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "23",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "23",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "23",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "24",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "24",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "24",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "24",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "25",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "25",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "25",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "25",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+
+                { "HH", "48",    Locale.UK, true}, // Hour in day (0-23)
+                { "KK", "48",    Locale.UK, true}, // Hour in am/pm (0-11)
+                { "hh", "48",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+                { "kk", "48",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
+                });
+    }
+
+    private final String format;
+    private final String input;
+    private final Locale locale;
+    private final boolean valid;
+    private final TimeZone timeZone = TimeZone.getDefault();
+
+    public FastDateParserSDFTest(final String format, final String input, final Locale locale, final boolean valid) {
+        this.format = format;
+        this.input = input;
+        this.locale = locale;
+        this.valid = valid;
+    }
+
+    @Test
+    public void testOriginal() throws Exception {
+        checkParse(input);
+    }
+
+    @Test
+    public void testOriginalPP() throws Exception {
+        checkParsePosition(input);
+    }
+
+    @Test
+    public void testUpperCase() throws Exception {
+        checkParse(input.toUpperCase(locale));
+    }
+
+    @Test
+    public void testUpperCasePP() throws Exception {
+        checkParsePosition(input.toUpperCase(locale));
+    }
+
+    @Test
+    public void testLowerCase() throws Exception {
+        checkParse(input.toLowerCase(locale));
+    }
+
+    @Test
+    public void testLowerCasePP() throws Exception {
+        checkParsePosition(input.toLowerCase(locale));
+    }
+
+    private void checkParse(final String formattedDate) {
+        final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+        sdf.setTimeZone(timeZone);
+        final DateParser fdf = new FastDateParser(format, timeZone, locale);
+        Date expectedTime=null;
+        Class<?> sdfE = null;
+        try {
+            expectedTime = sdf.parse(formattedDate);
+            if (!valid) {
+                // Error in test data
+                throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
+            }
+        } catch (final ParseException e) {
+            if (valid) {
+                // Error in test data
+                throw new RuntimeException("Test data error: expected SDF parse to succeed, but got " + e);
+            }
+            sdfE = e.getClass();
+        }
+        Date actualTime = null;
+        Class<?> fdfE = null;
+        try {
+            actualTime = fdf.parse(formattedDate);
+            if (!valid) {
+                // failure in test
+                fail("Expected FDP parse to fail, but got " + actualTime);
+            }
+        } catch (final ParseException e) {
+            if (valid) {
+                // failure in test
+                fail("Expected FDP parse to succeed, but got " + e);
+            }
+            fdfE = e.getClass();
+        }
+        if (valid) {
+            assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime);            
+        } else {
+            assertEquals(locale.toString()+" "+formattedDate + " expected same Exception ", sdfE, fdfE);            
+        }
+    }
+    private void checkParsePosition(final String formattedDate) {
+        final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+        sdf.setTimeZone(timeZone);
+        final DateParser fdf = new FastDateParser(format, timeZone, locale);
+
+        final ParsePosition sdfP = new ParsePosition(0);
+        final Date expectedTime = sdf.parse(formattedDate, sdfP);
+        final int sdferrorIndex = sdfP.getErrorIndex();
+        if (valid) {
+            assertEquals("Expected SDF error index -1 ", -1, sdferrorIndex);
+            final int endIndex = sdfP.getIndex();
+            final int length = formattedDate.length();
+            if (endIndex != length) {
+                // Error in test data
+                throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length);                
+            }
+        } else {
+            final int errorIndex = sdfP.getErrorIndex();
+            if (errorIndex == -1) {
+                throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);                
+            }
+        }
+
+        final ParsePosition fdfP = new ParsePosition(0);
+        final Date actualTime = fdf.parse(formattedDate, fdfP);
+        final int fdferrorIndex = fdfP.getErrorIndex();
+        if (valid) {
+            assertEquals("Expected FDF error index -1 ", -1, fdferrorIndex);
+            final int endIndex = fdfP.getIndex();
+            final int length = formattedDate.length();
+            assertEquals("Expected FDF to parse full string " + fdfP, length, endIndex);
+            assertEquals(locale.toString()+" "+formattedDate +"\n", expectedTime, actualTime);
+        } else {
+            assertNotEquals("Test data error: expected FDF parse to fail, but got " + actualTime, -1, fdferrorIndex);
+            assertTrue("FDF error index ("+ fdferrorIndex + ") should approxiamate SDF index (" + sdferrorIndex + ")",
+                    sdferrorIndex - fdferrorIndex <= 4);
+        }        
+    }
+}