A lead time due to Daylight Savings Time JDK interface output date format is not consistent with the expected time of bug investigation summary

bug description

The problem originated in the details of a new co-worker birthday statistical user interfaces in the project, in which a user's date of birth in the database is "1988-07-29", but to get the user's date of birth through the rest but the interface is "1988- 07-28. "

Environment Description

Bug before the start of the investigation, first described under the project environment:

  • System: centos 7.5
  • JDK:1.8.0_171
  • Technology stack: spring boot, Jackson, Druid, mybatis, oracle.

bug investigation

Find the data layer, first query the database time and time zone.

SQL> SELECT SYSTIMESTAMP, SESSIONTIMEZONE FROM DUAL;
SYSTIMESTAMP                                                                     SESSIONTIMEZONE
-------------------------------------------------------------------------------- ---------------------------------------------------------------------------
17-JUL-19 02.20.06.687149 PM +08:00                                              +08:00

SQL>
复制代码

Database time and time zone are no problem.

Sure the operating system and java process time zone

  • Check the operating system time zone
[test@test ~]$ date -R
Wed, 17 Jul 2019 16:48:32 +0800
[test@test ~]$ cat /etc/timezone
Asia/Shanghai
复制代码
  • View java process time zone
[test@test ~]$ jinfo 7490 |grep user.timezone
user.timezone = Asia/Shanghai
复制代码

We can see the same time zone used by the operating system and java process using the time zone is east eight districts.

Continue to look to see mybatis upper layer debug and JDBC

See the question field mapper jdbcType type field is mapped jdbcType = "TIMESTAMP", corresponding to the type of treatment in mybatis registration processing TypeHandlerRegistry.java class class DateTypeHandler.java.

this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));
复制代码

Check DateTypeHandler.java categories:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;

public class DateTypeHandler extends BaseTypeHandler<Date> {
  public DateTypeHandler() {
  }

  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
    ps.setTimestamp(i, new Timestamp(parameter.getTime()));
  }

  public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
    Timestamp sqlTimestamp = rs.getTimestamp(columnName);
    return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
  }

  public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);
    return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
  }

  public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);
    return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
  }
}

复制代码

getTimestamp (String columnLabel) is used as the data source Druid, wherein getNullableResult (ResultSet rs, String columnName) method parameters are used ResultSet DruidPooledResultSet.java by column name acquired value is then converted to a value of type Date.

The Timestamp is seen on FIG debug JDK classes, i.e. JDK see here is used time and time zone denoted at 2 it can be seen from the figure the time zone is also used JDK East eight regions, but from 1 and 3 seems a bit different, at first a change UTC / GMT + has a daylightSaving such a time at 0900,3, just in terms of hours to one hour. This value is called by the google search know when daylight saving time.

Common concept of time UTC, GMT, CST, DST

  • UTC Coordinated Universal Time (English: C oordinated U niversal T IME, French: T the emps U niversel C oordonné, abbreviated UTC ) is the world's leading standard time, which is when the atomic -second basis, as close as possible in time Greenwich standard time . ROC using CNS 7648's "data element and interchange formats - Information exchange - date and time notation" (with ISO 8601 similar) called Coordinated Universal Time. People's Republic of China adopted ISO 8601: 2000 of the National Standard GB / T 7408-2005 "Data elements and interchange formats - Information interchange the date and time representation" also known as the Coordinated Universal Time. (Excerpt from: zh.wikipedia.org/wiki/%E5%8D... )

  • GMT Greenwich Mean Time (English: G reenwich M EAN T IME, GMT ) refers situated UK London suburb of Royal Greenwich Observatory, the local mean solar time , because the prime meridian is defined as by where warp . (Excerpt from: zh.wikipedia.org/wiki/%E6%A0... )

  • CST Beijing , also known as China Standard Time , is mainland China 's standard time , than Coordinated Universal fast eight hours (ie UTC + 8 ), and Hong Kong , Macao , Taipei , Kuala Lumpur , Singapore standard time is the same as other places.

    GMT not Beijing local mean solar time (longitude 116.4 °), but longitude local solar time level of 120 °, a difference of about 14.5 minutes [ 1] . Beijing is located in Chinese territory by the geometric center position Shaanxi Lintong is granted when the National Academy of Sciences Center of nine cesium atomic clocks and two hydrogen atomic clock set to achieve precise alignments and calculation of timekeeping through and through satellite in real time comparison with other countries Time Service Department . (Excerpt from: zh.wikipedia.org/wiki/%E5%8C... )

  • DST Daylight Saving Time (English: daylight time, the United Kingdom and other regions), also known as daylight savings time, daylight savings time (English: daylight saving time, DST, United States), is a kind of summer the expense of normal month sunrise time, updating the time practice. Area usually made when the summer, will start near the spring, when the time is adjusted forward one hour and transferred back to the normal time [in the fall 1] . In fact, daylight saving time will result in the spring date of the conversion sleep time by one hour in the fall and date of the conversion will be one hour extra sleep time [ 2] [ 3] . (Excerpt from: zh.wikipedia.org/wiki/%E5%A4... )

  • Chinese daylight saving time in April 1986, China's central authorities issued a "system of implementation of daylight saving time in the country," which specific practices are: from mid-year at 2:00 on April the first Sunday of the whole (Beijing time), the the clock ahead one hour, is about to start daylight saving time by the hands dialing 2:00 pm to 3; mid-September to the whole (Beijing daylight saving time), then the clock back one hour 2:00 on a Sunday, coming from the hands dial 2 to 1, the end of daylight saving time. From 1986 to 1991, six years, except for 1986 due to the first year of implementation of daylight saving time, beginning from May 4 to the outer end of September 14, the period shall be governed by the implementation of other years. Start and end of daylight saving time in a few days, the news media have published notices authorities. Since 1992, suspended the implementation of daylight saving time. (Excerpt from: baike.baidu.com/item/%E5%A4... )

