Author: chas
Date: Mon Mar 23 02:33:41 2015
New Revision: 1668511
URL: http://svn.apache.org/r1668511
Log:
LANG-1101 FastDateParser and FastDatePrinter support 'X' format
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java?rev=1668511&r1=1668510&r2=1668511&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
(original)
+++
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
Mon Mar 23 02:33:41 2015
@@ -96,7 +96,10 @@ public class FastDateParser implements D
/**
* <p>Constructs a new FastDateParser.</p>
- *
+ *
+ * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
or another variation of the
+ * factory methods of {@link FastDateFormat} to get a cached
FastDateParser instance.
+ *
* @param pattern non-null {@link java.text.SimpleDateFormat}
compatible
* pattern
* @param timeZone non-null time zone to use
@@ -467,13 +470,14 @@ public class FastDateParser implements D
* false, if this field is a constant value
*/
abstract boolean addRegex(FastDateParser parser, StringBuilder
regex);
+
}
/**
* A <code>Pattern</code> to parse the user supplied SimpleDateFormat
pattern
*/
private static final Pattern formatPattern= Pattern.compile(
-
"D+|E+|F+|G+|H+|K+|M+|S+|W+|Z+|a+|d+|h+|k+|m+|s+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
+
"D+|E+|F+|G+|H+|K+|M+|S+|W+|X+|Z+|a+|d+|h+|k+|m+|s+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
/**
* Obtain a Strategy given a field from a SimpleDateFormat pattern
@@ -524,6 +528,8 @@ public class FastDateParser implements D
return WEEK_OF_YEAR_STRATEGY;
case 'y':
return formatField.length()>2 ?LITERAL_YEAR_STRATEGY
:ABBREVIATED_YEAR_STRATEGY;
+ case 'X':
+ return
ISO8601TimeZoneStrategy.getStrategy(formatField.length());
case 'Z':
if (formatField.equals("ZZ")) {
return ISO_8601_STRATEGY;
@@ -834,14 +840,18 @@ public class FastDateParser implements D
private static class ISO8601TimeZoneStrategy extends Strategy {
// Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
- private static final String PATTERN =
"(Z|(?:[+-]\\d{2}(?::?\\d{2})?))";
+ private final String pattern;
+
+ ISO8601TimeZoneStrategy(String pattern) {
+ this.pattern = pattern;
+ }
/**
* {@inheritDoc}
*/
@Override
boolean addRegex(FastDateParser parser, StringBuilder regex) {
- regex.append(PATTERN);
+ regex.append(pattern);
return true;
}
@@ -856,6 +866,23 @@ public class FastDateParser implements D
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}))");
+
+ static Strategy getStrategy(int tokenLen) {
+ switch(tokenLen) {
+ case 1:
+ return ISO_8601_1_STRATEGY;
+ case 2:
+ return ISO_8601_2_STRATEGY;
+ case 3:
+ return ISO_8601_3_STRATEGY;
+ default:
+ throw new IllegalArgumentException("invalid number
of X");
+ }
+ }
}
private static final Strategy NUMBER_MONTH_STRATEGY = new
NumberStrategy(Calendar.MONTH) {
@@ -887,5 +914,7 @@ public class FastDateParser implements D
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();
+ private static final Strategy ISO_8601_STRATEGY = new
ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::?\\d{2})?))");
+
+
}
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java?rev=1668511&r1=1668510&r2=1668511&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
(original)
+++
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
Mon Mar 23 02:33:41 2015
@@ -38,7 +38,7 @@ import org.apache.commons.lang3.Validate
* <p>FastDatePrinter is a fast and thread-safe version of
* {@link java.text.SimpleDateFormat}.</p>
*
- * <p>To obtain a proxy to a FastDatePrinter, use {@link
FastDateFormat#getInstance(String, TimeZone, Locale)}
+ * <p>To obtain a FastDatePrinter, use {@link
FastDateFormat#getInstance(String, TimeZone, Locale)}
* or another variation of the factory methods of {@link
FastDateFormat}.</p>
*
* <p>Since FastDatePrinter is thread safe, you can use a static member
instance:</p>
@@ -64,6 +64,10 @@ import org.apache.commons.lang3.Validate
* ISO 8601 full format time zones (eg. {@code +08:00} or {@code -11:00}).
* This introduces a minor incompatibility with Java 1.4, but at a gain of
* useful functionality.</p>
+ *
+ * <p>Starting with JDK7, ISO 8601 support was added using the pattern
{@code 'X'}.
+ * To maintain compatibility, {@code 'ZZ'} will continue to be supported,
but using
+ * one of the {@code 'X'} formats is recommended.
*
* <p>Javadoc cites for the year pattern: <i>For formatting, if the
number of
* pattern letters is 2, the year is truncated to 2 digits; otherwise it
is
@@ -137,6 +141,8 @@ public class FastDatePrinter implements
//-----------------------------------------------------------------------
/**
* <p>Constructs a new FastDatePrinter.</p>
+ * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
or another variation of the
+ * factory methods of {@link FastDateFormat} to get a cached
FastDatePrinter instance.
*
* @param pattern {@link java.text.SimpleDateFormat} compatible
pattern
* @param timeZone non-null time zone to use
@@ -265,6 +271,9 @@ public class FastDatePrinter implements
case 'K': // hour in am/pm (0..11)
rule = selectNumberRule(Calendar.HOUR, tokenLen);
break;
+ case 'X': // ISO 8601
+ rule = Iso8601_Rule.getRule(tokenLen);
+ break;
case 'z': // time zone (text)
if (tokenLen >= 4) {
rule = new TimeZoneNameRule(mTimeZone, mLocale,
TimeZone.LONG);
@@ -581,6 +590,11 @@ public class FastDatePrinter implements
init();
}
+ private static void appendDigits(final StringBuffer buffer, final
int value) {
+ buffer.append((char)(value / 10 + '0'));
+ buffer.append((char)(value % 10 + '0'));
+ }
+
// Rules
//-----------------------------------------------------------------------
/**
@@ -588,7 +602,7 @@ public class FastDatePrinter implements
*/
private interface Rule {
/**
- * Returns the estimated lentgh of the result.
+ * Returns the estimated length of the result.
*
* @return the estimated length
*/
@@ -810,8 +824,7 @@ public class FastDatePrinter implements
if (value < 10) {
buffer.append((char)(value + '0'));
} else {
- buffer.append((char)(value / 10 + '0'));
- buffer.append((char)(value % 10 + '0'));
+ appendDigits(buffer, value);
}
}
}
@@ -863,8 +876,7 @@ public class FastDatePrinter implements
for (int i = mSize; --i >= 2; ) {
buffer.append('0');
}
- buffer.append((char)(value / 10 + '0'));
- buffer.append((char)(value % 10 + '0'));
+ appendDigits(buffer, value);
} else {
int digits;
if (value < 1000) {
@@ -918,8 +930,7 @@ public class FastDatePrinter implements
@Override
public final void appendTo(final StringBuffer buffer, final int
value) {
if (value < 100) {
- buffer.append((char)(value / 10 + '0'));
- buffer.append((char)(value % 10 + '0'));
+ appendDigits(buffer, value);
} else {
buffer.append(Integer.toString(value));
}
@@ -960,8 +971,7 @@ public class FastDatePrinter implements
*/
@Override
public final void appendTo(final StringBuffer buffer, final int
value) {
- buffer.append((char)(value / 10 + '0'));
- buffer.append((char)(value % 10 + '0'));
+ appendDigits(buffer, value);
}
}
@@ -999,8 +1009,7 @@ public class FastDatePrinter implements
*/
@Override
public final void appendTo(final StringBuffer buffer, final int
value) {
- buffer.append((char)(value / 10 + '0'));
- buffer.append((char)(value % 10 + '0'));
+ appendDigits(buffer, value);
}
}
@@ -1121,7 +1130,7 @@ public class FastDatePrinter implements
return value;
}
- /**
+ /**
* <p>Inner class to output a time zone name.</p>
*/
private static class TimeZoneNameRule implements Rule {
@@ -1178,7 +1187,7 @@ public class FastDatePrinter implements
static final TimeZoneNumberRule INSTANCE_COLON = new
TimeZoneNumberRule(true, false);
static final TimeZoneNumberRule INSTANCE_NO_COLON = new
TimeZoneNumberRule(false, false);
static final TimeZoneNumberRule INSTANCE_ISO_8601 = new
TimeZoneNumberRule(true, true);
-
+
final boolean mColon;
final boolean mISO8601;
@@ -1221,16 +1230,95 @@ public class FastDatePrinter implements
}
final int hours = offset / (60 * 60 * 1000);
- buffer.append((char)(hours / 10 + '0'));
- buffer.append((char)(hours % 10 + '0'));
+ appendDigits(buffer, hours);
if (mColon) {
buffer.append(':');
}
final int minutes = offset / (60 * 1000) - 60 * hours;
- buffer.append((char)(minutes / 10 + '0'));
- buffer.append((char)(minutes % 10 + '0'));
+ appendDigits(buffer, minutes);
+ }
+ }
+
+ /**
+ * <p>Inner class to output a time zone as a number {@code +/-HHMM}
+ * or {@code +/-HH:MM}.</p>
+ */
+ private static class Iso8601_Rule implements Rule {
+
+ // Sign TwoDigitHours or Z
+ static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3);
+ // Sign TwoDigitHours Minutes or Z
+ static final Iso8601_Rule ISO8601_HOURS_MINUTES = new
Iso8601_Rule(5);
+ // Sign TwoDigitHours : Minutes or Z
+ static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new
Iso8601_Rule(6);
+
+ static Iso8601_Rule getRule(int tokenLen) {
+ switch(tokenLen) {
+ case 1:
+ return Iso8601_Rule.ISO8601_HOURS;
+ case 2:
+ return Iso8601_Rule.ISO8601_HOURS_MINUTES;
+ case 3:
+ return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
+ default:
+ throw new IllegalArgumentException("invalid number
of X");
+ }
+ }
+
+ final int length;
+
+ /**
+ * Constructs an instance of {@code Iso8601_Rule} with the
specified properties.
+ *
+ * @param length The number of characters in output (unless Z is
output)
+ */
+ Iso8601_Rule(final int length) {
+ this.length = length;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int estimateLength() {
+ return length;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void appendTo(final StringBuffer buffer, final Calendar
calendar) {
+ int zoneOffset = calendar.get(Calendar.ZONE_OFFSET);
+ if (zoneOffset == 0) {
+ buffer.append("Z");
+ return;
+ }
+
+ int offset = zoneOffset + calendar.get(Calendar.DST_OFFSET);
+
+ if (offset < 0) {
+ buffer.append('-');
+ offset = -offset;
+ } else {
+ buffer.append('+');
+ }
+
+ final int hours = offset / (60 * 60 * 1000);
+ appendDigits(buffer, hours);
+
+ if (length<5) {
+ return;
+ }
+
+ if (length==6) {
+ buffer.append(':');
+ }
+
+ final int minutes = offset / (60 * 1000) - 60 * hours;
+ appendDigits(buffer, minutes);
}
}
Modified:
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java?rev=1668511&r1=1668510&r2=1668511&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
(original)
+++
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
Mon Mar 23 02:33:41 2015
@@ -56,6 +56,7 @@ public class FastDateParserTest {
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");
@@ -556,4 +557,63 @@ public class FastDateParserTest {
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(TimeZone tz) {
+ 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(TimeZone zone, String one, String
two, String three, 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 {
+ String formatStub = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ String dateStub = "2001-02-04T12:08:56.235";
+
+ for (Expected1806 trial : Expected1806.values()) {
+ Calendar cal = initializeCalendar(trial.zone);
+
+ 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));
+ }
+ }
+
}
Modified:
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java?rev=1668511&r1=1668510&r2=1668511&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
(original)
+++
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTest.java
Mon Mar 23 02:33:41 2015
@@ -19,6 +19,7 @@ package org.apache.commons.lang3.time;
import static org.junit.Assert.*;
import java.io.Serializable;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -39,6 +40,8 @@ public class FastDatePrinterTest {
private static final String YYYY_MM_DD = "yyyy/MM/dd";
private static final TimeZone NEW_YORK =
TimeZone.getTimeZone("America/New_York");
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+ private static final TimeZone INDIA =
TimeZone.getTimeZone("Asia/Calcutta");
private static final Locale SWEDEN = new Locale("sv", "SE");
DatePrinter getInstance(final String format) {
@@ -272,4 +275,55 @@ public class FastDatePrinterTest {
FastDateFormat colonFormat = FastDateFormat.getInstance("ZZZ");
assertEquals("+00:00", colonFormat.format(c));
}
+
+ private static Calendar initializeCalendar(TimeZone tz) {
+ 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;
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void test1806Argument() {
+ getInstance("XXXX");
+ }
+
+ private static enum Expected1806 {
+ India(INDIA, "+05", "+0530", "+05:30"), Greenwich(GMT,
"Z", "Z", "Z"), NewYork(
+ NEW_YORK, "-05", "-0500", "-05:00");
+
+ private Expected1806(TimeZone zone, String one, String
two, String three) {
+ this.zone = zone;
+ this.one = one;
+ this.two = two;
+ this.three = three;
+ }
+
+ final TimeZone zone;
+ final String one;
+ final String two;
+ final String three;
+ }
+
+
+ @Test
+ public void test1806() throws ParseException {
+ for (Expected1806 trial : Expected1806.values()) {
+ Calendar cal = initializeCalendar(trial.zone);
+
+ DatePrinter printer = getInstance("X", trial.zone);
+ assertEquals(trial.one, printer.format(cal));
+
+ printer = getInstance("XX", trial.zone);
+ assertEquals(trial.two, printer.format(cal));
+
+ printer = getInstance("XXX", trial.zone);
+ assertEquals(trial.three, printer.format(cal));
+ }
+ }
}