org.sqlite.date.FastDateParser Maven / Gradle / Ivy
/*
* 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.sqlite.date;
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.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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;
/**
* FastDateParser is a fast and thread-safe version of {@link java.text.SimpleDateFormat}.
*
* To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone,
* Locale)} or another variation of the factory methods of {@link FastDateFormat}.
*
*
Since FastDateParser is thread safe, you can use a static member instance:
* private static final DateParser DATE_PARSER = FastDateFormat.getInstance("yyyy-MM-dd");
*
*
*
This class can be used as a direct replacement for SimpleDateFormat
in most
* parsing situations. This class is especially useful in multi-threaded server environments.
* SimpleDateFormat
is not thread-safe in any JDK version, nor will it be as Sun has closed
* the bug/RFE.
*
*
Only parsing is supported by this class, but all patterns are compatible with
* SimpleDateFormat.
*
*
The class operates in lenient mode, so for example a time of 90 minutes is treated as 1 hour
* 30 minutes.
*
*
Timing tests indicate this class is as about as fast as SimpleDateFormat in single thread
* applications and about 25% faster in multi-thread applications.
*
* @version $Id$
* @since 3.2
* @see FastDatePrinter
*/
public class FastDateParser implements DateParser, Serializable {
/**
* Required for serialization support.
*
* @see java.io.Serializable
*/
private static final long serialVersionUID = 2L;
static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP");
// defining fields
private final String pattern;
private final TimeZone timeZone;
private final Locale locale;
private final int century;
private final int startYear;
// derived fields
private transient Pattern parsePattern;
private transient Strategy[] strategies;
// dynamic fields to communicate with Strategy
private transient String currentFormatField;
private transient Strategy nextStrategy;
/**
* Constructs a new FastDateParser. 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
* @param locale non-null locale
*/
protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
this(pattern, timeZone, locale, null);
}
/**
* Constructs a new FastDateParser.
*
* @param pattern non-null {@link java.text.SimpleDateFormat} compatible pattern
* @param timeZone non-null time zone to use
* @param locale non-null locale
* @param centuryStart The start of the century for 2 digit year parsing
* @since 3.3
*/
protected FastDateParser(
final String pattern,
final TimeZone timeZone,
final Locale locale,
final Date centuryStart) {
this.pattern = pattern;
this.timeZone = timeZone;
this.locale = locale;
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
int centuryStartYear;
if (centuryStart != null) {
definingCalendar.setTime(centuryStart);
centuryStartYear = definingCalendar.get(Calendar.YEAR);
} else if (locale.equals(JAPANESE_IMPERIAL)) {
centuryStartYear = 0;
} else {
// from 80 years ago to 20 years from now
definingCalendar.setTime(new Date());
centuryStartYear = definingCalendar.get(Calendar.YEAR) - 80;
}
century = centuryStartYear / 100 * 100;
startYear = centuryStartYear - century;
init(definingCalendar);
}
/**
* Initialize derived fields from defining fields. This is called from constructor and from
* readObject (de-serialization)
*
* @param definingCalendar the {@link java.util.Calendar} instance used to initialize this
* FastDateParser
*/
private void init(final Calendar definingCalendar) {
final StringBuilder regex = new StringBuilder();
final List collector = new ArrayList();
final Matcher patternMatcher = formatPattern.matcher(pattern);
if (!patternMatcher.lookingAt()) {
throw new IllegalArgumentException(
"Illegal pattern character '"
+ pattern.charAt(patternMatcher.regionStart())
+ "'");
}
currentFormatField = patternMatcher.group();
Strategy currentStrategy = getStrategy(currentFormatField, definingCalendar);
for (; ; ) {
patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
if (!patternMatcher.lookingAt()) {
nextStrategy = null;
break;
}
final String nextFormatField = patternMatcher.group();
nextStrategy = getStrategy(nextFormatField, definingCalendar);
if (currentStrategy.addRegex(this, regex)) {
collector.add(currentStrategy);
}
currentFormatField = nextFormatField;
currentStrategy = nextStrategy;
}
if (patternMatcher.regionStart() != patternMatcher.regionEnd()) {
throw new IllegalArgumentException(
"Failed to parse \""
+ pattern
+ "\" ; gave up at index "
+ patternMatcher.regionStart());
}
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()
*/
public String getPattern() {
return pattern;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#getTimeZone()
*/
public TimeZone getTimeZone() {
return timeZone;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#getLocale()
*/
public Locale getLocale() {
return locale;
}
/**
* Returns the generated pattern (for testing purposes).
*
* @return the generated pattern
*/
Pattern getParsePattern() {
return parsePattern;
}
// Basics
// -----------------------------------------------------------------------
/**
* Compare another object for equality with this object.
*
* @param obj the object to compare to
* @return true
if equal to this instance
*/
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof FastDateParser)) {
return false;
}
final FastDateParser other = (FastDateParser) obj;
return pattern.equals(other.pattern)
&& timeZone.equals(other.timeZone)
&& locale.equals(other.locale);
}
/**
* Return a hashcode compatible with equals.
*
* @return a hashcode compatible with equals
*/
@Override
public int hashCode() {
return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
}
/**
* Get a string version of this formatter.
*
* @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(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
init(definingCalendar);
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
*/
public Object parseObject(final String source) throws ParseException {
return parse(source);
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
*/
public Date parse(final String source) throws ParseException {
String normalizedSource = source.length() == 19 ? (source + ".000") : source;
final Date date = parse(normalizedSource, new ParsePosition(0));
if (date == null) {
// Add a note re supported date range
if (locale.equals(JAPANESE_IMPERIAL)) {
throw new ParseException(
"(The "
+ locale
+ " locale does not support dates before 1868 AD)\n"
+ "Unparseable date: \""
+ normalizedSource
+ "\" does not match "
+ parsePattern.pattern(),
0);
}
throw new ParseException(
"Unparseable date: \""
+ normalizedSource
+ "\" does not match "
+ parsePattern.pattern(),
0);
}
return date;
}
/* (non-Javadoc)
* @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
*/
public Object parseObject(final String source, final ParsePosition pos) {
return parse(source, pos);
}
/**
* This implementation updates the ParsePosition if the parse succeeeds. However, unlike the
* method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} it is not able to set
* the error Index - i.e. {@link ParsePosition#getErrorIndex()} - if the parse fails.
*
* To determine if the parse has succeeded, the caller must check if the current parse
* position given by {@link ParsePosition#getIndex()} has been updated. If the input buffer has
* been fully parsed, then the index will point to just after the end of the input buffer.
*
*
See org.apache.commons.lang3.time.DateParser#parse(java.lang.String,
* java.text.ParsePosition) {@inheritDoc}
*/
public Date parse(final String source, final ParsePosition pos) {
final int offset = pos.getIndex();
final Matcher matcher = parsePattern.matcher(source.substring(offset));
if (!matcher.lookingAt()) {
return null;
}
// timing tests indicate getting new instance is 19% faster than cloning
final Calendar cal = Calendar.getInstance(timeZone, locale);
cal.clear();
for (int i = 0; i < strategies.length; ) {
final 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 StringBuilder
*/
private static StringBuilder escapeRegex(
final StringBuilder regex, final String value, final boolean unquote) {
regex.append("\\Q");
for (int i = 0; i < value.length(); ++i) {
char c = value.charAt(i);
switch (c) {
case '\'':
if (unquote) {
if (++i == value.length()) {
return regex;
}
c = value.charAt(i);
}
break;
case '\\':
if (++i == value.length()) {
break;
}
/*
* If we have found \E, we replace it with \E\\E\Q, i.e. we stop the quoting,
* quote the \ in \E, then restart the quoting.
*
* Otherwise we just output the two characters.
* In each case the initial \ needs to be output and the final char is done at the end
*/
regex.append(c); // we always want the original \
c = value.charAt(i); // Is it followed by E ?
if (c == 'E') { // \E detected
regex.append("E\\\\E\\"); // see comment above
c = 'Q'; // appended below
}
break;
default:
break;
}
regex.append(c);
}
regex.append("\\E");
return regex;
}
/**
* Get the short and long values displayed for a field
*
* @param field The field of interest
* @param definingCalendar The calendar to obtain the short and long values
* @param locale The locale of display names
* @return A Map of the field key / value pairs
*/
private static Map getDisplayNames(
final int field, final Calendar definingCalendar, final Locale locale) {
return definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
}
/**
* Adjust dates to be within appropriate century
*
* @param twoDigitYear The year to adjust
* @return A value between centuryStart(inclusive) to centuryStart+100(exclusive)
*/
private int adjustYear(final int twoDigitYear) {
final int trial = century + twoDigitYear;
return twoDigitYear >= startYear ? trial : 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 abstract static class Strategy {
/**
* Is this field a number? The default implementation returns false.
*
* @return true, if field is a number
*/
boolean isNumber() {
return false;
}
/**
* Set the Calendar with the parsed field.
*
* The default implementation does nothing.
*
* @param parser The parser calling this strategy
* @param cal The Calendar
to set
* @param value The parsed field to translate and set in cal
*/
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {}
/**
* Generate a Pattern
regular expression to the StringBuilder
* which will accept this field
*
* @param parser The parser calling this strategy
* @param regex The StringBuilder
to append to
* @return true, if this field will set the calendar; false, if this field is a constant
* value
*/
abstract boolean addRegex(FastDateParser parser, StringBuilder regex);
}
/** A Pattern
to parse the user supplied SimpleDateFormat pattern */
private static final Pattern formatPattern =
Pattern.compile(
"D+|E+|F+|G+|H+|K+|M+|S+|W+|X+|Z+|a+|d+|h+|k+|m+|s+|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
* @param definingCalendar The calendar to obtain the short and long values
* @return The Strategy that will handle parsing for the field
*/
private Strategy getStrategy(final String formatField, final Calendar definingCalendar) {
switch (formatField.charAt(0)) {
case '\'':
if (formatField.length() > 2) {
return new CopyQuotedStrategy(
formatField.substring(1, formatField.length() - 1));
}
// $FALL-THROUGH$
default:
return new CopyQuotedStrategy(formatField);
case 'D':
return DAY_OF_YEAR_STRATEGY;
case 'E':
return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
case 'F':
return DAY_OF_WEEK_IN_MONTH_STRATEGY;
case 'G':
return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
case 'H': // Hour in day (0-23)
return HOUR_OF_DAY_STRATEGY;
case 'K': // Hour in am/pm (0-11)
return HOUR_STRATEGY;
case 'M':
return formatField.length() >= 3
? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar)
: NUMBER_MONTH_STRATEGY;
case 'S':
return MILLISECOND_STRATEGY;
case 'W':
return WEEK_OF_MONTH_STRATEGY;
case 'a':
return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
case 'd':
return DAY_OF_MONTH_STRATEGY;
case 'h': // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
return HOUR12_STRATEGY;
case 'k': // Hour in day (1-24), i.e. midnight is 24, not 0
return HOUR24_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 'X':
return ISO8601TimeZoneStrategy.getStrategy(formatField.length());
case 'Z':
if (formatField.equals("ZZ")) {
return ISO_8601_STRATEGY;
}
// $FALL-THROUGH$
case 'z':
return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
}
}
@SuppressWarnings("unchecked") // OK because we are creating an array with no entries
private static final ConcurrentMap[] caches =
new ConcurrentMap[Calendar.FIELD_COUNT];
/**
* Get a cache of Strategies for a particular field
*
* @param field The Calendar field
* @return a cache of Locale to Strategy
*/
private static ConcurrentMap getCache(final int field) {
synchronized (caches) {
if (caches[field] == null) {
caches[field] = new ConcurrentHashMap(3);
}
return caches[field];
}
}
/**
* Construct a Strategy that parses a Text field
*
* @param field The Calendar field
* @param definingCalendar The calendar to obtain the short and long values
* @return a TextStrategy for the field and Locale
*/
private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
final ConcurrentMap cache = getCache(field);
Strategy strategy = cache.get(locale);
if (strategy == null) {
strategy =
field == Calendar.ZONE_OFFSET
? new TimeZoneStrategy(locale)
: new CaseInsensitiveTextStrategy(field, definingCalendar, locale);
final Strategy inCache = cache.putIfAbsent(locale, strategy);
if (inCache != null) {
return inCache;
}
}
return strategy;
}
/** A strategy that copies the static or quoted field in the parsing pattern */
private static class CopyQuotedStrategy extends Strategy {
private final String formatField;
/**
* Construct a Strategy that ensures the formatField has literal text
*
* @param formatField The literal text to match
*/
CopyQuotedStrategy(final String formatField) {
this.formatField = formatField;
}
/** {@inheritDoc} */
@Override
boolean isNumber() {
char c = formatField.charAt(0);
if (c == '\'') {
c = formatField.charAt(1);
}
return Character.isDigit(c);
}
/** {@inheritDoc} */
@Override
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
escapeRegex(regex, formatField, true);
return false;
}
}
/** A strategy that handles a text field in the parsing pattern */
private static class CaseInsensitiveTextStrategy extends Strategy {
private final int field;
private final Locale locale;
private final Map lKeyValues;
/**
* Construct a Strategy that parses a Text field
*
* @param field The Calendar field
* @param definingCalendar The Calendar to use
* @param locale The Locale to use
*/
CaseInsensitiveTextStrategy(
final int field, final Calendar definingCalendar, final Locale locale) {
this.field = field;
this.locale = locale;
final Map keyValues = getDisplayNames(field, definingCalendar, locale);
this.lKeyValues = new HashMap();
for (final Map.Entry entry : keyValues.entrySet()) {
lKeyValues.put(entry.getKey().toLowerCase(locale), entry.getValue());
}
}
/** {@inheritDoc} */
@Override
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
regex.append("((?iu)");
for (final String textKeyValue : lKeyValues.keySet()) {
escapeRegex(regex, textKeyValue, false).append('|');
}
regex.setCharAt(regex.length() - 1, ')');
return true;
}
/** {@inheritDoc} */
@Override
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
final Integer iVal = lKeyValues.get(value.toLowerCase(locale));
if (iVal == null) {
final StringBuilder sb = new StringBuilder(value);
sb.append(" not in (");
for (final String textKeyValue : lKeyValues.keySet()) {
sb.append(textKeyValue).append(' ');
}
sb.setCharAt(sb.length() - 1, ')');
throw new IllegalArgumentException(sb.toString());
}
cal.set(field, iVal.intValue());
}
}
/** A strategy that handles a number field in the parsing pattern */
private static class NumberStrategy extends Strategy {
private final int field;
/**
* Construct a Strategy that parses a Number field
*
* @param field The Calendar field
*/
NumberStrategy(final int field) {
this.field = field;
}
/** {@inheritDoc} */
@Override
boolean isNumber() {
return true;
}
/** {@inheritDoc} */
@Override
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
// See LANG-954: We use {Nd} rather than {IsNd} because Android does not support the Is
// prefix
if (parser.isNextNumber()) {
regex.append("(\\p{Nd}{").append(parser.getFieldWidth()).append("}+)");
} else {
regex.append("(\\p{Nd}++)");
}
return true;
}
/** {@inheritDoc} */
@Override
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
cal.set(field, modify(Integer.parseInt(value)));
}
/**
* Make any modifications to parsed integer
*
* @param iValue The parsed integer
* @return The modified value
*/
int modify(final int iValue) {
return iValue;
}
}
private static final Strategy ABBREVIATED_YEAR_STRATEGY =
new NumberStrategy(Calendar.YEAR) {
/** {@inheritDoc} */
@Override
void setCalendar(
final FastDateParser parser, final Calendar cal, final String value) {
int iValue = Integer.parseInt(value);
if (iValue < 100) {
iValue = parser.adjustYear(iValue);
}
cal.set(Calendar.YEAR, iValue);
}
};
/** A strategy that handles a timezone field in the parsing pattern */
private static class TimeZoneStrategy extends Strategy {
private final String validTimeZoneChars;
private final SortedMap tzNames =
new TreeMap(String.CASE_INSENSITIVE_ORDER);
/** Index of zone id */
private static final int ID = 0;
/** Index of the long name of zone in standard time */
private static final int LONG_STD = 1;
/** Index of the short name of zone in standard time */
private static final int SHORT_STD = 2;
/** Index of the long name of zone in daylight saving time */
private static final int LONG_DST = 3;
/** Index of the short name of zone in daylight saving time */
private static final int SHORT_DST = 4;
/**
* Construct a Strategy that parses a TimeZone
*
* @param locale The Locale
*/
TimeZoneStrategy(final Locale locale) {
final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
for (final String[] zone : zones) {
if (zone[ID].startsWith("GMT")) {
continue;
}
final TimeZone tz = TimeZone.getTimeZone(zone[ID]);
if (!tzNames.containsKey(zone[LONG_STD])) {
tzNames.put(zone[LONG_STD], tz);
}
if (!tzNames.containsKey(zone[SHORT_STD])) {
tzNames.put(zone[SHORT_STD], tz);
}
if (tz.useDaylightTime()) {
if (!tzNames.containsKey(zone[LONG_DST])) {
tzNames.put(zone[LONG_DST], tz);
}
if (!tzNames.containsKey(zone[SHORT_DST])) {
tzNames.put(zone[SHORT_DST], tz);
}
}
}
final StringBuilder sb = new StringBuilder();
sb.append("(GMT[+-]\\d{1,2}:\\d{2}").append('|');
sb.append("[+-]\\d{4}").append('|');
for (final String id : tzNames.keySet()) {
escapeRegex(sb, id, false).append('|');
}
sb.setCharAt(sb.length() - 1, ')');
validTimeZoneChars = sb.toString();
}
/** {@inheritDoc} */
@Override
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
regex.append(validTimeZoneChars);
return true;
}
/** {@inheritDoc} */
@Override
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
TimeZone tz;
if (value.charAt(0) == '+' || value.charAt(0) == '-') {
tz = TimeZone.getTimeZone("GMT" + value);
} 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 class ISO8601TimeZoneStrategy extends Strategy {
// Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
private final String pattern;
/**
* Construct a Strategy that parses a TimeZone
*
* @param pattern The Pattern
*/
ISO8601TimeZoneStrategy(String pattern) {
this.pattern = pattern;
}
/** {@inheritDoc} */
@Override
boolean addRegex(FastDateParser parser, StringBuilder regex) {
regex.append(pattern);
return true;
}
/** {@inheritDoc} */
@Override
void setCalendar(FastDateParser parser, Calendar cal, String value) {
if (value.equals("Z")) {
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
} else {
cal.setTimeZone(TimeZone.getTimeZone("GMT" + value));
}
}
private static final Strategy ISO_8601_1_STRATEGY =
new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");
private static final Strategy ISO_8601_2_STRATEGY =
new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))");
private static final Strategy ISO_8601_3_STRATEGY =
new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))");
/**
* Factory method for ISO8601TimeZoneStrategies.
*
* @param tokenLen a token indicating the length of the TimeZone String to be formatted.
* @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code
* tokenLen}. If no such strategy exists, an IllegalArgumentException will be thrown.
*/
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) {
@Override
int modify(final int iValue) {
return iValue - 1;
}
};
private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
private static final Strategy WEEK_OF_MONTH_STRATEGY =
new NumberStrategy(Calendar.WEEK_OF_MONTH);
private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY =
new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
private static final Strategy HOUR24_OF_DAY_STRATEGY =
new NumberStrategy(Calendar.HOUR_OF_DAY) {
@Override
int modify(final int iValue) {
return iValue == 24 ? 0 : iValue;
}
};
private static final Strategy HOUR12_STRATEGY =
new NumberStrategy(Calendar.HOUR) {
@Override
int modify(final int iValue) {
return iValue == 12 ? 0 : iValue;
}
};
private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
private static final Strategy ISO_8601_STRATEGY =
new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::?\\d{2})?))");
}