China implemented daylight saving time provisions (daylight saving time)
from 1935 to 1951, the annual May 1 to September 30.
1952 Nian March 1 to October 31.
1953-1954, the annual April 1 to October 31.
1955-1956, the annual May 1 to September 30.
1957-1959, the annual April 1 to September 30.
1960-1961, the annual June 1 to September 30.
1974-1975, the annual April 1 to October 31.
1979 Nian July 1 to September 30.
The first first Sunday 1:00 until mid-September 1986 to 1991, each year in mid-April 1 Sunday only. As follows:
1986 years April 13 to September 14,
1987 years April 12 to September 13,
1988 years April 10 to September 11,
1989 years April 16 to September 17 ,
1990 Nian April 15 to September 16,
1991 Nian April 14 to September 15.

By contrast, we can see the application of the corresponding user Birthday "1988-07-29" just in China daylight saving time zone, because we operating system, database, JDK used are "Asia / Shanghai" timezone, should not be wrong, by the figure above debug the results we have confirmed the result is not the problem.

Continue out investigation service layer and the interface layer, locate the problem

Project using a spring boot interface to provide rest to return json messages, using spring framework Jackson default resolution. Projects need external output uniform date format, Jackson made a bit of configuration:

#jackson
#日期格式化
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
复制代码

We see JacksonProperties.java Source:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.jackson;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(
  prefix = "spring.jackson"
)
public class JacksonProperties {
  private String dateFormat;
  private String jodaDateTimeFormat;
  private String propertyNamingStrategy;
  private Map<SerializationFeature, Boolean> serialization = new EnumMap(SerializationFeature.class);
  private Map<DeserializationFeature, Boolean> deserialization = new EnumMap(DeserializationFeature.class);
  private Map<MapperFeature, Boolean> mapper = new EnumMap(MapperFeature.class);
  private Map<Feature, Boolean> parser = new EnumMap(Feature.class);
  private Map<com.fasterxml.jackson.core.JsonGenerator.Feature, Boolean> generator = new EnumMap(com.fasterxml.jackson.core.JsonGenerator.Feature.class);
  private Include defaultPropertyInclusion;
  private TimeZone timeZone = null;
  private Locale locale;

  public JacksonProperties() {
  }

  public String getDateFormat() {
    return this.dateFormat;
  }

  public void setDateFormat(String dateFormat) {
    this.dateFormat = dateFormat;
  }

  public String getJodaDateTimeFormat() {
    return this.jodaDateTimeFormat;
  }

  public void setJodaDateTimeFormat(String jodaDataTimeFormat) {
    this.jodaDateTimeFormat = jodaDataTimeFormat;
  }

  public String getPropertyNamingStrategy() {
    return this.propertyNamingStrategy;
  }

  public void setPropertyNamingStrategy(String propertyNamingStrategy) {
    this.propertyNamingStrategy = propertyNamingStrategy;
  }

  public Map<SerializationFeature, Boolean> getSerialization() {
    return this.serialization;
  }

  public Map<DeserializationFeature, Boolean> getDeserialization() {
    return this.deserialization;
  }

  public Map<MapperFeature, Boolean> getMapper() {
    return this.mapper;
  }

  public Map<Feature, Boolean> getParser() {
    return this.parser;
  }

  public Map<com.fasterxml.jackson.core.JsonGenerator.Feature, Boolean> getGenerator() {
    return this.generator;
  }

  public Include getDefaultPropertyInclusion() {
    return this.defaultPropertyInclusion;
  }

