Applying Chas Honton's implementation of DateParser and subsequent integration into FastDateFormat and the time package. See LANG-462.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1236055 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Henri Yandell 2012-01-26 07:00:26 +00:00
parent dfa6882a3b
commit cc340ad2eb
12 changed files with 3157 additions and 1154 deletions

View File

@ -247,6 +247,9 @@
<contributor>
<name>Michael Heuer</name>
</contributor>
<contributor>
<name>Chas Honton</name>
</contributor>
<contributor>
<name>Chris Hyzer</name>
</contributor>

View File

@ -0,0 +1,95 @@
/*
* 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.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* <p>DateParser is the "missing" interface for the parsing methods of
* {@link java.text.DateFormat}.</p>
*
* @since 3.2
*/
public interface DateParser {
/**
* Equivalent to DateFormat.parse(String).
*
* See {@link java.text.DateFormat#parse(String)} for more information.
* @param source A <code>String</code> whose beginning should be parsed.
* @return A <code>Date</code> parsed from the string
* @throws ParseException if the beginning of the specified string cannot be parsed.
*/
public Date parse(String source) throws ParseException;
/**
* Equivalent to DateFormat.parse(String, ParsePosition).
*
* See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information.
*
* @param source A <code>String</code>, part of which should be parsed.
* @param pos A <code>ParsePosition</code> object with index and error index information
* as described above.
* @return A <code>Date</code> parsed from the string. In case of error, returns null.
* @throws NullPointerException if text or pos is null.
*/
public Date parse(String source, ParsePosition pos);
// Accessors
//-----------------------------------------------------------------------
/**
* <p>Get the pattern used by this parser.</p>
*
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
*/
public String getPattern();
/**
* <p>Get the time zone used by this parser.</p>
*
* <p>This zone is always used for <code>Date</code> formatting.
* If a <code>Calendar</code> is passed in to be formatted, the
* time zone on that may be used depending on
* {@link #getTimeZoneOverridesCalendar()}.</p>
*
* @return the time zone
*/
public TimeZone getTimeZone();
/**
* <p>Get the locale used by this parser.</p>
*
* @return the locale
*/
public Locale getLocale();
/**
* Parses text from a string to produce a Date.
* See {@link java.text.DateFormat#parseObject(String)}
*/
Object parseObject(String source) throws ParseException;
/**
* Parse a date/time string according to the given parse position.
* See {@link java.text.DateFormat#parseObject(String, ParsePosition)}
*/
public Object parseObject(String source, ParsePosition pos);
}

View File

