JAVA技术文章

修改ObjectMapper的StdDateFormat(字符串

2017-12-06  本文已影响1043人  rejoice001

使用RestTemplate来进行http接口请求接收响应时,org.springframework.http.converter.json.MappingJackson2HttpMessageConverter中反系列化时实际上是使用ObjectMapper来进行反系列化,最终对日期的操作com.fasterxml.jackson.databind.util.StdDateFormat.parse(String dateStr),里面内置了4种格式:"yyyy-MM-dd'T'HH:mm:ss.SSSZ"、"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"、"yyyy-MM-dd"、"EEE, dd MMM yyyy HH:mm:ss zzz",不能很好支持中国人的习惯,另外此类是线程不安全的,每次都会创建一个新的对象来操作日期,尽管ObjectMapper有全局设置dateForma:mapper.setDateFormat(dateFormat),但是最终获取DateFormat时只会Clone其属性,而常量和方法还是类的:com.fasterxml.jackson.databind.DeserializationContext.getDateFormat()

protected DateFormat getDateFormat()  
{  
    if (_dateFormat != null) {  
        return _dateFormat;  
    }  
    /* 24-Feb-2012, tatu: At this point, all timezone configuration 
     *    should have occured, with respect to default dateformat 
     *    and timezone configuration. But we still better clone 
     *    an instance as formatters may be stateful. 
     */  
    DateFormat df = _config.getDateFormat();  
    _dateFormat = df = (DateFormat) df.clone();  
    return df;  
}  

所以只能重写com.fasterxml.jackson.databind.util.StdDateFormat的parse和format方法(需要可自行重写),由于简书无法设置字体颜色,无法高亮修改部分,

其中修改部分为

静态常亮中DATE_FORMAT_TO_ALL开始处设置了几个新增的格式

/** 
     * yyyy-MM-dd HH:mm:ss.SSS 
     */  
    protected final static String DATE_FORMAT_TO_ALL= "yyyy-MM-dd HH:mm:ss.SSS";  
    /** 
     * yyyy-MM-dd HH:mm:ss 
     */  
    protected final static String DATE_FORMAT_TO_SECOND= "yyyy-MM-dd HH:mm:ss";  
    /** 
     * yyyy-MM-dd HH:mm 
     */  
    protected final static String DATE_FORMAT_TO_MINUTE= "yyyy-MM-dd HH:mm";  

并加入到集合中

 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,  
        DATE_FORMAT_TO_ALL,  
        DATE_FORMAT_TO_SECOND,  
        DATE_FORMAT_TO_MINUTE,  
    };  

具体parse方法中,如果默认的方法转换结果为null,则采用新加的格式

@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;  
        }else{  
            //修改处,使用新的格式转换
            //modified by rejoice.zhang at 2017-02-27  
            result = DateUtils.parseDate(dateStr, DATE_FORMAT_TO_ALL,DATE_FORMAT_TO_SECOND,DATE_FORMAT_TO_MINUTE);  
          

下面是完整类的代码(注意:每个人的版本不同,不要拷贝我的完整代码,建议自行拷贝自身版本的代码进行修改,如果有需要)

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 org.apache.commons.lang3.time.DateUtils;  
  
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 
     */  
<font color=red>
    protected final static String DATE_FORMAT_STR_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";  
  </font>
    /** 
     * 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";  
      
    /** 
     * yyyy-MM-dd HH:mm:ss.SSS 
     */  
    protected final static String DATE_FORMAT_TO_ALL= "yyyy-MM-dd HH:mm:ss.SSS";  
    /** 
     * yyyy-MM-dd HH:mm:ss 
     */  
    protected final static String DATE_FORMAT_TO_SECOND= "yyyy-MM-dd HH:mm:ss";  
    /** 
     * yyyy-MM-dd HH:mm 
     */  
    protected final static String DATE_FORMAT_TO_MINUTE= "yyyy-MM-dd HH:mm";  
  
    /** 
     * 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,  
        DATE_FORMAT_TO_ALL,  
        DATE_FORMAT_TO_SECOND,  
        DATE_FORMAT_TO_MINUTE,  
    };  
  
    /** 
     * 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;  
        }else{  
            //modified by rejoice.zhang at 2017-02-27  
            result = DateUtils.parseDate(dateStr, DATE_FORMAT_TO_ALL,DATE_FORMAT_TO_SECOND,DATE_FORMAT_TO_MINUTE);  
            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') break;  
        }  
        if (i < 0) { // all digits  
            if (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);  
    }  
  
    /* 
    /********************************************************** 
    /* 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;  
    }  
}  

当然上面是全局设置,也可以在特定的类的属性上面加上@JsonFormat (局部)

上一篇 下一篇

猜你喜欢

热点阅读