Repository: commons-lang
Updated Branches:
refs/heads/master 2ebf9a21d -> 2fa0b168d
LANG-1192: FastDateFormat support of the week-year component (uppercase
'Y')
Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit:
http://git-wip-us.apache.org/repos/asf/commons-lang/commit/2fa0b168
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/2fa0b168
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/2fa0b168
Branch: refs/heads/master
Commit: 2fa0b168d62a07365b2787d0ed97fa1c2cfb673b
Parents: 2ebf9a2
Author: Chas Honton <c...@apache.org>
Authored: Sun Dec 13 16:38:35 2015 -0800
Committer: Chas Honton <c...@apache.org>
Committed: Sun Dec 13 16:38:35 2015 -0800
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
.../commons/lang3/time/CalendarReflection.java | 99 ++++++++++++++++++++
.../apache/commons/lang3/time/DateParser.java | 16 ++++
.../commons/lang3/time/FastDateFormat.java | 11 ++-
.../commons/lang3/time/FastDateParser.java | 32 ++++---
.../commons/lang3/time/FastDatePrinter.java | 54 ++++++++---
.../apache/commons/lang3/time/WeekYearTest.java | 90 ++++++++++++++++++
7 files changed, 276 insertions(+), 27 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d4904c4..7a184df 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
<body>
<release version="3.5" date="tba" description="tba">
+ <action issue="LANG-1192" type="add" dev="chas" due-to="Dominik
Stadler">FastDateFormat support of the week-year component (uppercase
'Y')</action>
<action issue="LANG-1193" type="fix" dev="sebb" due-to="Qin
Li">ordinalIndexOf("abc", "ab", 1) gives incorrect answer of -1 (correct
answer should be 0); revert fix for LANG-1077</action>
<action issue="LANG-1182" type="update" dev="britter" due-to="Larry
West, Pascal Schumacher">Clarify JavaDoc of
StringUtils.containsAny()</action>
<action issue="LANG-1169" type="add" dev="lguibert" due-to="Rafal
Glowinski, Robert Parr, Arman Sharif">Add StringUtils methods to compare a
string to multiple strings</action>
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/main/java/org/apache/commons/lang3/time/CalendarReflection.java
----------------------------------------------------------------------
diff --git
a/src/main/java/org/apache/commons/lang3/time/CalendarReflection.java
b/src/main/java/org/apache/commons/lang3/time/CalendarReflection.java
new file mode 100644
index 0000000..79ebb3f
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/time/CalendarReflection.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.lang.reflect.Method;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+/**
+ * Use reflection to access java 1.7 methods in Calendar. This allows
compilation with 1.6 compiler.
+ */
+class CalendarReflection {
+
+ private static final Method IS_WEEK_DATE_SUPPORTED =
getCalendarMethod("isWeekDateSupported");
+ private static final Method GET_WEEK_YEAR =
getCalendarMethod("getWeekYear");
+
+ private static Method getCalendarMethod(String methodName,
Class<?>... argTypes) {
+ try {
+ Method m = Calendar.class.getMethod(methodName, argTypes);
+ return m;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Does this calendar instance support week date?
+ * @param calendar The calendar instance.
+ * @return false, if runtime is less than java 1.7; otherwise, the
result of calendar.isWeekDateSupported().
+ */
+ static boolean isWeekDateSupported(Calendar calendar) {
+ try {
+ return IS_WEEK_DATE_SUPPORTED!=null &&
((Boolean)IS_WEEK_DATE_SUPPORTED.invoke(calendar)).booleanValue();
+ } catch (Exception e) {
+ return ExceptionUtils.<Boolean>rethrow(e);
+ }
+ }
+
+ /**
+ * Invoke getWeekYear() method of calendar instance.
+ * <p>
+ * If runtime is 1.7 or better and calendar instance support week
year,
+ * return the value from invocation of getWeekYear().
+ * <p>
+ * If runtime is less than 1.7, and calendar is an instance of
+ * GregorianCalendar, return an approximation of the week year.
+ * (Approximation is good for all years after the Julian to Gregorian
+ * cutover.)
+ * <p>
+ * Otherwise, return the calendar instance year value.
+ *
+ * @param calendar The calendar instance.
+ * @return the week year or year value.
+ */
+ public static int getWeekYear(Calendar calendar) {
+ try {
+ if (isWeekDateSupported(calendar)) {
+ return (Integer) GET_WEEK_YEAR.invoke(calendar);
+ }
+ } catch (Exception e) {
+ return ExceptionUtils.<Integer> rethrow(e);
+ }
+
+ int year = calendar.get(Calendar.YEAR);
+ if (IS_WEEK_DATE_SUPPORTED == null && calendar instanceof
GregorianCalendar) {
+ // not perfect, won't work before gregorian cutover
+ // good enough for most business use.
+ switch (calendar.get(Calendar.MONTH)) {
+ case Calendar.JANUARY:
+ if (calendar.get(Calendar.WEEK_OF_YEAR) >= 52) {
+ --year;
+ }
+ break;
+ case Calendar.DECEMBER:
+ if (calendar.get(Calendar.WEEK_OF_YEAR) == 1) {
+ ++year;
+ }
+ break;
+ }
+ }
+ return year;
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/main/java/org/apache/commons/lang3/time/DateParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/time/DateParser.java
b/src/main/java/org/apache/commons/lang3/time/DateParser.java
index 120c4ab..c91f9a2 100644
--- a/src/main/java/org/apache/commons/lang3/time/DateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/DateParser.java
@@ -18,6 +18,7 @@ package org.apache.commons.lang3.time;
import java.text.ParseException;
import java.text.ParsePosition;
+import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -53,6 +54,21 @@ public interface DateParser {
*/
Date parse(String source, ParsePosition pos);
+ /**
+ * 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.
+ */
+ boolean parse(String source, ParsePosition pos, Calendar calendar);
+
// Accessors
//-----------------------------------------------------------------------
/**
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
----------------------------------------------------------------------
diff --git
a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
index abb5198..c2907b2 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
@@ -550,7 +550,16 @@ public class FastDateFormat extends Format implements
DateParser, DatePrinter {
*/
@Override
public Date parse(final String source, final ParsePosition pos) {
- return parser.parse(source, pos);
+ return parser.parse(source, pos);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
org.apache.commons.lang3.time.DateParser#parse(java.lang.String,
java.text.ParsePosition, java.util.Calendar)
+ */
+ @Override
+ public boolean parse(final String source, final ParsePosition pos,
final Calendar calendar) {
+ return parser.parse(source, pos, calendar);
}
/* (non-Javadoc)
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
----------------------------------------------------------------------
diff --git
a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
index 4dc897b..8210ee3 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -170,7 +170,7 @@ public class FastDateParser implements DateParser,
Serializable {
//-----------------------------------------------------------------------
/**
- * Struct to hold strategy and filed width
+ * Struct to hold strategy and field width
*/
private static class StrategyAndWidth {
final Strategy strategy;
@@ -401,12 +401,12 @@ public class FastDateParser implements DateParser,
Serializable {
return parse(source, pos, cal) ?cal.getTime() :null;
}
-
+
/**
- * Parse a formatted date string according to the format. Updates
the Calendar with parsed fields.
+ * 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.
+ * 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.
@@ -415,17 +415,18 @@ public class FastDateParser implements DateParser,
Serializable {
* @throws IllegalArgumentException when Calendar has been set to be
not lenient, and a parsed field is
* out of range.
*/
- public boolean parse(final String source, final ParsePosition pos,
final Calendar calendar) {
- ListIterator<StrategyAndWidth> lt = patterns.listIterator();
- while(lt.hasNext()) {
- StrategyAndWidth pattern = lt.next();
- int maxWidth = pattern.getMaxWidth(lt);
- if(!pattern.strategy.parse(this, calendar, source, pos,
maxWidth)) {
- return false;
- }
- }
- return true;
- }
+ @Override
+ public boolean parse(final String source, final ParsePosition pos,
final Calendar calendar) {
+ ListIterator<StrategyAndWidth> lt = patterns.listIterator();
+ while(lt.hasNext()) {
+ StrategyAndWidth pattern = lt.next();
+ int maxWidth = pattern.getMaxWidth(lt);
+ if(!pattern.strategy.parse(this, calendar, source, pos,
maxWidth)) {
+ return false;
+ }
+ }
+ return true;
+ }
// Support for strategies
//-----------------------------------------------------------------------
@@ -606,6 +607,7 @@ public class FastDateParser implements DateParser,
Serializable {
case 'w':
return WEEK_OF_YEAR_STRATEGY;
case 'y':
+ case 'Y':
return width>2 ?LITERAL_YEAR_STRATEGY
:ABBREVIATED_YEAR_STRATEGY;
case 'X':
return ISO8601TimeZoneStrategy.getStrategy(width);
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
----------------------------------------------------------------------
diff --git
a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
index 4f84cc7..f044552 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
@@ -25,7 +25,6 @@ import java.text.FieldPosition;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
-import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@@ -211,11 +210,15 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
rule = new TextField(Calendar.ERA, ERAs);
break;
case 'y': // year (number)
+ case 'Y': // week year
if (tokenLen == 2) {
rule = TwoDigitYearField.INSTANCE;
} else {
rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ?
4 : tokenLen);
}
+ if (c == 'Y') {
+ rule = new WeekYear((NumberRule) rule);
+ }
break;
case 'M': // month in year (text and number)
if (tokenLen >= 4) {
@@ -438,7 +441,7 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
*/
@Override
public String format(final long millis) {
- final Calendar c = newCalendar(); // hard code GregorianCalendar
+ final Calendar c = newCalendar();
c.setTimeInMillis(millis);
return applyRulesToString(c);
}
@@ -453,12 +456,11 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
}
/**
- * Creation method for ne calender instances.
+ * Creation method for new calender instances.
* @return a new Calendar instance.
*/
- private GregorianCalendar newCalendar() {
- // hard code GregorianCalendar
- return new GregorianCalendar(mTimeZone, mLocale);
+ private Calendar newCalendar() {
+ return Calendar.getInstance(mTimeZone, mLocale);
}
/* (non-Javadoc)
@@ -466,7 +468,7 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
*/
@Override
public String format(final Date date) {
- final Calendar c = newCalendar(); // hard code GregorianCalendar
+ final Calendar c = newCalendar();
c.setTime(date);
return applyRulesToString(c);
}
@@ -492,7 +494,7 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
*/
@Override
public StringBuffer format(final Date date, final StringBuffer buf) {
- final Calendar c = newCalendar(); // hard code GregorianCalendar
+ final Calendar c = newCalendar();
c.setTime(date);
return applyRules(c, buf);
}
@@ -519,7 +521,7 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
*/
@Override
public <B extends Appendable> B format(final Date date, final B buf) {
- final Calendar c = newCalendar(); // hard code GregorianCalendar
+ final Calendar c = newCalendar();
c.setTime(date);
return applyRules(c, buf);
}
@@ -528,9 +530,13 @@ public class FastDatePrinter implements DatePrinter,
Serializable {
* @see
org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar,
java.lang.Appendable)
*/
@Override
- public <B extends Appendable> B format(final Calendar calendar, final
B buf) {
+ public <B extends Appendable> B format(Calendar calendar, final B
buf) {
// do not pass in calendar directly, this will cause TimeZone of
FastDatePrinter to be ignored
- return format(calendar.getTime(), buf);
+ if(!calendar.getTimeZone().equals(mTimeZone)) {
+ calendar = (Calendar)calendar.clone();
+ calendar.setTimeZone(mTimeZone);
+ }
+ return applyRules(calendar, buf);
}
/**
@@ -1202,6 +1208,32 @@ public class FastDatePrinter implements
DatePrinter, Serializable {
}
}
+ /**
+ * <p>Inner class to output the numeric day in week.</p>
+ */
+ private static class WeekYear implements NumberRule {
+ private final NumberRule mRule;
+
+ WeekYear(final NumberRule rule) {
+ mRule = rule;
+ }
+
+ @Override
+ public int estimateLength() {
+ return mRule.estimateLength();
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final Calendar
calendar) throws IOException {
+ mRule.appendTo(buffer,
CalendarReflection.getWeekYear(calendar));
+ }
+
+ @Override
+ public void appendTo(final Appendable buffer, final int value)
throws IOException {
+ mRule.appendTo(buffer, value);
+ }
+ }
+
//-----------------------------------------------------------------------
private static final ConcurrentMap<TimeZoneDisplayKey, String>
cTimeZoneDisplayCache =
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/2fa0b168/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
b/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
new file mode 100644
index 0000000..95f5ad8
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/WeekYearTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class WeekYearTest {
+
+ @Parameters(name = "{index}: {3}")
+ public static Collection<Object[]> data() {
+ return Arrays
+ .asList(new Object[][] {
+ { 2005, Calendar.JANUARY, 1, "2004-W53-6" },
+ { 2005, Calendar.JANUARY, 2, "2004-W53-7" },
+ { 2005, Calendar.DECEMBER, 31, "2005-W52-6" },
+ { 2007, Calendar.JANUARY, 1, "2007-W01-1" },
+ { 2007, Calendar.DECEMBER, 30, "2007-W52-7" },
+ { 2007, Calendar.DECEMBER, 31, "2008-W01-1" },
+ { 2008, Calendar.JANUARY, 1, "2008-W01-2" },
+ { 2008, Calendar.DECEMBER, 28, "2008-W52-7" },
+ { 2008, Calendar.DECEMBER, 29, "2009-W01-1" },
+ { 2008, Calendar.DECEMBER, 30, "2009-W01-2" },
+ { 2008, Calendar.DECEMBER, 31, "2009-W01-3" },
+ { 2009, Calendar.JANUARY, 1, "2009-W01-4" },
+ { 2009, Calendar.DECEMBER, 31, "2009-W53-4" },
+ { 2010, Calendar.JANUARY, 1, "2009-W53-5" },
+ { 2010, Calendar.JANUARY, 2, "2009-W53-6" },
+ { 2010, Calendar.JANUARY, 3, "2009-W53-7" }
+ });
+ }
+
+ final Calendar vulgar;
+ final String isoForm;
+
+ public WeekYearTest(int year, int month, int day, String isoForm) {
+ vulgar = new GregorianCalendar(year, month, day);
+ this.isoForm = isoForm;
+ }
+
+ @Test
+ public void testParser() throws ParseException {
+ final DateParser parser = new FastDateParser("YYYY-'W'ww-u",
TimeZone.getDefault(), Locale.getDefault());
+
+ Calendar cal = Calendar.getInstance();
+ cal.setMinimalDaysInFirstWeek(4);
+ cal.setFirstDayOfWeek(Calendar.MONDAY);
+ cal.clear();
+
+ parser.parse(isoForm, new ParsePosition(0), cal);
+ Assert.assertEquals(vulgar.getTime(), cal.getTime());
+ }
+
+ @Test
+ public void testPrinter() {
+ final FastDatePrinter printer = new
FastDatePrinter("YYYY-'W'ww-u", TimeZone.getDefault(), Locale.getDefault());
+
+ vulgar.setMinimalDaysInFirstWeek(4);
+ vulgar.setFirstDayOfWeek(Calendar.MONDAY);
+
+ Assert.assertEquals(isoForm, printer.format(vulgar));
+ }
+}