@ -0,0 +1,125 @@
/*
* 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.FieldPosition;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* <p>DatePrinter is the "missing" interface for the format methods of
* {@link java.text.DateFormat}.</p>
*
* @since 3.2
*/
public interface DatePrinter {
/**
* <p>Formats a millisecond {@code long} value.</p>
*
* @param millis the millisecond value to format
* @return the formatted string
* @since 2.1
*/
public String format(long millis);
/**
* <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
*
* @param date the date to format
* @return the formatted string
*/
public String format(Date date);
/**
* <p>Formats a {@code Calendar} object.</p>
*
* @param calendar the calendar to format
* @return the formatted string
*/
public String format(Calendar calendar);
/**
* <p>Formats a milliseond {@code long} value into the
* supplied {@code StringBuffer}.</p>
*
* @param millis the millisecond value to format
* @param buf the buffer to format into
* @return the specified string buffer
*/
public StringBuffer format(long millis, StringBuffer buf);
/**
* <p>Formats a {@code Date} object into the
* supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
*
* @param date the date to format
* @param buf the buffer to format into
* @return the specified string buffer
*/
public StringBuffer format(Date date, StringBuffer buf);
/**
* <p>Formats a {@code Calendar} object into the
* supplied {@code StringBuffer}.</p>
*
* @param calendar the calendar to format
* @param buf the buffer to format into
* @return the specified string buffer
*/
public StringBuffer format(Calendar calendar, StringBuffer buf);
// Accessors
//-----------------------------------------------------------------------
/**
* <p>Gets the pattern used by this printer.</p>
*
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
*/
public String getPattern();
/**
* <p>Gets the time zone used by this printer.</p>
*
* <p>This zone is always used for {@code Date} printing. </p>
*
* @return the time zone
*/
public TimeZone getTimeZone();
/**
* <p>Gets the locale used by this printer.</p>
*
* @return the locale
*/
public Locale getLocale();
/**
* <p>Formats a {@code Date}, {@code Calendar} or
* {@code Long} (milliseconds) object.</p>
*
* See {@link java.text.DateFormat#format(Object, StringBuffer, FieldPosition)
*
* @param obj the object to format
* @param toAppendTo the buffer to append to
* @param pos the position - ignored
* @return the buffer passed in
*/
StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,816 @@
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>FastDateParser is a fast and thread-safe version of
* {@link java.text.SimpleDateFormat}.</p>
*
* <p>This class can be used as a direct replacement to
* <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 have closed the
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
* </p>
*
* <p>Only parsing is supported, but all patterns are compatible with
* SimpleDateFormat.</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
*/
public class FastDateParser implements DateParser, Serializable {
/**
* Required for serialization support.
*
* @see java.io.Serializable
*/
private static final long serialVersionUID = 1L;
private static final ConcurrentMap<Locale,TimeZoneStrategy> tzsCache=
new ConcurrentHashMap<Locale,TimeZoneStrategy>(3);
// defining fields
private final String pattern;
private final TimeZone timeZone;
private final Locale locale;
// derived fields
private transient Pattern parsePattern;
private transient Strategy[] strategies;
private transient int thisYear;
private transient ConcurrentMap<Integer, KeyValue[]> nameValues;
// dynamic fields to communicate with Strategy
private transient String currentFormatField;
private transient Strategy nextStrategy;
/**
* <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
*/
protected FastDateParser(String pattern, TimeZone timeZone, Locale locale) {
this.pattern = pattern;
this.timeZone = timeZone;
this.locale = locale;
init();
}
/**
* Initialize derived fields from defining fields.
* This is called from constructor and from readObject (de-serialization)
*/
private void init() {
thisYear= Calendar.getInstance(timeZone, locale).get(Calendar.YEAR);
nameValues= new ConcurrentHashMap<Integer, KeyValue[]>();
StringBuilder regex= new StringBuilder();
List<Strategy> collector = new ArrayList<Strategy>();
Matcher patternMatcher= formatPattern.matcher(pattern);
if(!patternMatcher.lookingAt()) {
throw new IllegalArgumentException("Invalid pattern");
}
currentFormatField= patternMatcher.group();
Strategy currentStrategy= getStrategy(currentFormatField);
for(;;) {
patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
if(!patternMatcher.lookingAt()) {
nextStrategy = null;
break;
}
String nextFormatField= patternMatcher.group();
nextStrategy = getStrategy(nextFormatField);
if(currentStrategy.addRegex(this, regex)) {
collector.add(currentStrategy);
}
currentFormatField= nextFormatField;
currentStrategy= nextStrategy;
}
if(currentStrategy.addRegex(this, regex)) {
collector.add(currentStrategy);
}
currentFormatField= null;
strategies= collector.toArray(new Strategy[collector.size()]);
parsePattern= Pattern.compile(regex.toString());
}
// Accessors
//-----------------------------------------------------------------------
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#getPattern()
*/
@Override
public String getPattern() {
return pattern;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#getTimeZone()
*/
@Override
public TimeZone getTimeZone() {
return timeZone;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#getLocale()
*/
@Override
public Locale getLocale() {
return locale;
}
// Basics
//-----------------------------------------------------------------------
/**
* <p>Compare another object for equality with this object.</p>
*
* @param obj the object to compare to
* @return <code>true</code>if equal to this instance
*/
@Override
public boolean equals(Object obj) {
if (! (obj instanceof FastDateParser) ) {
return false;
}
FastDateParser other = (FastDateParser) obj;
return pattern.equals(other.pattern)
&& timeZone.equals(other.timeZone)
&& locale.equals(other.locale);
}
/**
* <p>Return a hashcode compatible with equals.</p>
*
* @return a hashcode compatible with equals
*/
@Override
public int hashCode() {
return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
}
/**
* <p>Get a string version of this formatter.</p>
*
* @return a debugging string
*/
@Override
public String toString() {
return "FastDateParser[" + pattern + "," + locale + "," + timeZone.getID() + "]";
}
// Serializing
//-----------------------------------------------------------------------
/**
* Create the object after serialization. This implementation reinitializes the
* transient properties.
*
* @param in ObjectInputStream from which the object is being deserialized.
* @throws IOException if there is an IO issue.
* @throws ClassNotFoundException if a class cannot be found.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
init();
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
*/
@Override
public Object parseObject(String source) throws ParseException {
return parse(source);
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
*/
@Override
public Date parse(String source) throws ParseException {
Date date= parse(source, new ParsePosition(0));
if(date==null) {
throw new ParseException(source+" does not match "+parsePattern.pattern(), 0);
}
return date;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
*/
@Override
public Object parseObject(String source, ParsePosition pos) {
return parse(source, pos);
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
*/
@Override
public Date parse(String source, ParsePosition pos) {
int offset= pos.getIndex();
Matcher matcher= parsePattern.matcher(source.substring(offset));
if(!matcher.lookingAt()) {
return null;
}
// timing tests indicate getting new instance is 19% faster than cloning
Calendar cal= Calendar.getInstance(timeZone, locale);
cal.clear();
for(int i=0; i<strategies.length;) {
Strategy strategy= strategies[i++];
strategy.setCalendar(this, cal, matcher.group(i));
}
pos.setIndex(offset+matcher.end());
return cal.getTime();
}
// Support for strategies
//-----------------------------------------------------------------------
/**
* 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(StringBuilder regex, String value, boolean unquote) {
boolean wasWhite= false;
for(int i= 0; i<value.length(); ++i) {
char c= value.charAt(i);
if(Character.isWhitespace(c)) {
if(!wasWhite) {
wasWhite= true;
regex.append("\\s*+");
}
continue;
}
wasWhite= false;
switch(c) {
case '\'':
if(unquote) {
if(++i==value.length()) {
return regex;
}
c= value.charAt(i);
}
break;
case '?':
case '[':
case ']':
case '(':
case ')':
case '{':
case '}':
case '\\':
case '|':
case '*':
case '+':
case '^':
case '$':
case '.':
regex.append('\\');
}
regex.append(c);
}
return regex;
}
/**
* A class to store Key / Value pairs
*/
private static class KeyValue {
public String key;
public int value;
/**
* Construct a Key / Value pair
* @param key The key
* @param value The value
*/
public KeyValue(String key, int value) {
this.key= key;
this.value= value;
}
}
/**
* ignore case comparison of keys
*/
private static final Comparator<KeyValue> IGNORE_CASE_COMPARATOR = new Comparator<KeyValue> () {
public int compare(KeyValue left, KeyValue right) {
return left.key.compareToIgnoreCase(right.key);
}
};
/**
* Get the short and long values displayed for a field
* @param field The field of interest
* @return A sorted array of the field key / value pairs
*/
KeyValue[] getDisplayNames(int field) {
KeyValue[] fieldKeyValues= nameValues.get(field);
if(fieldKeyValues==null) {
DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
switch(field) {
case Calendar.ERA:
fieldKeyValues= createKeyValues(symbols.getEras(), null);
break;
case Calendar.DAY_OF_WEEK:
fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
break;
case Calendar.AM_PM:
fieldKeyValues= createKeyValues(symbols.getAmPmStrings(), null);
break;
case Calendar.MONTH:
fieldKeyValues= createKeyValues(symbols.getMonths(), symbols.getShortMonths());
break;
default:
throw new IllegalArgumentException("Invalid field value "+field);
}
KeyValue[] prior = nameValues.putIfAbsent(field, fieldKeyValues);
if(prior!=null) {
fieldKeyValues= prior;
}
}
return fieldKeyValues;
}
/**
* Create key / value pairs from keys
* @param longValues The allowable long names for a field
* @param shortValues The optional allowable short names for a field
* @return The sorted name / value pairs for the field
*/
private static KeyValue[] createKeyValues(String[] longValues, String[] shortValues) {
KeyValue[] fieldKeyValues= new KeyValue[count(longValues)+count(shortValues)];
copy(fieldKeyValues, copy(fieldKeyValues, 0, longValues), shortValues);
Arrays.sort(fieldKeyValues, IGNORE_CASE_COMPARATOR);
return fieldKeyValues;
}
/**
* Get a count of valid values in array. A valid value is of non-zero length.
* @param values The values to check. This parameter may be null
* @return The number of valid values
*/
private static int count(String[] values) {
int count= 0;
if(values!=null) {
for(String value : values) {
if(value.length()>0) {
++count;
}
}
}
return count;
}
/**
* Create key / value pairs from values
* @param fieldKeyValues The destination array
* @param offset The offset into the destination array
* @param values The values to use to create key / value pairs. This parameter may be null.
* @return The offset into the destination array
*/
private static int copy(KeyValue[] fieldKeyValues, int offset, String[] values) {
if(values!=null) {
for(int i= 0; i<values.length; ++i) {
String value= values[i];
if(value.length()>0) {
fieldKeyValues[offset++]= new KeyValue(value, i);
}
}
}
return offset;
}
/**
* Adjust dates to be within 80 years before and 20 years after instantiation
* @param twoDigitYear The year to adjust
* @return A value within -80 and +20 years from instantiation of this instance
*/
protected int adjustYear(int twoDigitYear) {
int trial= twoDigitYear + thisYear - thisYear%100;
if(trial < thisYear+20) {
return trial;
}
return trial-100;
}
/**
* Is the next field a number?
* @return true, if next field will be a number
*/
boolean isNextNumber() {
return nextStrategy!=null && nextStrategy.isNumber();
}
/**
* What is the width of the current field?
* @return The number of characters in the current format field
*/
int getFieldWidth() {
return currentFormatField.length();
}
/**
* A strategy to parse a single field from the parsing pattern
*/
private interface Strategy {
/**
* Is this field a number?
* @return true, if field is a number
*/
boolean isNumber();
/**
* Set the Calendar with the parsed field
* @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(FastDateParser parser, Calendar cal, String value);
/**
* Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code>
* which will accept this field
* @param parser The parser calling this strategy
* @param regex The <code>StringBuilder</code> to append to
* @return true, if this field will set the calendar;
* false, if this field is a constant value
*/
boolean addRegex(FastDateParser parser, StringBuilder regex);
}
/**
* A <code>Pattern</code> to parse the user supplied SimpleDateFormat pattern
*/
private static 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]++");
/**
* Obtain a Strategy given a field from a SimpleDateFormat pattern
* @param formatField A sub-sequence of the SimpleDateFormat pattern
* @return The Strategy that will handle parsing for the field
*/
private Strategy getStrategy(String formatField) {
switch(formatField.charAt(0)) {
case '\'':
if(formatField.length()>2) {
formatField= formatField.substring(1, formatField.length()-1);
}
default:
return new CopyQuotedStrategy(formatField);
case 'D':
return DAY_OF_YEAR_STRATEGY;
case 'E':
return DAY_OF_WEEK_STRATEGY;
case 'F':
return DAY_OF_WEEK_IN_MONTH_STRATEGY;
case 'G':
return ERA_STRATEGY;
case 'H':
return MODULO_HOUR_OF_DAY_STRATEGY;
case 'K':
return HOUR_STRATEGY;
case 'M':
return formatField.length()>=3 ?TEXT_MONTH_STRATEGY :NUMBER_MONTH_STRATEGY;
case 'S':
return MILLISECOND_STRATEGY;
case 'W':
return WEEK_OF_MONTH_STRATEGY;
case 'Z':
break;
case 'a':
return AM_PM_STRATEGY;
case 'd':
return DAY_OF_MONTH_STRATEGY;
case 'h':
return MODULO_HOUR_STRATEGY;
case 'k':
return HOUR_OF_DAY_STRATEGY;
case 'm':
return MINUTE_STRATEGY;
case 's':
return SECOND_STRATEGY;
case 'w':
return WEEK_OF_YEAR_STRATEGY;
case 'y':
return formatField.length()>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
case 'z':
break;
}
TimeZoneStrategy tzs= tzsCache.get(locale);
if(tzs==null) {
tzs= new TimeZoneStrategy(locale);
TimeZoneStrategy inCache= tzsCache.putIfAbsent(locale, tzs);
if(inCache!=null) {
return inCache;
}
}
return tzs;
}
/**
* A strategy that copies the static or quoted field in the parsing pattern
*/
private static class CopyQuotedStrategy implements Strategy {
private final String formatField;
/**
* Construct a Strategy that ensures the formatField has literal text
* @param formatField The literal text to match
*/
CopyQuotedStrategy(String formatField) {
this.formatField= formatField;
}
/**
* {@inheritDoc}
*/
public boolean isNumber() {
char c= formatField.charAt(0);
if(c=='\'') {
c= formatField.charAt(1);
}
return Character.isDigit(c);
}
/**
* {@inheritDoc}
*/
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
escapeRegex(regex, formatField, true);
return false;
}
/**
* {@inheritDoc}
*/
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
}
}
/**
* A strategy that handles a text field in the parsing pattern
*/
private static class TextStrategy implements Strategy {
private final int field;
/**
* Construct a Strategy that parses a Text field
* @param field The Calendar field
*/
TextStrategy(int field) {
this.field= field;
}
/**
* {@inheritDoc}
*/
public boolean isNumber() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
regex.append('(');
for(KeyValue textKeyValue : parser.getDisplayNames(field)) {
escapeRegex(regex, textKeyValue.key, false).append('|');
}
regex.setCharAt(regex.length()-1, ')');
return true;
}
/**
* {@inheritDoc}
*/
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
KeyValue[] textKeyValues= parser.getDisplayNames(field);
int idx= Arrays.binarySearch(textKeyValues, new KeyValue(value, -1), IGNORE_CASE_COMPARATOR);
if(idx<0) {
StringBuilder sb= new StringBuilder(value);
sb.append(" not in (");
for(KeyValue textKeyValue : textKeyValues) {
sb.append(textKeyValue.key).append(' ');
}
sb.setCharAt(sb.length()-1, ')');
throw new IllegalArgumentException(sb.toString());
}
cal.set(field, textKeyValues[idx].value);
}
}
/**
* A strategy that handles a number field in the parsing pattern
*/
private static class NumberStrategy implements Strategy {
protected final int field;
/**
* Construct a Strategy that parses a Number field
* @param field The Calendar field
*/
NumberStrategy(int field) {
this.field= field;
}
/**
* {@inheritDoc}
*/
public boolean isNumber() {
return true;
}
/**
* {@inheritDoc}
*/
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
if(parser.isNextNumber()) {
regex.append("(\\d{").append(parser.getFieldWidth()).append("}+)");
}
else {
regex.append("(\\d++)");
}
return true;
}
/**
* {@inheritDoc}
*/
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
cal.set(field, modify(Integer.parseInt(value)));
}
/**
* Make any modifications to parsed integer
* @param iValue The parsed integer
* @return The modified value
*/
public int modify(int iValue) {
return iValue;
}
}
private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
/**
* {@inheritDoc}
*/
@Override
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
int iValue= Integer.parseInt(value);
if(iValue<100) {
iValue= parser.adjustYear(iValue);
}
cal.set(Calendar.YEAR, iValue);
}
};
/**
* A strategy that handles a timezone field in the parsing pattern
*/
private static class TimeZoneStrategy implements Strategy {
final String validTimeZoneChars;
final SortedMap<String, TimeZone> tzNames= new TreeMap<String, TimeZone>(String.CASE_INSENSITIVE_ORDER);
/**
* Construct a Strategy that parses a TimeZone
* @param locale The Locale
*/
TimeZoneStrategy(Locale locale) {
for(String id : TimeZone.getAvailableIDs()) {
if(id.startsWith("GMT")) {
continue;
}
TimeZone tz= TimeZone.getTimeZone(id);
tzNames.put(tz.getDisplayName(false, TimeZone.SHORT, locale), tz);
tzNames.put(tz.getDisplayName(false, TimeZone.LONG, locale), tz);
if(tz.useDaylightTime()) {
tzNames.put(tz.getDisplayName(true, TimeZone.SHORT, locale), tz);
tzNames.put(tz.getDisplayName(true, TimeZone.LONG, locale), tz);
}
}
StringBuilder sb= new StringBuilder();
sb.append("(GMT[+\\-]\\d{0,1}\\d{2}|[+\\-]\\d{2}:?\\d{2}|");
for(String id : tzNames.keySet()) {
escapeRegex(sb, id, false).append('|');
}
sb.setCharAt(sb.length()-1, ')');
validTimeZoneChars= sb.toString();
}
/**
* {@inheritDoc}
*/
public boolean isNumber() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
regex.append(validTimeZoneChars);
return true;
}
/**
* {@inheritDoc}
*/
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
TimeZone tz;
if(value.charAt(0)=='+' || value.charAt(0)=='-') {
tz= TimeZone.getTimeZone("GMT"+value);
}
else if(value.startsWith("GMT")) {
tz= TimeZone.getTimeZone(value);
}
else {
tz= tzNames.get(value);
if(tz==null) {
throw new IllegalArgumentException(value + " is not a supported timezone name");
}
}
cal.setTimeZone(tz);
}
};
private static final Strategy ERA_STRATEGY = new TextStrategy(Calendar.ERA);
private static final Strategy DAY_OF_WEEK_STRATEGY = new TextStrategy(Calendar.DAY_OF_WEEK);
private static final Strategy AM_PM_STRATEGY = new TextStrategy(Calendar.AM_PM);
private static final Strategy TEXT_MONTH_STRATEGY = new TextStrategy(Calendar.MONTH);
private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
public int modify(int iValue) {
return iValue-1;
}
};
private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
private static final Strategy MODULO_HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
public int modify(int iValue) {
return iValue%24;
}
};
private static final Strategy MODULO_HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR) {
public int modify(int iValue) {
return iValue%12;
}
};
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ abstract class FormatCache<F extends Format> {
private final ConcurrentMap<MultipartKey, F> cInstanceCache
= new ConcurrentHashMap<MultipartKey, F>(7);
private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
= new ConcurrentHashMap<MultipartKey, String>(7);
/**
@ -120,6 +120,20 @@ abstract class FormatCache<F extends Format> {
if (locale == null) {
locale = Locale.getDefault();
}
String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
return getInstance(pattern, timeZone, locale);
}
/**
* <p>Gets a date/time format for the specified styles and locale.</p>
*
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
* @param locale The non-null locale of the desired format
* @return a localized standard date/time format
* @throws IllegalArgumentException if the Locale has no date/time pattern defined
*/
public static String getPatternForStyle(Integer dateStyle, Integer timeStyle, Locale locale) {
MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
String pattern = cDateTimeInstanceCache.get(key);
@ -147,8 +161,7 @@ abstract class FormatCache<F extends Format> {
throw new IllegalArgumentException("No date time pattern for locale: " + locale);
}
}
return getInstance(pattern, timeZone, locale);
return pattern;
}
// ----------------------------------------------------------------------
@ -172,12 +185,9 @@ abstract class FormatCache<F extends Format> {
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ( obj instanceof MultipartKey == false ) {
return false;
}
// Eliminate the usual boilerplate because
// this inner static class is only used in a generic ConcurrentHashMap
// which will not compare against other Object types
return Arrays.equals(keys, ((MultipartKey)obj).keys);
}

