第一章 初步分析
第二章 Mybatis 源码分析
第三章 Jackson 源码分析 意想不到的Time处理类
初步分析
文中,主要针对项目部署服务器时区、数据库时区、Jvm运行设置时区和java.sql.Time
字段序列化过程时区问题进行展开分析。并给出三个可能问题相对应的解决方案。但是,前段时间又出现时区问题。让我必须重新思考此问题。
因此,我初步认为是Mybatis在数据持久化过程中,对java.sql.Time
进行时区处理,导致获取的数据产生时区问题。但通过对Mybatis源码分析,排除了此原因。
以下内容主要对Jackson源码进行阅读,理解分析java.sql.Time
字段序列化过程,并定位时区问题。
package com.fasterxml.jackson.annotation;
import java.lang.annotation.*;
import java.util.Locale;
import java.util.TimeZone;
/**
* General-purpose annotation used for configuring details of how
* values of properties are to be serialized.
* Unlike most other Jackson annotations, annotation does not
* have specific universal interpretation: instead, effect depends on datatype
* of property being annotated (or more specifically, deserializer
* and serializer being used).
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonFormat
{
public final static String DEFAULT_LOCALE = "##default";
public final static String DEFAULT_TIMEZONE = "##default";
public String pattern() default "";
public Shape shape() default Shape.ANY;
public String locale() default DEFAULT_LOCALE;
public String timezone() default DEFAULT_TIMEZONE;
public OptBoolean lenient() default OptBoolean.DEFAULT;
public JsonFormat.Feature[] with() default { };
public JsonFormat.Feature[] without() default { };
public enum Shape
{
ANY,
NATURAL,
SCALAR,
ARRAY,
OBJECT,
NUMBER,
NUMBER_FLOAT,
NUMBER_INT,
STRING,
BOOLEAN,
BINARY
;
public boolean isNumeric() {
return (this == NUMBER) || (this == NUMBER_INT) || (this == NUMBER_FLOAT);
}
public boolean isStructured() {
return (this == OBJECT) || (this == ARRAY);
}
}
public enum Feature {
ACCEPT_SINGLE_VALUE_AS_ARRAY,
ACCEPT_CASE_INSENSITIVE_PROPERTIES,
ACCEPT_CASE_INSENSITIVE_VALUES,
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS,
WRITE_DATES_WITH_ZONE_ID,
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,
WRITE_SORTED_MAP_ENTRIES,
ADJUST_DATES_TO_CONTEXT_TIME_ZONE
}
public static class Features
{
private final int _enabled, _disabled;
private final static Features EMPTY = new Features(0, 0);
private Features(int e, int d) {
_enabled = e;
_disabled = d;
}
}
public static class Value
implements JacksonAnnotationValue<JsonFormat>, // since 2.6
java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final static Value EMPTY = new Value();
private final String _pattern;
private final Shape _shape;
private final Locale _locale;
private final String _timezoneStr;
private final Boolean _lenient;
private final Features _features;
// lazily constructed when created from annotations
private transient TimeZone _timezone;
public Value() {
this("", Shape.ANY, "", "", Features.empty(), null);
}
public Value(JsonFormat ann) {
this(ann.pattern(), ann.shape(), ann.locale(), ann.timezone(),
Features.construct(ann), ann.lenient().asBoolean());
}
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
Boolean lenient)
{
this(p, sh,
(localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?
null : new Locale(localeStr),
(tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?
null : tzStr,
null, f, lenient);
}
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
Boolean lenient)
{
_pattern = p;
_shape = (sh == null) ? Shape.ANY : sh;
_locale = l;
_timezone = tz;
_timezoneStr = null;
_features = (f == null) ? Features.empty() : f;
_lenient = lenient;
}
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
Boolean lenient)
{
_pattern = p;
_shape = (sh == null) ? Shape.ANY : sh;
_locale = l;
_timezone = tz;
_timezoneStr = tzStr;
_features = (f == null) ? Features.empty() : f;
_lenient = lenient;
}
}
}
SqlTimeSerializer
SqlTimeSerializer
源码
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
@JacksonStdImpl
@SuppressWarnings("serial")
public class SqlTimeSerializer
extends StdScalarSerializer<java.sql.Time>
{
public SqlTimeSerializer() { super(java.sql.Time.class); }
@Override
public void serialize(java.sql.Time value, JsonGenerator g, SerializerProvider provider) throws IOException
{
g.writeString(value.toString());
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("string", true);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
visitStringFormat(visitor, typeHint, JsonValueFormat.DATE_TIME);
}
}
UTF8JsonGenerator
源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.fasterxml.jackson.core.json;
import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
public class UTF8JsonGenerator extends JsonGeneratorImpl {
private static final byte BYTE_u = 117;
private static final byte BYTE_0 = 48;
private static final byte BYTE_LBRACKET = 91;
private static final byte BYTE_RBRACKET = 93;
private static final byte BYTE_LCURLY = 123;
private static final byte BYTE_RCURLY = 125;
private static final byte BYTE_BACKSLASH = 92;
private static final byte BYTE_COMMA = 44;
private static final byte BYTE_COLON = 58;
private static final int MAX_BYTES_TO_BUFFER = 512;
private static final byte[] HEX_CHARS = CharTypes.copyHexBytes();
private static final byte[] NULL_BYTES = new byte[]{110, 117, 108, 108};
private static final byte[] TRUE_BYTES = new byte[]{116, 114, 117, 101};
private static final byte[] FALSE_BYTES = new byte[]{102, 97, 108, 115, 101};
protected final OutputStream _outputStream;
protected byte _quoteChar;
protected byte[] _outputBuffer;
protected int _outputTail;
protected final int _outputEnd;
protected final int _outputMaxContiguous;
protected char[] _charBuffer;
protected final int _charBufferLength;
protected byte[] _entityBuffer;
protected boolean _bufferRecyclable;
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar) {
super(ctxt, features, codec);
this._outputStream = out;
this._quoteChar = (byte)quoteChar;
if (quoteChar != '"') {
this._outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar);
}
this._bufferRecyclable = true;
this._outputBuffer = ctxt.allocWriteEncodingBuffer();
this._outputEnd = this._outputBuffer.length;
this._outputMaxContiguous = this._outputEnd >> 3;
this._charBuffer = ctxt.allocConcatBuffer();
this._charBufferLength = this._charBuffer.length;
if (this.isEnabled(Feature.ESCAPE_NON_ASCII)) {
this.setHighestNonEscapedChar(127);
}
}
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
super(ctxt, features, codec);
this._outputStream = out;
this._quoteChar = (byte)quoteChar;
if (quoteChar != '"') {
this._outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar);
}
this._bufferRecyclable = bufferRecyclable;
this._outputTail = outputOffset;
this._outputBuffer = outputBuffer;
this._outputEnd = this._outputBuffer.length;
this._outputMaxContiguous = this._outputEnd >> 3;
this._charBuffer = ctxt.allocConcatBuffer();
this._charBufferLength = this._charBuffer.length;
}
/** @deprecated */
@Deprecated
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out) {
this(ctxt, features, codec, out, '"');
}
/** @deprecated */
@Deprecated
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
this(ctxt, features, codec, out, '"', outputBuffer, outputOffset, bufferRecyclable);
}
public void writeString(String text) throws IOException {
this._verifyValueWrite("write a string");
if (text == null) {
this._writeNull();
} else {
int len = text.length();
if (len > this._outputMaxContiguous) {
this._writeStringSegments(text, true);
} else {
if (this._outputTail + len >= this._outputEnd) {
this._flushBuffer();
}
this._outputBuffer[this._outputTail++] = this._quoteChar;
this._writeStringSegment((String)text, 0, len);
if (this._outputTail >= this._outputEnd) {
this._flushBuffer();
}
this._outputBuffer[this._outputTail++] = this._quoteChar;
}
}
}
protected final void _verifyValueWrite(String typeMsg) throws IOException {
int status = this._writeContext.writeValue();
if (this._cfgPrettyPrinter != null) {
this._verifyPrettyValueWrite(typeMsg, status);
} else {
byte b;
switch(status) {
case 0:
case 4:
default:
return;
case 1:
b = 44;
break;
case 2:
b = 58;
break;
case 3:
if (this._rootValueSeparator != null) {
byte[] raw = this._rootValueSeparator.asUnquotedUTF8();
if (raw.length > 0) {
this._writeBytes(raw);
}
}
return;
case 5:
this._reportCantWriteValueExpectName(typeMsg);
return;
}
if (this._outputTail >= this._outputEnd) {
this._flushBuffer();
}
this._outputBuffer[this._outputTail++] = b;
}
}
private final void _writeStringSegments(String text, boolean addQuotes) throws IOException {
if (addQuotes) {
if (this._outputTail >= this._outputEnd) {
this._flushBuffer();
}
this._outputBuffer[this._outputTail++] = this._quoteChar;
}
int left = text.length();
int len;
for(int offset = 0; left > 0; left -= len) {
len = Math.min(this._outputMaxContiguous, left);
if (this._outputTail + len > this._outputEnd) {
this._flushBuffer();
}
this._writeStringSegment(text, offset, len);
offset += len;
}
if (addQuotes) {
if (this._outputTail >= this._outputEnd) {
this._flushBuffer();
}
this._outputBuffer[this._outputTail++] = this._quoteChar;
}
}
private final void _writeStringSegments(String text, int offset, int totalLen) throws IOException {
do {
int len = Math.min(this._outputMaxContiguous, totalLen);
if (this._outputTail + len > this._outputEnd) {
this._flushBuffer();
}
this._writeStringSegment(text, offset, len);
offset += len;
totalLen -= len;
} while(totalLen > 0);
}
private final void _writeStringSegment(String text, int offset, int len) throws IOException {
len += offset;
int outputPtr = this._outputTail;
byte[] outputBuffer = this._outputBuffer;
for(int[] escCodes = this._outputEscapes; offset < len; ++offset) {
int ch = text.charAt(offset);
if (ch > 127 || escCodes[ch] != 0) {
break;
}
outputBuffer[outputPtr++] = (byte)ch;
}
this._outputTail = outputPtr;
if (offset < len) {
if (this._characterEscapes != null) {
this._writeCustomStringSegment2(text, offset, len);
} else if (this._maximumNonEscapedChar == 0) {
this._writeStringSegment2(text, offset, len);
} else {
this._writeStringSegmentASCII2(text, offset, len);
}
}
}
private final void _writeUTF8Segments(byte[] utf8, int offset, int totalLen) throws IOException, JsonGenerationException {
do {
int len = Math.min(this._outputMaxContiguous, totalLen);
this._writeUTF8Segment(utf8, offset, len);
offset += len;
totalLen -= len;
} while(totalLen > 0);
}
private final void _writeUTF8Segment(byte[] utf8, int offset, int len) throws IOException, JsonGenerationException {
int[] escCodes = this._outputEscapes;
int ptr = offset;
int end = offset + len;
byte ch;
do {
if (ptr >= end) {
if (this._outputTail + len > this._outputEnd) {
this._flushBuffer();
}
System.arraycopy(utf8, offset, this._outputBuffer, this._outputTail, len);
this._outputTail += len;
return;
}
ch = utf8[ptr++];
} while(ch < 0 || escCodes[ch] == 0);
this._writeUTF8Segment2(utf8, offset, len);
}
private final void _writeUTF8Segment2(byte[] utf8, int offset, int len) throws IOException, JsonGenerationException {
int outputPtr = this._outputTail;
if (outputPtr + len * 6 > this._outputEnd) {
this._flushBuffer();
outputPtr = this._outputTail;
}
byte[] outputBuffer = this._outputBuffer;
int[] escCodes = this._outputEscapes;
len += offset;
while(true) {
while(offset < len) {
byte b = utf8[offset++];
if (b >= 0 && escCodes[b] != 0) {
int escape = escCodes[b];
if (escape > 0) {
outputBuffer[outputPtr++] = 92;
outputBuffer[outputPtr++] = (byte)escape;
} else {
outputPtr = this._writeGenericEscape(b, outputPtr);
}
} else {
outputBuffer[outputPtr++] = b;
}
}
this._outputTail = outputPtr;
return;
}
}
}
java.sql.Time
源码:
package java.sql;
import java.time.Instant;
import java.time.LocalTime;
/**
* A thin wrapper around the java.util.Date
class that allows the JDBC
* API to identify this as an SQL TIME
value. The Time
* class adds formatting and
* parsing operations to support the JDBC escape syntax for time
* values.
*
The date components should be set to the "zero epoch"
* value of January 1, 1970 and should not be accessed.
*/
public class Time extends java.util.Date {
/**
* Constructs a Time
object initialized with the
* given values for the hour, minute, and second.
* The driver sets the date components to January 1, 1970.
* Any method that attempts to access the date components of a
* Time
object will throw a
* java.lang.IllegalArgumentException
.
*
* The result is undefined if a given argument is out of bounds.
*
* @param hour 0 to 23
* @param minute 0 to 59
* @param second 0 to 59
*
* @deprecated Use the constructor that takes a milliseconds value
* in place of this constructor
*/
@Deprecated
public Time(int hour, int minute, int second) {
super(70, 0, 1, hour, minute, second);
}
/**
* Constructs a Time
object using a milliseconds time value.
*
* @param time milliseconds since January 1, 1970, 00:00:00 GMT;
* a negative number is milliseconds before
* January 1, 1970, 00:00:00 GMT
*/
public Time(long time) {
super(time);
}
/**
* Formats a time in JDBC time escape format.
*
* @return a String
in hh:mm:ss format
*/
@SuppressWarnings("deprecation")
public String toString () {
int hour = super.getHours();
int minute = super.getMinutes();
int second = super.getSeconds();
String hourString;
String minuteString;
String secondString;
if (hour < 10) {
hourString = "0" + hour;
} else {
hourString = Integer.toString(hour);
}
if (minute < 10) {
minuteString = "0" + minute;
} else {
minuteString = Integer.toString(minute);
}
if (second < 10) {
secondString = "0" + second;
} else {
secondString = Integer.toString(second);
}
return (hourString + ":" + minuteString + ":" + secondString);
}
}
java.util.Date
源码:
package java.util;
import java.text.DateFormat;
import java.time.LocalDate;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.lang.ref.SoftReference;
import java.time.Instant;
import sun.util.calendar.BaseCalendar;
import sun.util.calendar.CalendarDate;
import sun.util.calendar.CalendarSystem;
import sun.util.calendar.CalendarUtils;
import sun.util.calendar.Era;
import sun.util.calendar.Gregorian;
import sun.util.calendar.ZoneInfo;
/**
* The class Date
represents a specific instant
* in time, with millisecond precision.
*
* Prior to JDK 1.1, the class Date
had two additional
* functions. It allowed the interpretation of dates as year, month, day, hour,
* minute, and second values. It also allowed the formatting and parsing
* of date strings. Unfortunately, the API for these functions was not
* amenable to internationalization. As of JDK 1.1, the
* Calendar
class should be used to convert between dates and time
* fields and the DateFormat
class should be used to format and
* parse date strings.
* The corresponding methods in Date
are deprecated.
*
* Although the Date
class is intended to reflect
* coordinated universal time (UTC), it may not do so exactly,
* depending on the host environment of the Java Virtual Machine.
* Nearly all modern operating systems assume that 1 day =
* 24 × 60 × 60 = 86400 seconds
* in all cases. In UTC, however, about once every year or two there
* is an extra second, called a "leap second." The leap
* second is always added as the last second of the day, and always
* on December 31 or June 30. For example, the last minute of the
* year 1995 was 61 seconds long, thanks to an added leap second.
* Most computer clocks are not accurate enough to be able to reflect
* the leap-second distinction.
*
* Some computer standards are defined in terms of Greenwich mean
* time (GMT), which is equivalent to universal time (UT). GMT is
* the "civil" name for the standard; UT is the
* "scientific" name for the same standard. The
* distinction between UTC and UT is that UTC is based on an atomic
* clock and UT is based on astronomical observations, which for all
* practical purposes is an invisibly fine hair to split. Because the
* earth's rotation is not uniform (it slows down and speeds up
* in complicated ways), UT does not always flow uniformly. Leap
* seconds are introduced as needed into UTC so as to keep UTC within
* 0.9 seconds of UT1, which is a version of UT with certain
* corrections applied. There are other time and date systems as
* well; for example, the time scale used by the satellite-based
* global positioning system (GPS) is synchronized to UTC but is
* not adjusted for leap seconds. An interesting source of
* further information is the U.S. Naval Observatory, particularly
* the Directorate of Time at:
*
* http://tycho.usno.navy.mil
*
*
* and their definitions of "Systems of Time" at:
*
* http://tycho.usno.navy.mil/systime.html
*
*
* In all methods of class Date
that accept or return
* year, month, date, hours, minutes, and seconds values, the
* following representations are used:
*
* - A year y is represented by the integer
* y
- 1900
.
* - A month is represented by an integer from 0 to 11; 0 is January,
* 1 is February, and so forth; thus 11 is December.
*
- A date (day of month) is represented by an integer from 1 to 31
* in the usual manner.
*
- An hour is represented by an integer from 0 to 23. Thus, the hour
* from midnight to 1 a.m. is hour 0, and the hour from noon to 1
* p.m. is hour 12.
*
- A minute is represented by an integer from 0 to 59 in the usual manner.
*
- A second is represented by an integer from 0 to 61; the values 60 and
* 61 occur only for leap seconds and even then only in Java
* implementations that actually track leap seconds correctly. Because
* of the manner in which leap seconds are currently introduced, it is
* extremely unlikely that two leap seconds will occur in the same
* minute, but this specification follows the date and time conventions
* for ISO C.
*
*
* In all cases, arguments given to methods for these purposes need
* not fall within the indicated ranges; for example, a date may be
* specified as January 32 and is interpreted as meaning February 1.
*
* @author James Gosling
* @author Arthur van Hoff
* @author Alan Liu
* @see java.text.DateFormat
* @see java.util.Calendar
* @see java.util.TimeZone
* @since JDK1.0
*/
public class Date
implements java.io.Serializable, Cloneable, Comparable<Date>
{
private static final BaseCalendar gcal =
CalendarSystem.getGregorianCalendar();
private static BaseCalendar jcal;
private transient long fastTime;
private transient BaseCalendar.Date cdate;
private static int defaultCenturyStart;
/**
* Returns the hour represented by this Date object. The
* returned value is a number (0 through 23)
* representing the hour within the day that contains or begins
* with the instant in time represented by this Date
* object, as interpreted in the local time zone.
*
* @return the hour represented by this date.
* @see java.util.Calendar
* @deprecated As of JDK version 1.1,
* replaced by Calendar.get(Calendar.HOUR_OF_DAY)
.
*/
@Deprecated
public int getHours() {
return normalize().getHours();
}
/**
* Returns the number of minutes past the hour represented by this date,
* as interpreted in the local time zone.
* The value returned is between 0
and 59
.
*
* @return the number of minutes past the hour represented by this date.
* @see java.util.Calendar
* @deprecated As of JDK version 1.1,
* replaced by Calendar.get(Calendar.MINUTE)
.
*/
@Deprecated
public int getMinutes() {
return normalize().getMinutes();
}
private final BaseCalendar.Date normalize() {
if (cdate == null) {
BaseCalendar cal = getCalendarSystem(fastTime);
cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,
TimeZone.getDefaultRef());
return cdate;
}
// Normalize cdate with the TimeZone in cdate first. This is
// required for the compatible behavior.
if (!cdate.isNormalized()) {
cdate = normalize(cdate);
}
// If the default TimeZone has changed, then recalculate the
// fields with the new TimeZone.
TimeZone tz = TimeZone.getDefaultRef();
if (tz != cdate.getZone()) {
cdate.setZone(tz);
CalendarSystem cal = getCalendarSystem(cdate);
cal.getCalendarDate(fastTime, cdate);
}
return cdate;
}
// fastTime and the returned data are in sync upon return.
private final BaseCalendar.Date normalize(BaseCalendar.Date date) {
int y = date.getNormalizedYear();
int m = date.getMonth();
int d = date.getDayOfMonth();
int hh = date.getHours();
int mm = date.getMinutes();
int ss = date.getSeconds();
int ms = date.getMillis();
TimeZone tz = date.getZone();
// If the specified year can't be handled using a long value
// in milliseconds, GregorianCalendar is used for full
// compatibility with underflow and overflow. This is required
// by some JCK tests. The limits are based max year values -
// years that can be represented by max values of d, hh, mm,
// ss and ms. Also, let GregorianCalendar handle the default
// cutover year so that we don't need to worry about the
// transition here.
if (y == 1582 || y > 280000000 || y < -280000000) {
if (tz == null) {
tz = TimeZone.getTimeZone("GMT");
}
GregorianCalendar gc = new GregorianCalendar(tz);
gc.clear();
gc.set(GregorianCalendar.MILLISECOND, ms);
gc.set(y, m-1, d, hh, mm, ss);
fastTime = gc.getTimeInMillis();
BaseCalendar cal = getCalendarSystem(fastTime);
date = (BaseCalendar.Date) cal.getCalendarDate(fastTime, tz);
return date;
}
BaseCalendar cal = getCalendarSystem(y);
if (cal != getCalendarSystem(date)) {
date = (BaseCalendar.Date) cal.newCalendarDate(tz);
date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms);
}
// Perform the GregorianCalendar-style normalization.
fastTime = cal.getTime(date);
// In case the normalized date requires the other calendar
// system, we need to recalculate it using the other one.
BaseCalendar ncal = getCalendarSystem(fastTime);
if (ncal != cal) {
date = (BaseCalendar.Date) ncal.newCalendarDate(tz);
date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms);
fastTime = ncal.getTime(date);
}
return date;
}
/**
* Returns the Gregorian or Julian calendar system to use with the
* given date. Use Gregorian from October 15, 1582.
*
* @param year normalized calendar year (not -1900)
* @return the CalendarSystem to use for the specified date
*/
private static final BaseCalendar getCalendarSystem(int year) {
if (year >= 1582) {
return gcal;
}
return getJulianCalendar();
}
private static final BaseCalendar getCalendarSystem(long utc) {
// Quickly check if the time stamp given by `utc' is the Epoch
// or later. If it's before 1970, we convert the cutover to
// local time to compare.
if (utc >= 0
|| utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER
- TimeZone.getDefaultRef().getOffset(utc)) {
return gcal;
}
return getJulianCalendar();
}
private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) {
if (jcal == null) {
return gcal;
}
if (cdate.getEra() != null) {
return jcal;
}
return gcal;
}
synchronized private static final BaseCalendar getJulianCalendar() {
if (jcal == null) {
jcal = (BaseCalendar) CalendarSystem.forName("julian");
}
return jcal;
}
}
Jackson
实际调用了SqlTimeSerializer
类的serialize()
方法进行序列化,进一步跟踪看到调用java.sql.Time
类的toString()
的结果作为参数给了UTF8JsonGenerator
类的writeString()
方法,进行UTF8格式化。而通过对java.sql.Time
类的toString()
方法源码阅读,可以发现其中获取是调用父类java.util.Date
的方法获取小时,分钟和秒,但是此些方法中调用的normalize()
方法涉及时区问题,可能由于时区问题,导致获取时间和所需产生时差。
normalize()
涉及时区代码
private final BaseCalendar.Date normalize() {
if (cdate == null) {
BaseCalendar cal = getCalendarSystem(fastTime);
cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,
TimeZone.getDefaultRef());
return cdate;
}
// Normalize cdate with the TimeZone in cdate first. This is
// required for the compatible behavior.
if (!cdate.isNormalized()) {
cdate = normalize(cdate);
}
// If the default TimeZone has changed, then recalculate the
// fields with the new TimeZone.
TimeZone tz = TimeZone.getDefaultRef();
if (tz != cdate.getZone()) {
cdate.setZone(tz);
CalendarSystem cal = getCalendarSystem(cdate);
cal.getCalendarDate(fastTime, cdate);
}
return cdate;
}
Jackson
对java.util.Date
类进行时区处理源码 @Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException
{
final JsonFormat.Value format = findFormatOverrides(ctxt, property,
handledType());
if (format != null) {
TimeZone tz = format.getTimeZone();
final Boolean lenient = format.getLenient();
// First: fully custom pattern?
if (format.hasPattern()) {
final String pattern = format.getPattern();
final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
// 时间格式化处理
SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
if (tz == null) {
tz = ctxt.getTimeZone();
}
// 时区设置
df.setTimeZone(tz);
if (lenient != null) {
df.setLenient(lenient);
}
return withDateFormat(df, pattern);
}
// But if not, can still override timezone
if (tz != null) {
DateFormat df = ctxt.getConfig().getDateFormat();
// one shortcut: with our custom format, can simplify handling a bit
if (df.getClass() == StdDateFormat.class) {
final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
StdDateFormat std = (StdDateFormat) df;
std = std.withTimeZone(tz);
std = std.withLocale(loc);
if (lenient != null) {
std = std.withLenient(lenient);
}
df = std;
} else {
// otherwise need to clone, re-set timezone:
df = (DateFormat) df.clone();
df.setTimeZone(tz);
if (lenient != null) {
df.setLenient(lenient);
}
}
return withDateFormat(df, _formatString);
}
// or maybe even just leniency?
if (lenient != null) {
DateFormat df = ctxt.getConfig().getDateFormat();
String pattern = _formatString;
// one shortcut: with our custom format, can simplify handling a bit
if (df.getClass() == StdDateFormat.class) {
StdDateFormat std = (StdDateFormat) df;
std = std.withLenient(lenient);
df = std;
pattern = std.toPattern();
} else {
// otherwise need to clone,
df = (DateFormat) df.clone();
df.setLenient(lenient);
if (df instanceof SimpleDateFormat) {
((SimpleDateFormat) df).toPattern();
}
}
if (pattern == null) {
pattern = "[unknown]";
}
return withDateFormat(df, pattern);
}
}
return this;
}
实际时区处理代码如下:
if (format.hasPattern()) {
final String pattern = format.getPattern();
final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
// 时间格式化处理
SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
if (tz == null) {
tz = ctxt.getTimeZone();
}
// 时区设置
df.setTimeZone(tz);
if (lenient != null) {
df.setLenient(lenient);
}
return withDateFormat(df, pattern);
}
通过以上的源码分析,得出你觉得的觉得不是真的觉得,Jackson就在对java.sql.Time
序列化中根本不考虑时区问题。
建议你自己自定义个处理类对java.sql.Time
进行处理,支持处理指定时区。
以下是我写的序列化工具,希望可以对你有帮助!
java.sql.Time 字段使用 Jackson 进行序列化