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);
+ }
+ }
+}