  public void setDefaultPropertyInclusion(Include defaultPropertyInclusion) {
    this.defaultPropertyInclusion = defaultPropertyInclusion;
  }

  public TimeZone getTimeZone() {
    return this.timeZone;
  }

  public void setTimeZone(TimeZone timeZone) {
    this.timeZone = timeZone;
  }

  public Locale getLocale() {
    return this.locale;
  }

  public void setLocale(Locale locale) {
    this.locale = locale;
  }
}

复制代码

Spring.jackson.time-zone attribute that operation is java.util.TimeZone. So we pass some test code conversion process simulation:

package com.test;

import java.sql.Date;
import java.util.TimeZone;

/**
 * @author alexpdh
 * @date 2019/07/17
 */
public class Test {

	public static void main(String[] args) {
		System.out.println("当前的默认时区为: " + TimeZone.getDefault().getID());
		Date date1 = Date.valueOf("1988-07-29");
		Date date2 = Date.valueOf("1983-07-29");
		System.out.println("在中国夏令时范围内的时间 date1=" + date1);
		System.out.println("正常东八区时间 date2=" + date2);
//		模拟 spring.jackson.time-zone=GMT+8 属性设置
		TimeZone zone = TimeZone.getTimeZone("GMT+8");
		TimeZone.setDefault(zone);
		System.out.println(TimeZone.getDefault().getID());
		Date date3 = date1;
		Date date4 = date2;
		System.out.println("转换后的在中国夏令时范围内的时间date3=" + date3);
		System.out.println("转换后的正常东八区时间 date4=" + date4);
	}
}

复制代码

After running output:

当前的默认时区为: Asia/Shanghai
在中国夏令时范围内的时间 date1=1988-07-29
正常东八区时间 date2=1983-07-29
GMT+08:00
转换后的在中国夏令时范围内的时间date3=1988-07-28
转换后的正常东八区时间 date4=1983-07-29
复制代码

From here, finally found the problem occurs, we see from the picture is a debug because that date is in daylight saving time interval within China, to nearly an hour, using the format of UTC / GMT + 0900, and in the newspaper jackjson json text format is converted when using the format UTC / GMT + 0800's. That is the area we as UTC / GMT + 0900 of "1988-07-29 00:00:00" This is a time of conversion to UTC standard eight districts of the East when JDK / GMT + 0800 format, you need to slow the one hour into "1988-07-28 23:00:00."

bug solved

Navigate to solve the problem is very simple, only need to modify the following settings:

#jackson
#日期格式化
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=Asia/Shanghai
复制代码

To maintain consistent time zone issue is resolved.

to sum up

Through this investigation bug I got some personal gains.

Correct storage time

Liao Xuefeng teacher read an article article "How to handle correctly the time," the time is right when it comes to storage:

Excerpt: www.liaoxuefeng.com/article/978...

Based on the "data storage and display phase separation" design principle, as long as we represent the absolute time time stamp (Long type, whether or Float) stored in the database, formatted according to the time zone set by the user to the correct character in the show when string. So, when the database stores the time and date, just put Long Float or time stamp to indicate the existence BIGINTor REALtypes of columns, completely and leave the database to provide their own DATETIMEor TIMESTAMPdo not worry about time zone application server and database server setup problems encountered you do not have to bother about the Oracle database with timezoneand with local timezonein the end what's the difference. When the read time, read or is a Long Float, it can only be correctly displayed according to the user's time zone is formatted as a string.

Time-based storage absolute timestamp, fundamentally no problem time zone. Time zone just a display issue. Obtain additional benefits also include:

  • Compare is a comparison of the value of two times, the problem does not involve time zone, it is extremely simple;
  • Screening is also the time of screening between two values, write SQL that is between(?, ?);
  • When the display time, the Long or Float spread page, whether or using server-side scripting with JavaScript can be simply and correctly display the time.

Two auxiliary functions only thing you need is to write String->Longand Long->String. String->LongThe effect is to convert time string input by the user specified by the user into a database stored Long District.

The only drawback is that you see is not a database query time string, but similar 1413266801750numbers and the like.

When setting pay attention to the different areas of software development language or time required for treatment

This is consistent with the underlying operating system to start from systems, databases, development languages ​​time zones. Such as after using the system java language, install the operating system can put the system time zone and time to adjust, and then install the database and JDK, general JDK default will use the time zone operating system settings during installation, thus eliminating the need to re step time zone set separately at the JDK. If you need to use the code in any frame or packet processing time, when the need to verify whether the set when the system area and the area where the presence of daylight saving time, when Americans now seemingly also still in use daylight saving time.

gongzhongzhanghao

Guess you like

Origin juejin.im/post/5d2f0ab3e51d4577596487a1