View File

@ -16,16 +16,25 @@
*/
package org.apache.commons.lang3.time;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import junit.framework.TestCase;
import org.apache.commons.lang3.SerializationUtils;
import org.junit.Test;
/**
* Unit tests {@link org.apache.commons.lang3.time.FastDateFormat}.
@ -33,19 +42,21 @@ import org.apache.commons.lang3.SerializationUtils;
* @since 2.0
* @version $Id$
*/
public class FastDateFormatTest extends TestCase {
public FastDateFormatTest(String name) {
super(name);
}
public class FastDateFormatTest {
/*
* Only the cache methods need to be tested here.
* The print methods are tested by {@link FastDateFormat_PrinterTest}
* and the parse methods are tested by {@link FastDateFormat_ParserTest}
*/
@Test
public void test_getInstance() {
FastDateFormat format1 = FastDateFormat.getInstance();
FastDateFormat format2 = FastDateFormat.getInstance();
assertSame(format1, format2);
assertEquals(new SimpleDateFormat().toPattern(), format1.getPattern());
}
@Test
public void test_getInstance_String() {
FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy");
@ -58,6 +69,7 @@ public class FastDateFormatTest extends TestCase {
assertEquals(TimeZone.getDefault(), format2.getTimeZone());
}
@Test
public void test_getInstance_String_TimeZone() {
Locale realDefaultLocale = Locale.getDefault();
TimeZone realDefaultZone = TimeZone.getDefault();
@ -86,6 +98,7 @@ public class FastDateFormatTest extends TestCase {
}
}
@Test
public void test_getInstance_String_Locale() {
Locale realDefaultLocale = Locale.getDefault();
try {
@ -103,6 +116,7 @@ public class FastDateFormatTest extends TestCase {
}
}
@Test
public void test_changeDefault_Locale_DateInstance() {
Locale realDefaultLocale = Locale.getDefault();
try {
@ -123,6 +137,7 @@ public class FastDateFormatTest extends TestCase {
}
}
@Test
public void test_changeDefault_Locale_DateTimeInstance() {
Locale realDefaultLocale = Locale.getDefault();
try {
@ -143,6 +158,7 @@ public class FastDateFormatTest extends TestCase {
}
}
@Test
public void test_getInstance_String_TimeZone_Locale() {
Locale realDefaultLocale = Locale.getDefault();
TimeZone realDefaultZone = TimeZone.getDefault();
@ -168,154 +184,128 @@ public class FastDateFormatTest extends TestCase {
Locale.setDefault(realDefaultLocale);
TimeZone.setDefault(realDefaultZone);
}
}
@Test
public void testCheckDefaults() {
FastDateFormat format = FastDateFormat.getInstance();
FastDateFormat medium = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT);
assertEquals(medium, format);
SimpleDateFormat sdf = new SimpleDateFormat();
assertEquals(sdf.toPattern(), format.getPattern());
assertEquals(Locale.getDefault(), format.getLocale());
assertEquals(TimeZone.getDefault(), format.getTimeZone());
}
public void testFormat() {
Locale realDefaultLocale = Locale.getDefault();
TimeZone realDefaultZone = TimeZone.getDefault();
try {
Locale.setDefault(Locale.US);
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
@Test
public void testCheckDifferingStyles() {
FastDateFormat shortShort = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT, Locale.US);
FastDateFormat shortLong = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.LONG, Locale.US);
FastDateFormat longShort = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT, Locale.US);
FastDateFormat longLong = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.LONG, Locale.US);
assertFalse(shortShort.equals(shortLong));
assertFalse(shortShort.equals(longShort));
assertFalse(shortShort.equals(longLong));
assertFalse(shortLong.equals(longShort));
assertFalse(shortLong.equals(longLong));
assertFalse(longShort.equals(longLong));
}
GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
Date date1 = cal1.getTime();
Date date2 = cal2.getTime();
long millis1 = date1.getTime();
long millis2 = date2.getTime();
@Test
public void testDateDefaults() {
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, Locale.CANADA),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
assertEquals(sdf.format(date1), fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
assertEquals("2003-01-10T15:33:20", fdf.format(millis1));
assertEquals("2003-07-10T09:00:00", fdf.format(date2));
assertEquals("2003-07-10T09:00:00", fdf.format(cal2));
assertEquals("2003-07-10T09:00:00", fdf.format(millis2));
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
}
fdf = FastDateFormat.getInstance("Z");
assertEquals("-0500", fdf.format(date1));
assertEquals("-0500", fdf.format(cal1));
assertEquals("-0500", fdf.format(millis1));
@Test
public void testTimeDefaults() {
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, Locale.CANADA),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
assertEquals("-0400", fdf.format(date2));
assertEquals("-0400", fdf.format(cal2));
assertEquals("-0400", fdf.format(millis2));
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
fdf = FastDateFormat.getInstance("ZZ");
assertEquals("-05:00", fdf.format(date1));
assertEquals("-05:00", fdf.format(cal1));
assertEquals("-05:00", fdf.format(millis1));
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
}
assertEquals("-04:00", fdf.format(date2));
assertEquals("-04:00", fdf.format(cal2));
assertEquals("-04:00", fdf.format(millis2));
@Test
public void testTimeDateDefaults() {
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, Locale.CANADA),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.CANADA));
String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M" +
" dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z";
fdf = FastDateFormat.getInstance(pattern);
sdf = new SimpleDateFormat(pattern);
// SDF bug fix starting with Java 7
assertEquals(sdf.format(date1).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date1));
assertEquals(sdf.format(date2).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date2));
} finally {
Locale.setDefault(realDefaultLocale);
TimeZone.setDefault(realDefaultZone);
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.getDefault()));
}
@Test
public void testParseSync() throws ParseException, InterruptedException {
final FastDateFormat formatter= FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
long sdfTime= measureTime(formatter, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z") {
private static final long serialVersionUID = 1L; // because SimpleDateFormat is serializable
@Override
public Object parseObject(String formattedDate) throws ParseException {
synchronized(this) {
return super.parse(formattedDate);
}
}
});
long fdfTime= measureTime(formatter, FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS Z"));
String times= "FastDateParser:"+fdfTime+" SimpleDateFormat:"+sdfTime;
System.out.println(times);
}
final static private int NTHREADS= 10;
final static private int NROUNDS= 10000;
private long measureTime(final Format formatter, final Format parser) throws ParseException, InterruptedException {
final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
final AtomicInteger failures= new AtomicInteger(0);
final AtomicLong totalElapsed= new AtomicLong(0);
for(int i= 0; i<NTHREADS; ++i) {
pool.submit(new Runnable() {
public void run() {
for(int i= 0; i<NROUNDS; ++i) {
try {
Date date= new Date();
String formattedDate= formatter.format(date);
long start= System.currentTimeMillis();
Object pd= parser.parseObject(formattedDate);
totalElapsed.addAndGet(System.currentTimeMillis()-start);
if(!date.equals(pd)) {
failures.incrementAndGet();
}
} catch (Exception e) {
failures.incrementAndGet();
e.printStackTrace();
}
}
}
});
}
}
/**
* Test case for {@link FastDateFormat#getDateInstance(int, java.util.Locale)}.
*/
public void testShortDateStyleWithLocales() {
Locale usLocale = Locale.US;
Locale swedishLocale = new Locale("sv", "SE");
Calendar cal = Calendar.getInstance();
cal.set(2004, 1, 3);
FastDateFormat fdf = FastDateFormat.getDateInstance(FastDateFormat.SHORT, usLocale);
assertEquals("2/3/04", fdf.format(cal));
fdf = FastDateFormat.getDateInstance(FastDateFormat.SHORT, swedishLocale);
assertEquals("2004-02-03", fdf.format(cal));
}
/**
* Tests that pre-1000AD years get padded with yyyy
*/
public void testLowYearPadding() {
Calendar cal = Calendar.getInstance();
FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/DD");
cal.set(1,0,1);
assertEquals("0001/01/01", format.format(cal));
cal.set(10,0,1);
assertEquals("0010/01/01", format.format(cal));
cal.set(100,0,1);
assertEquals("0100/01/01", format.format(cal));
cal.set(999,0,1);
assertEquals("0999/01/01", format.format(cal));
}
/**
* Show Bug #39410 is solved
*/
public void testMilleniumBug() {
Calendar cal = Calendar.getInstance();
FastDateFormat format = FastDateFormat.getInstance("dd.MM.yyyy");
cal.set(1000,0,1);
assertEquals("01.01.1000", format.format(cal));
}
/**
* testLowYearPadding showed that the date was buggy
* This test confirms it, getting 366 back as a date
*/
public void testSimpleDate() {
Calendar cal = Calendar.getInstance();
FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
cal.set(2004,11,31);
assertEquals("2004/12/31", format.format(cal));
cal.set(999,11,31);
assertEquals("0999/12/31", format.format(cal));
cal.set(1,2,2);
assertEquals("0001/03/02", format.format(cal));
}
public void testLang303() {
Calendar cal = Calendar.getInstance();
cal.set(2004,11,31);
FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
String output = format.format(cal);
format = (FastDateFormat) SerializationUtils.deserialize( SerializationUtils.serialize( format ) );
assertEquals(output, format.format(cal));
}
public void testLang538() {
// more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
// for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
cal.clear();
cal.set(2009, 9, 16, 8, 42, 16);
FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
}
public void testLang645() {
Locale locale = new Locale("sv", "SE");
Calendar cal = Calendar.getInstance();
cal.set(2010, 0, 1, 12, 0, 0);
Date d = cal.getTime();
FastDateFormat fdf = FastDateFormat.getInstance("EEEE', week 'ww", locale);
assertEquals("fredag, week 53", fdf.format(d));
pool.shutdown();
if(!pool.awaitTermination(20, TimeUnit.SECONDS)) {
pool.shutdownNow();
fail("did not complete tasks");
}
assertEquals(0, failures.get());
return totalElapsed.get();
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.util.Locale;
import java.util.TimeZone;
/**
* Unit tests for the parse methods of FastDateFormat
*
* @since 3.2
*/
public class FastDateFormat_ParserTest extends FastDateParserTest {
@Override
protected DateParser getInstance(String format, TimeZone timeZone, Locale locale) {
return FastDateFormat.getInstance(format, timeZone, locale);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.util.Locale;
import java.util.TimeZone;
/**
* Unit tests for the print methods of FastDateFormat
*
* @since 3.2
*/
public class FastDateFormat_PrinterTest extends FastDatePrinterTest {
@Override
protected DatePrinter getInstance(String format, TimeZone timeZone, Locale locale) {
return FastDateFormat.getInstance(format, timeZone, locale);
}
}

View File

@ -0,0 +1,364 @@
/*
* 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.commons.lang3.time;
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.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.SerializationUtils;
import org.junit.Test;
/**
* Unit tests {@link org.apache.commons.lang3.time.FastDateParser}.
*
* @since 3.2
*/
public class FastDateParserTest {
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 Locale SWEDEN = new Locale("sv", "SE");
DateParser getInstance(String format) {
return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
}
private DateParser getDateInstance(int dateStyle, Locale locale) {
return getInstance(FormatCache.getPatternForStyle(dateStyle, null, locale), TimeZone.getDefault(), Locale.getDefault());
}
private DateParser getInstance(String format, Locale locale) {
return getInstance(format, TimeZone.getDefault(), locale);
}
private DateParser getInstance(String format, TimeZone timeZone) {
return getInstance(format, timeZone, Locale.getDefault());
}
/**
* Override this method in derived tests to change the construction of instances
* @param format
* @param timeZone
* @param locale
* @return
*/
protected DateParser getInstance(String format, TimeZone timeZone, Locale locale) {
return new FastDateParser(format, timeZone, locale);
}
@Test
public void test_Equality_Hash() {
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)
};
Map<DateParser,Integer> map= new HashMap<DateParser,Integer>();
int i= 0;
for(DateParser parser:parsers) {
map.put(parser, i++);
}
i= 0;
for(DateParser parser:parsers) {
assertEquals(i++, (int)map.get(parser));
}
}
@Test
public void testParseZone() throws ParseException {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 6, 10, 16, 33, 20);
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, 1, 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, 1, 10, 15, 5, 6);
assertEquals(cal.getTime(), fdf.parse("2003-02-10T15:05:06.000 +0500"));
}
@Test
public void testParseLongShort() throws ParseException {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 1, 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);
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");
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 {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
DateParser h = getInstance("yyyy-MM-dd hh a mm:ss", NEW_YORK, Locale.US);
DateParser K = getInstance("yyyy-MM-dd KK a mm:ss", NEW_YORK, Locale.US);
DateParser k = getInstance("yyyy-MM-dd kk:mm:ss", NEW_YORK, Locale.US);
DateParser H = getInstance("yyyy-MM-dd HH:mm:ss", NEW_YORK, Locale.US);
cal.set(2010, 7, 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, 7, 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, 7, 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, 7, 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"));
}
@Test
public void testLocales() throws ParseException {
for(Locale locale : Locale.getAvailableLocales()) {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 1, 10);
try {
String longFormat= "GGGG/yyyy/MMMM/dddd/aaaa/EEEE/ZZZZ";
SimpleDateFormat sdf = new SimpleDateFormat(longFormat, locale);
DateParser fdf = getInstance(longFormat, locale);
checkParse(cal, sdf, fdf);
cal.set(Calendar.ERA, GregorianCalendar.BC);
checkParse(cal, sdf, fdf);
String shortFormat= "G/y/M/d/a/E/Z";
sdf = new SimpleDateFormat(shortFormat, locale);
fdf = getInstance(shortFormat, locale);
checkParse(cal, sdf, fdf);
cal.set(Calendar.ERA, GregorianCalendar.AD);
checkParse(cal, sdf, fdf);
}
catch(ParseException ex) {
// TODO: why do ja_JP_JP, hi_IN, th_TH, and th_TH_TH fail?
System.out.println("Locale "+locale+ " failed");
}
}
}
private void checkParse(Calendar cal, SimpleDateFormat sdf, DateParser fdf) throws ParseException {
String formattedDate= sdf.format(cal.getTime());
Date expectedTime = sdf.parse(formattedDate);
Date actualTime = fdf.parse(formattedDate);
assertEquals(expectedTime, actualTime);
}
@Test
public void testParseNumerics() throws ParseException {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 1, 10, 15, 33, 20);
cal.set(Calendar.MILLISECOND, 989);
DateParser fdf = getInstance("yyyyMMddHHmmssSSS", NEW_YORK, Locale.US);
assertEquals(cal.getTime(), fdf.parse("20030210153320989"));
}
@Test
public void testQuotes() throws ParseException {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 1, 10, 15, 33, 20);
cal.set(Calendar.MILLISECOND, 989);
DateParser fdf = getInstance("''yyyyMMdd'A''B'HHmmssSSS''", NEW_YORK, Locale.US);
assertEquals(cal.getTime(), fdf.parse("'20030210A'B153320989'"));
}
@Test
public void testDayOf() throws ParseException {
Calendar cal= Calendar.getInstance(NEW_YORK, Locale.US);
cal.clear();
cal.set(2003, 1, 10);
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#getDateInstance(int, java.util.Locale)}.
* @throws ParseException
*/
@Test
public void testShortDateStyleWithLocales() throws ParseException {
DateParser fdf = getDateInstance(FastDateFormat.SHORT, Locale.US);
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2004, 1, 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
*/
@Test
public void testLowYearPadding() throws ParseException {
DateParser parser = getInstance(YMD_SLASH);
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1,0,1);
assertEquals(cal.getTime(), parser.parse("0001/01/01"));
cal.set(10,0,1);
assertEquals(cal.getTime(), parser.parse("0010/01/01"));
cal.set(100,0,1);
assertEquals(cal.getTime(), parser.parse("0100/01/01"));
cal.set(999,0,1);
assertEquals(cal.getTime(), parser.parse("0999/01/01"));
}
/**
* @throws ParseException
*/
@Test
public void testMilleniumBug() throws ParseException {
DateParser parser = getInstance(DMY_DOT);
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1000,0,1);
assertEquals(cal.getTime(), parser.parse("01.01.1000"));
}
@Test
public void testLang303() throws ParseException {
DateParser parser = getInstance(YMD_SLASH);
Calendar cal = Calendar.getInstance();
cal.set(2004,11,31);
Date date = parser.parse("2004/11/31");
parser = (DateParser) SerializationUtils.deserialize( SerializationUtils.serialize( (Serializable)parser ) );
assertEquals(date, parser.parse("2004/11/31"));
}
@Test
public void testLang538() throws ParseException {
DateParser parser = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-8"));
cal.clear();
cal.set(2009, 9, 16, 8, 42, 16);
assertEquals(cal.getTime(), parser.parse("2009-10-16T16:42:16.000Z"));
}
@Test
public void testEquals() {
DateParser parser1= getInstance(YMD_SLASH);
DateParser parser2= getInstance(YMD_SLASH);
assertEquals(parser1, parser2);
assertEquals(parser1.hashCode(), parser2.hashCode());
assertFalse(parser1.equals(new Object()));
}
@Test
public void testToStringContainsName() {
DateParser parser= getInstance(YMD_SLASH);
assertTrue(parser.toString().startsWith("FastDate"));
}
@Test
public void testPatternMatches() {
DateParser parser= getInstance(yMdHmsSZ);
assertEquals(yMdHmsSZ, parser.getPattern());
}
@Test
public void testLocaleMatches() {
DateParser parser= getInstance(yMdHmsSZ, SWEDEN);
assertEquals(SWEDEN, parser.getLocale());
}
@Test
public void testTimeZoneMatches() {
DateParser parser= getInstance(yMdHmsSZ, REYKJAVIK);
assertEquals(REYKJAVIK, parser.getTimeZone());
}
}

