欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題

本篇內(nèi)容主要講解“怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題”吧!

成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),鄂溫克企業(yè)網(wǎng)站建設(shè),鄂溫克品牌網(wǎng)站建設(shè),網(wǎng)站定制,鄂溫克網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,鄂溫克網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

bug描述

問(wèn)題起源于同事在項(xiàng)目中新增一個(gè)統(tǒng)計(jì)用戶生日明細(xì)的接口,其中一個(gè)用戶在數(shù)據(jù)庫(kù)中的生日日期是“1988-07-29”,然而通過(guò)rest接口得到該用戶的生日日期卻為 “1988-07-28”。

環(huán)境說(shuō)明

開(kāi)始bug排查之前,先說(shuō)明下項(xiàng)目環(huán)境:

  • 系統(tǒng):centos 7.5

  • JDK:1.8.0_171

  • 技術(shù)棧:spring boot、Jackson、Druid、mybatis、oracle。

bug 排查

從數(shù)據(jù)層開(kāi)始查找,先查詢數(shù)據(jù)庫(kù)時(shí)間和時(shí)區(qū)。
SQL> SELECT SYSTIMESTAMP, SESSIONTIMEZONE FROM DUAL;
SYSTIMESTAMP                                                                     SESSIONTIMEZONE
-------------------------------------------------------------------------------- ---------------------------------------------------------------------------
17-JUL-19 02.20.06.687149 PM +08:00                                              +08:00

SQL>

數(shù)據(jù)庫(kù)時(shí)間和時(shí)區(qū)都沒(méi)有問(wèn)題。

確認(rèn)操作系統(tǒng)和java進(jìn)程時(shí)區(qū)
  • 查看操作系統(tǒng)時(shí)區(qū)

[test@test ~]$ date -R
Wed, 17 Jul 2019 16:48:32 +0800
[test@test ~]$ cat /etc/timezone
Asia/Shanghai
  • 查看java進(jìn)程時(shí)區(qū)

[test@test ~]$ jinfo 7490 |grep user.timezone
user.timezone = Asia/Shanghai

可以看出我們操作系統(tǒng)使用的時(shí)區(qū)和java進(jìn)程使用的時(shí)區(qū)一致,都是東八區(qū)。

用debug繼續(xù)往上層查找查看mybatis和JDBC層

查看了問(wèn)題字段mapper映射字段的jdbcType類型為jdbcType="TIMESTAMP",在mybatis中類型處理注冊(cè)類TypeHandlerRegistry.java 中對(duì)應(yīng)的處理類為 DateTypeHandler.java。

this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));

進(jìn)一步查看 DateTypeHandler.java 類:

//
// 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;
  }
}

因?yàn)槭褂玫臄?shù)據(jù)源為Druid,其中 getNullableResult(ResultSet rs, String columnName) 方法參數(shù)中 ResultSet使用了DruidPooledResultSet.java 的 getTimestamp(String columnLabel) ,通過(guò)列名稱獲取值然后轉(zhuǎn)換為Date類型的值。

怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題

由上圖debug看到 Timestamp 是JDK中的類,也就是說(shuō)這里看到的是JDK使用的時(shí)間和時(shí)區(qū),從圖中標(biāo)注2處可以看出JDK使用的時(shí)區(qū)也是東八區(qū),但是從1和3處看起來(lái)似乎有點(diǎn)不一樣,首先1處變化為UTC/GMT+0900,3處有一個(gè)daylightSaving的這樣一個(gè)時(shí)間,換算為小時(shí)剛好為1個(gè)小時(shí)。這個(gè)值通過(guò)google搜索知道叫做夏令時(shí)。

