com.fasterxml.jackson.databind.util.StdDateFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
package com.fasterxml.jackson.databind.util;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
import com.fasterxml.jackson.core.io.NumberInput;
/**
* Default {@link DateFormat} implementation used by standard Date
* serializers and deserializers. For serialization defaults to using
* an ISO-8601 compliant format (format String "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
* and for deserialization, both ISO-8601 and RFC-1123.
*/
@SuppressWarnings("serial")
public class StdDateFormat
extends DateFormat
{
/* TODO !!! 24-Nov-2009, tatu: Need to rewrite this class:
* JDK date parsing is awfully brittle, and ISO-8601 is quite
* permissive. The two don't mix, need to write a better one.
*/
// Note: [JACKSON-697] is the issue for rewrite
/**
* Defines a commonly used date format that conforms
* to ISO-8601 date formatting standard, when it includes basic undecorated
* timezone definition
*/
protected final static String DATE_FORMAT_STR_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
/**
* Same as 'regular' 8601, but handles 'Z' as an alias for "+0000"
* (or "GMT")
*/
protected final static String DATE_FORMAT_STR_ISO8601_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
/**
* ISO-8601 with just the Date part, no time
*/
protected final static String DATE_FORMAT_STR_PLAIN = "yyyy-MM-dd";
/**
* This constant defines the date format specified by
* RFC 1123 / RFC 822.
*/
protected final static String DATE_FORMAT_STR_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* For error messages we'll also need a list of all formats.
*/
protected final static String[] ALL_FORMATS = new String[] {
DATE_FORMAT_STR_ISO8601,
DATE_FORMAT_STR_ISO8601_Z,
DATE_FORMAT_STR_RFC1123,
DATE_FORMAT_STR_PLAIN
};
/**
* By default we use GMT for everything.
*/
private final static TimeZone DEFAULT_TIMEZONE;
static {
DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT");
}
protected final static DateFormat DATE_FORMAT_RFC1123;
protected final static DateFormat DATE_FORMAT_ISO8601;
protected final static DateFormat DATE_FORMAT_ISO8601_Z;
protected final static DateFormat DATE_FORMAT_PLAIN;
/* Let's construct "blueprint" date format instances: can not be used
* as is, due to thread-safety issues, but can be used for constructing
* actual instances more cheaply (avoids re-parsing).
*/
static {
/* Another important thing: let's force use of GMT for
* baseline DataFormat objects
*/
DATE_FORMAT_RFC1123 = new SimpleDateFormat(DATE_FORMAT_STR_RFC1123, Locale.US);
DATE_FORMAT_RFC1123.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_ISO8601 = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601);
DATE_FORMAT_ISO8601.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_ISO8601_Z = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601_Z);
DATE_FORMAT_ISO8601_Z.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_PLAIN = new SimpleDateFormat(DATE_FORMAT_STR_PLAIN);
DATE_FORMAT_PLAIN.setTimeZone(DEFAULT_TIMEZONE);
}
/**
* A singleton instance can be used for cloning purposes.
*/
public final static StdDateFormat instance = new StdDateFormat();
/**
* Caller may want to explicitly override timezone to use; if so,
* we will have non-null value here.
*/
protected transient TimeZone _timezone;
protected transient DateFormat _formatRFC1123;
protected transient DateFormat _formatISO8601;
protected transient DateFormat _formatISO8601_z;
protected transient DateFormat _formatPlain;
/*
/**********************************************************
/* Life cycle, accessing singleton "standard" formats
/**********************************************************
*/
public StdDateFormat() { }
public StdDateFormat(TimeZone tz) {
_timezone = tz;
}
public static TimeZone getDefaultTimeZone() {
return DEFAULT_TIMEZONE;
}
/**
* Method used for creating a new instance with specified timezone;
* if no timezone specified, defaults to the default timezone (UTC).
*/
public StdDateFormat withTimeZone(TimeZone tz) {
if (tz == null) {
tz = DEFAULT_TIMEZONE;
}
return new StdDateFormat(tz);
}
@Override
public StdDateFormat clone() {
/* Although there is that much state to share, we do need to
* orchestrate a bit, mostly since timezones may be changed
*/
return new StdDateFormat();
}
/**
* Method for getting the globally shared DateFormat instance
* that uses GMT timezone and can handle simple ISO-8601
* compliant date format.
*/
public static DateFormat getBlueprintISO8601Format() {
return DATE_FORMAT_ISO8601;
}
/**
* Method for getting a non-shared DateFormat instance
* that uses specified timezone and can handle simple ISO-8601
* compliant date format.
*/
public static DateFormat getISO8601Format(TimeZone tz) {
return _cloneFormat(DATE_FORMAT_ISO8601, tz);
}
/**
* Method for getting the globally shared DateFormat instance
* that uses GMT timezone and can handle RFC-1123
* compliant date format.
*/
public static DateFormat getBlueprintRFC1123Format() {
return DATE_FORMAT_RFC1123;
}
/**
* Method for getting a non-shared DateFormat instance
* that uses specific timezone and can handle RFC-1123
* compliant date format.
*/
public static DateFormat getRFC1123Format(TimeZone tz) {
return _cloneFormat(DATE_FORMAT_RFC1123, tz);
}
/*
/**********************************************************
/* Public API
/**********************************************************
*/
@Override
public void setTimeZone(TimeZone tz)
{
/* DateFormats are timezone-specific (via Calendar contained),
* so need to reset instances if timezone changes:
*/
if (tz != _timezone) {
_formatRFC1123 = null;
_formatISO8601 = null;
_formatISO8601_z = null;
_formatPlain = null;
_timezone = tz;
}
}
@Override
public Date parse(String dateStr) throws ParseException
{
dateStr = dateStr.trim();
ParsePosition pos = new ParsePosition(0);
Date result = parse(dateStr, pos);
if (result != null) {
return result;
}
StringBuilder sb = new StringBuilder();
for (String f : ALL_FORMATS) {
if (sb.length() > 0) {
sb.append("\", \"");
} else {
sb.append('"');
}
sb.append(f);
}
sb.append('"');
throw new ParseException
(String.format("Can not parse date \"%s\": not compatible with any of standard forms (%s)",
dateStr, sb.toString()), pos.getErrorIndex());
}
@Override
public Date parse(String dateStr, ParsePosition pos)
{
if (looksLikeISO8601(dateStr)) { // also includes "plain"
return parseAsISO8601(dateStr, pos);
}
/* 14-Feb-2010, tatu: As per [JACKSON-236], better also
* consider "stringified" simple time stamp
*/
int i = dateStr.length();
while (--i >= 0) {
char ch = dateStr.charAt(i);
if (ch < '0' || ch > '9') {
// 07-Aug-2013, tatu: And #267 points out that negative numbers should also work
if (i > 0 || ch != '-') {
break;
}
}
}
if (i < 0) { // all digits
// let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
if (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false)) {
return new Date(Long.parseLong(dateStr));
}
}
// Otherwise, fall back to using RFC 1123
return parseAsRFC1123(dateStr, pos);
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition)
{
if (_formatISO8601 == null) {
_formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601);
}
return _formatISO8601.format(date, toAppendTo, fieldPosition);
}
/*
/**********************************************************
/* Std overrides
/**********************************************************
*/
@Override
public String toString() {
String str = "DateFormat "+getClass().getName();
TimeZone tz = _timezone;
if (tz != null) {
str += " (timezone: "+tz+")";
}
return str;
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
/**
* Overridable helper method used to figure out which of supported
* formats is the likeliest match.
*/
protected boolean looksLikeISO8601(String dateStr)
{
if (dateStr.length() >= 5
&& Character.isDigit(dateStr.charAt(0))
&& Character.isDigit(dateStr.charAt(3))
&& dateStr.charAt(4) == '-'
) {
return true;
}
return false;
}
protected Date parseAsISO8601(String dateStr, ParsePosition pos)
{
/* 21-May-2009, tatu: DateFormat has very strict handling of
* timezone modifiers for ISO-8601. So we need to do some scrubbing.
*/
/* First: do we have "zulu" format ('Z' == "GMT")? If yes, that's
* quite simple because we already set date format timezone to be
* GMT, and hence can just strip out 'Z' altogether
*/
int len = dateStr.length();
char c = dateStr.charAt(len-1);
DateFormat df;
// [JACKSON-200]: need to support "plain" date...
if (len <= 10 && Character.isDigit(c)) {
df = _formatPlain;
if (df == null) {
df = _formatPlain = _cloneFormat(DATE_FORMAT_PLAIN);
}
} else if (c == 'Z') {
df = _formatISO8601_z;
if (df == null) {
df = _formatISO8601_z = _cloneFormat(DATE_FORMAT_ISO8601_Z);
}
// [JACKSON-334]: may be missing milliseconds... if so, add
if (dateStr.charAt(len-4) == ':') {
StringBuilder sb = new StringBuilder(dateStr);
sb.insert(len-1, ".000");
dateStr = sb.toString();
}
} else {
// Let's see if we have timezone indicator or not...
if (hasTimeZone(dateStr)) {
c = dateStr.charAt(len-3);
if (c == ':') { // remove optional colon
// remove colon
StringBuilder sb = new StringBuilder(dateStr);
sb.delete(len-3, len-2);
dateStr = sb.toString();
} else if (c == '+' || c == '-') { // missing minutes
// let's just append '00'
dateStr += "00";
}
// [JACKSON-334]: may be missing milliseconds... if so, add
len = dateStr.length();
// '+0000' (5 chars); should come after '.000' (4 chars) of milliseconds, so:
c = dateStr.charAt(len-9);
if (Character.isDigit(c)) {
StringBuilder sb = new StringBuilder(dateStr);
sb.insert(len-5, ".000");
dateStr = sb.toString();
}
df = _formatISO8601;
if (_formatISO8601 == null) {
df = _formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601);
}
} else {
/* 24-Nov-2009, tatu: Ugh. This is getting pretty
* ugly. Need to rewrite soon!
*/
// If not, plain date. Easiest to just patch 'Z' in the end?
StringBuilder sb = new StringBuilder(dateStr);
// And possible also millisecond part if missing
int timeLen = len - dateStr.lastIndexOf('T') - 1;
if (timeLen <= 8) {
sb.append(".000");
}
sb.append('Z');
dateStr = sb.toString();
df = _formatISO8601_z;
if (df == null) {
df = _formatISO8601_z = _cloneFormat(DATE_FORMAT_ISO8601_Z);
}
}
}
return df.parse(dateStr, pos);
}
protected Date parseAsRFC1123(String dateStr, ParsePosition pos)
{
if (_formatRFC1123 == null) {
_formatRFC1123 = _cloneFormat(DATE_FORMAT_RFC1123);
}
return _formatRFC1123.parse(dateStr, pos);
}
private final static boolean hasTimeZone(String str)
{
// Only accept "+hh", "+hhmm" and "+hh:mm" (and with minus), so
int len = str.length();
if (len >= 6) {
char c = str.charAt(len-6);
if (c == '+' || c == '-') return true;
c = str.charAt(len-5);
if (c == '+' || c == '-') return true;
c = str.charAt(len-3);
if (c == '+' || c == '-') return true;
}
return false;
}
private final DateFormat _cloneFormat(DateFormat df) {
return _cloneFormat(df, _timezone);
}
private final static DateFormat _cloneFormat(DateFormat df, TimeZone tz)
{
df = (DateFormat) df.clone();
if (tz != null) {
df.setTimeZone(tz);
}
return df;
}
}