View File

@ -0,0 +1,263 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang3.SerializationUtils;
import org.junit.Test;
/**
* Unit tests {@link org.apache.commons.lang3.time.FastDatePrinter}.
*
* @since 3.0
*/
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 Locale SWEDEN = new Locale("sv", "SE");
DatePrinter getInstance(String format) {
return getInstance(format, TimeZone.getDefault(), Locale.getDefault());
}
private DatePrinter getDateInstance(int dateStyle, Locale locale) {
return getInstance(FormatCache.getPatternForStyle(dateStyle, null, locale), TimeZone.getDefault(), Locale.getDefault());
}
private DatePrinter getInstance(String format, Locale locale) {
return getInstance(format, TimeZone.getDefault(), locale);
}
private DatePrinter getInstance(String format, TimeZone timeZone) {
return getInstance(format, timeZone, Locale.getDefault());
}
/**
* Override this method in derived tests to change the construction of instances
* @param format
* @param timeZone
* @param locale
* @return
*/
protected DatePrinter getInstance(String format, TimeZone timeZone, Locale locale) {
return new FastDatePrinter(format, timeZone, locale);
}
@Test
public void testFormat() {
Locale realDefaultLocale = Locale.getDefault();
TimeZone realDefaultZone = TimeZone.getDefault();
try {
Locale.setDefault(Locale.US);
TimeZone.setDefault(NEW_YORK);
GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
Date date1 = cal1.getTime();
Date date2 = cal2.getTime();
long millis1 = date1.getTime();
long millis2 = date2.getTime();
DatePrinter fdf = getInstance("yyyy-MM-dd'T'HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
assertEquals(sdf.format(date1), fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
assertEquals("2003-01-10T15:33:20", fdf.format(millis1));
assertEquals("2003-07-10T09:00:00", fdf.format(date2));
assertEquals("2003-07-10T09:00:00", fdf.format(cal2));
assertEquals("2003-07-10T09:00:00", fdf.format(millis2));
fdf = getInstance("Z");
assertEquals("-0500", fdf.format(date1));
assertEquals("-0500", fdf.format(cal1));
assertEquals("-0500", fdf.format(millis1));
assertEquals("-0400", fdf.format(date2));
assertEquals("-0400", fdf.format(cal2));
assertEquals("-0400", fdf.format(millis2));
fdf = getInstance("ZZ");
assertEquals("-05:00", fdf.format(date1));
assertEquals("-05:00", fdf.format(cal1));
assertEquals("-05:00", fdf.format(millis1));
assertEquals("-04:00", fdf.format(date2));
assertEquals("-04:00", fdf.format(cal2));
assertEquals("-04:00", fdf.format(millis2));
String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M" +
" dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z";
fdf = getInstance(pattern);
sdf = new SimpleDateFormat(pattern);
// SDF bug fix starting with Java 7
assertEquals(sdf.format(date1).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date1));
assertEquals(sdf.format(date2).replaceAll("2003 03 03 03", "2003 2003 03 2003"), fdf.format(date2));
} finally {
Locale.setDefault(realDefaultLocale);
TimeZone.setDefault(realDefaultZone);
}
}
/**
* Test case for {@link FastDatePrinter#getDateInstance(int, java.util.Locale)}.
*/
@Test
public void testShortDateStyleWithLocales() {
Locale usLocale = Locale.US;
Locale swedishLocale = new Locale("sv", "SE");
Calendar cal = Calendar.getInstance();
cal.set(2004, 1, 3);
DatePrinter fdf = getDateInstance(FastDateFormat.SHORT, usLocale);
assertEquals("2/3/04", fdf.format(cal));
fdf = getDateInstance(FastDateFormat.SHORT, swedishLocale);
assertEquals("2004-02-03", fdf.format(cal));
}
/**
* Tests that pre-1000AD years get padded with yyyy
*/
@Test
public void testLowYearPadding() {
Calendar cal = Calendar.getInstance();
DatePrinter format = getInstance(YYYY_MM_DD);
cal.set(1,0,1);
assertEquals("0001/01/01", format.format(cal));
cal.set(10,0,1);
assertEquals("0010/01/01", format.format(cal));
cal.set(100,0,1);
assertEquals("0100/01/01", format.format(cal));
cal.set(999,0,1);
assertEquals("0999/01/01", format.format(cal));
}
/**
* Show Bug #39410 is solved
*/
@Test
public void testMilleniumBug() {
Calendar cal = Calendar.getInstance();
DatePrinter format = getInstance("dd.MM.yyyy");
cal.set(1000,0,1);
assertEquals("01.01.1000", format.format(cal));
}
/**
* testLowYearPadding showed that the date was buggy
* This test confirms it, getting 366 back as a date
*/
@Test
public void testSimpleDate() {
Calendar cal = Calendar.getInstance();
DatePrinter format = getInstance(YYYY_MM_DD);
cal.set(2004,11,31);
assertEquals("2004/12/31", format.format(cal));
cal.set(999,11,31);
assertEquals("0999/12/31", format.format(cal));
cal.set(1,2,2);
assertEquals("0001/03/02", format.format(cal));
}
@Test
public void testLang303() {
Calendar cal = Calendar.getInstance();
cal.set(2004,11,31);
DatePrinter format = getInstance(YYYY_MM_DD);
String output = format.format(cal);
format = (DatePrinter) SerializationUtils.deserialize( SerializationUtils.serialize( (Serializable)format ) );
assertEquals(output, format.format(cal));
}
@Test
public void testLang538() {
// more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
// for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
cal.clear();
cal.set(2009, 9, 16, 8, 42, 16);
DatePrinter format = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
}
@Test
public void testLang645() {
Locale locale = new Locale("sv", "SE");
Calendar cal = Calendar.getInstance();
cal.set(2010, 0, 1, 12, 0, 0);
Date d = cal.getTime();
DatePrinter fdf = getInstance("EEEE', week 'ww", locale);
assertEquals("fredag, week 53", fdf.format(d));
}
@Test
public void testEquals() {
DatePrinter printer1= getInstance(YYYY_MM_DD);
DatePrinter printer2= getInstance(YYYY_MM_DD);
assertEquals(printer1, printer2);
assertEquals(printer1.hashCode(), printer2.hashCode());
assertFalse(printer1.equals(new Object()));
}
@Test
public void testToStringContainsName() {
DatePrinter printer= getInstance(YYYY_MM_DD);
assertTrue(printer.toString().startsWith("FastDate"));
}
@Test
public void testPatternMatches() {
DatePrinter printer= getInstance(YYYY_MM_DD);
assertEquals(YYYY_MM_DD, printer.getPattern());
}
@Test
public void testLocaleMatches() {
DatePrinter printer= getInstance(YYYY_MM_DD, SWEDEN);
assertEquals(SWEDEN, printer.getLocale());
}
@Test
public void testTimeZoneMatches() {
DatePrinter printer= getInstance(YYYY_MM_DD, NEW_YORK);
assertEquals(NEW_YORK, printer.getTimeZone());
}
}