常用時(shí)間概念 UTC,GMT,CST,DST
  • UTC協(xié)調(diào)世界時(shí)(英語(yǔ):Coordinated Universal Time,法語(yǔ):Temps Universel Coordonné,簡(jiǎn)稱UTC)是最主要的世界時(shí)間標(biāo)準(zhǔn),其以原子時(shí)秒長(zhǎng)為基礎(chǔ),在時(shí)刻上盡量接近于格林尼治標(biāo)準(zhǔn)時(shí)間。中華民國(guó)采用CNS 7648的《資料元及交換格式–資訊交換–日期及時(shí)間的表示法》(與ISO 8601類似)稱之為世界協(xié)調(diào)時(shí)間。中華人民共和國(guó)采用ISO 8601:2000的國(guó)家標(biāo)準(zhǔn)GB/T 7408-2005《數(shù)據(jù)元和交換格式 信息交換 日期和時(shí)間表示法》中亦稱之為協(xié)調(diào)世界時(shí)。(摘自:https://zh.wikipedia.org/wiki/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6)

  • GMT格林尼治標(biāo)準(zhǔn)時(shí)間(英語(yǔ):Greenwich Mean Time,GMT)是指位于英國(guó)倫敦郊區(qū)的皇家格林尼治天文臺(tái)當(dāng)?shù)氐钠教?yáng)時(shí),因?yàn)楸境踝游缇€被定義為通過(guò)那里的經(jīng)線。(摘自:https://zh.wikipedia.org/wiki/%E6%A0%BC%E6%9E%97%E5%B0%BC%E6%B2%BB%E6%A8%99%E6%BA%96%E6%99%82%E9%96%93)

  • CST北京時(shí)間,又名中國(guó)標(biāo)準(zhǔn)時(shí)間,是中國(guó)大陸的標(biāo)準(zhǔn)時(shí)間,比世界協(xié)調(diào)時(shí)快八小時(shí)(即UTC+8),與香港、澳門、臺(tái)北、吉隆坡、新加坡等地的標(biāo)準(zhǔn)時(shí)間相同。

    北京時(shí)間并不是北京市的地方平太陽(yáng)時(shí)間(東經(jīng)116.4°),而是東經(jīng)120°的地方平太陽(yáng)時(shí)間,二者相差約14.5分鐘[1]。北京時(shí)間由位于中國(guó)版圖幾何中心位置陜西臨潼的中國(guó)科學(xué)院國(guó)家授時(shí)中心的9臺(tái)銫原子鐘和2臺(tái)氫原子鐘組通過(guò)精密比對(duì)和計(jì)算實(shí)現(xiàn)報(bào)時(shí),并通過(guò)人造衛(wèi)星與世界各國(guó)授時(shí)部門進(jìn)行實(shí)時(shí)比對(duì)。(摘自:https://zh.wikipedia.org/wiki/%E5%8C%97%E4%BA%AC%E6%97%B6%E9%97%B4)

  • DST夏時(shí)制(英語(yǔ):daylight time,英國(guó)與其他地區(qū)),又稱夏令時(shí)、日光節(jié)約時(shí)間(英語(yǔ):daylight saving time, DST,美國(guó)),是一種在夏季月份犧牲正常的日出時(shí)間,而將時(shí)間調(diào)快的做法。通常使用夏時(shí)制的地區(qū),會(huì)在接近春季開(kāi)始的時(shí)候,將時(shí)間調(diào)快一小時(shí),并在秋季調(diào)回正常時(shí)間[1]。實(shí)際上,夏時(shí)制會(huì)造成在春季轉(zhuǎn)換當(dāng)日的睡眠時(shí)間減少一小時(shí),而在秋季轉(zhuǎn)換當(dāng)日則會(huì)多出一小時(shí)的睡眠時(shí)間[2][3]。(摘自:https://zh.wikipedia.org/wiki/%E5%A4%8F%E6%97%B6%E5%88%B6)

  • 中國(guó)夏令時(shí)1986年4月,中國(guó)中央有關(guān)部門發(fā)出“在全國(guó)范圍內(nèi)實(shí)行夏時(shí)制的通知”,具體作法是:每年從四月中旬第一個(gè)星期日的凌晨2時(shí)整(北京時(shí)間),將時(shí)鐘撥快一小時(shí),即將表針由2時(shí)撥至3時(shí),夏令時(shí)開(kāi)始;到九月中旬第一個(gè)星期日的凌晨2時(shí)整(北京夏令時(shí)),再將時(shí)鐘撥回一小時(shí),即將表針由2時(shí)撥至1時(shí),夏令時(shí)結(jié)束。從1986年到1991年的六個(gè)年度,除1986年因是實(shí)行夏時(shí)制的第一年,從5月4日開(kāi)始到9月14日結(jié)束外,其它年份均按規(guī)定的時(shí)段施行。在夏令時(shí)開(kāi)始和結(jié)束前幾天,新聞媒體均刊登有關(guān)部門的通告。1992年起,夏令時(shí)暫停實(shí)行。(摘自:https://baike.baidu.com/item/%E5%A4%8F%E4%BB%A4%E6%97%B6)

中國(guó)夏時(shí)制實(shí)施時(shí)間規(guī)定(夏令時(shí))1935年至1951年,每年5月1日至9月30日。 1952年3月1日至10月31日。 1953年至1954年,每年4月1日至10月31日。 1955年至1956年,每年5月1日至9月30日。 1957年至1959年,每年4月1日至9月30日。 1960年至1961年,每年6月1日至9月30日。 1974年至1975年,每年4月1日至10月31日。 1979年7月1日至9月30日。 1986年至1991年,每年4月中旬的第一個(gè)星期日1時(shí)起至9月中旬的第一個(gè)星期日1時(shí)止。具體如下: 1986年4月13日至9月14日, 1987年4月12日至9月13日, 1988年4月10日至9月11日, 1989年4月16日至9月17日, 1990年4月15日至9月16日, 1991年4月14日至9月15日。

通過(guò)對(duì)比我們可以看到應(yīng)用中的對(duì)應(yīng)的用戶生日"1988-07-29"剛好在中國(guó)的夏令時(shí)區(qū)間內(nèi),因?yàn)槲覀儾僮飨到y(tǒng)、數(shù)據(jù)庫(kù)、JDK使用的都是 "Asia/Shanghai" 時(shí)區(qū),應(yīng)該不會(huì)錯(cuò),通過(guò)上圖中debug結(jié)果我們也證實(shí)了結(jié)果是沒(méi)問(wèn)題的。

繼續(xù)往外排查業(yè)務(wù)層和接口層,定位到問(wèn)題

項(xiàng)目使用的是spring boot提供rest接口返回json報(bào)文,使用spring 默認(rèn)的Jackson框架解析。項(xiàng)目中有需要對(duì)外輸出統(tǒng)一日期格式,對(duì)Jackson做了一下配置:

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

我們通過(guò)查看 JacksonProperties.java源碼:

//
// 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 屬性操作的就是java.util.TimeZone。于是我們通過(guò)一段測(cè)試代碼模擬轉(zhuǎn)換過(guò)程:

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("當(dāng)前的默認(rèn)時(shí)區(qū)為: " + TimeZone.getDefault().getID());
		Date date1 = Date.valueOf("1988-07-29");
		Date date2 = Date.valueOf("1983-07-29");
		System.out.println("在中國(guó)夏令時(shí)范圍內(nèi)的時(shí)間 date1=" + date1);
		System.out.println("正常東八區(qū)時(shí)間 date2=" + date2);
//		模擬 spring.jackson.time-zone=GMT+8 屬性設(shè)置
		TimeZone zone = TimeZone.getTimeZone("GMT+8");
		TimeZone.setDefault(zone);
		System.out.println(TimeZone.getDefault().getID());
		Date date3 = date1;
		Date date4 = date2;
		System.out.println("轉(zhuǎn)換后的在中國(guó)夏令時(shí)范圍內(nèi)的時(shí)間date3=" + date3);
		System.out.println("轉(zhuǎn)換后的正常東八區(qū)時(shí)間 date4=" + date4);
	}
}

運(yùn)行后輸出結(jié)果:

當(dāng)前的默認(rèn)時(shí)區(qū)為: Asia/Shanghai
在中國(guó)夏令時(shí)范圍內(nèi)的時(shí)間 date1=1988-07-29
正常東八區(qū)時(shí)間 date2=1983-07-29
GMT+08:00
轉(zhuǎn)換后的在中國(guó)夏令時(shí)范圍內(nèi)的時(shí)間date3=1988-07-28
轉(zhuǎn)換后的正常東八區(qū)時(shí)間 date4=1983-07-29

從這里終于找到問(wèn)題發(fā)生點(diǎn)了,從debug那張圖我們看出了因?yàn)槟莻€(gè)日期是在中國(guó)的夏令時(shí)區(qū)間內(nèi),要快一個(gè)小時(shí),使用了UTC/GMT+0900的格式,而jackjson在將報(bào)文轉(zhuǎn)換為json格式的時(shí)候使用的是UTC/GMT+0800的格式。也就是說(shuō)我們將JDK時(shí)區(qū)為UTC/GMT+0900的"1988-07-29 00:00:00"這樣的一個(gè)時(shí)間轉(zhuǎn)換為了標(biāo)準(zhǔn)東八區(qū)的UTC/GMT+0800格式的時(shí)間,需要先調(diào)慢一個(gè)小時(shí)變成了"1988-07-28 23:00:00"。

bug解決

定位到問(wèn)題解決就很簡(jiǎn)單了,只需要修改下設(shè)置:

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

保持時(shí)區(qū)一致問(wèn)題得到解決。

到此,相信大家對(duì)“怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

當(dāng)前題目:怎么排查因JDK導(dǎo)致接口輸出日期格式的時(shí)間與預(yù)期時(shí)間不一致問(wèn)題
文章出自:http://aaarwkj.com/article30/pjdjpo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航營(yíng)銷型網(wǎng)站建設(shè)、手機(jī)網(wǎng)站建設(shè)做網(wǎng)站、響應(yīng)式網(wǎng)站、網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)
一本色道久久亚洲综合精品蜜桃| 国产亚洲欧美日韩激情在线| 国产中文字幕婷婷丁香| 亚洲av成人噜噜网站| 国语自产拍在线观看不卡| 狠狠综合久久av一区二区大宝| 日韩在线视频一区二区三| 成人国产在线欧美精品| 韩日av一区二区三区| 日韩精品一区二区三区av在线| 亚洲日日夜夜噜噜爽爽| 欧美视频亚洲视频自拍视频| 久草手机福利在线观看| 中文字幕伦理一区二区| 99久久久精品国产免费| 亚洲欧美韩国日本成人综合| 日本束缚人妻一区二区三区| 99精品国产一区二区青青性色| 欧美另类不卡在线观看| 高清欧美精品一区二区三区| 国产女人高潮流白丝视频| 日本福利资源在线观看| 最新日本人妻中文字幕| av在线中文字幕剧情| 婷婷激情六月中文字幕| 国产三级三级三级三级三级| 97成人在线免费视频| 蜜臀久久精品亚洲一区| 欧美午夜福利视频电影| 中文字幕国产精品经典三级| 成人在线午夜你懂的视频| 男人自拍天堂在线视频| 中高龄夫妇五十路六十路| 男女做爰高清无遮挡免费| 亚洲av日韩高清在线观看 | 国产精品欧美一区二区视频| 国产一区在线免费在线观看| 国产成人免费视频一区| 日韩国产精品视频二区| 成人在线视频国产自拍| 成人黄色av免费看|