昨天碰見一個問題,需要從數據庫中讀取數據傳輸給其他平台,從其他平台查看數據的時候發現時間不正確,多了8個小時,查看接收日志,接收到的時間是不正確,說明發送的時候應該就是不正確的,從發送程序查,發現發送的時候就是不正確的,而數據庫的時間是正確的,上網查了下,發現是連接數據庫的時候設置的時區不正確,連接數據庫時設置的是serverTimezone=UTC
,UTC是標准時間,比中國時間(東八區)時間早了8個小時,所以導致的不正確。網上的結論到這里就結束了,感覺還少了一部分,又上網查了下java程序運行的時候也有自己的時區,如果不設置的話一般會從系統中取,如果設置數據庫的時區和系統時區一致,那沒有問題,如果時間不一致的話,就會出現問題。
獲取當前程序所用時區:
public static void main(String[] args) {
Calendar ca = Calendar.getInstance();
TimeZone tz = ca.getTimeZone();
System.out.println(tz.getID());
}
控制台輸出:Asia/Shanghai
,而連接數據庫的時候設置的時區是serverTimezone=UTC
這時候就會出現問題,完整的流程應該是,程序從數據庫獲取時間,獲取時間的時候會看根據連接數據庫的時候設置的時區進行轉化,比如數據庫時間是2021-11-30 00:00:00,但是程序從數據庫取到后會比較兩者的時區,時區不一致時會進行轉化,UTC要比東八區慢八個小時,所以取到時間后會增加8個小時,獲取的時間是2021-11-30 08:00:00,到這里算是一個完成流程。
既然存入的時候會根據時區修改時間,那存入的時候也會做對應的修改,程序設置的時區是Asia/Shanghai
,連接MySql時設置的時區是UTC
,如果程序接收到時間是2021-11-30 00:00:00,那存入到數據庫中的時間為2021-10-31 16:00:00,數據庫的時間比程序設定時間慢8個小時,所以會再減少8個小時。
將連接數據庫的serverTimezone設置為serverTimezone=Asia/Shanghai
或者serverTimezone=GMT%2B8
即可解決問題。
到這里問題已經解決,后來看了下源碼,程序會獲取系統時間,如果獲取不到的話會用默認時間GMT
,也是0時區。
下面是部分源碼:
private static synchronized TimeZone setDefaultZone() {
TimeZone tz;
// get the time zone ID from the system properties
String zoneID = AccessController.doPrivileged(
new GetPropertyAction("user.timezone"));
// if the time zone ID is not set (yet), perform the
// platform to Java time zone ID mapping.
if (zoneID == null || zoneID.isEmpty()) {
String javaHome = AccessController.doPrivileged(
new GetPropertyAction("java.home"));
try {
zoneID = getSystemTimeZoneID(javaHome);
if (zoneID == null) {
zoneID = GMT_ID;
}
} catch (NullPointerException e) {
zoneID = GMT_ID;
}
}
// Get the time zone for zoneID. But not fall back to
// "GMT" here.
tz = getTimeZone(zoneID, false);
if (tz == null) {
// If the given zone ID is unknown in Java, try to
// get the GMT-offset-based time zone ID,
// a.k.a. custom time zone ID (e.g., "GMT-08:00").
String gmtOffsetID = getSystemGMTOffsetID();
if (gmtOffsetID != null) {
zoneID = gmtOffsetID;
}
tz = getTimeZone(zoneID, true);
}
assert tz != null;
final String id = zoneID;
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
System.setProperty("user.timezone", id);
return null;
}
});
defaultTimeZone = tz;
return tz;
}
源碼看到這里的時候,已經有native方法了,搜了下,沒想到網上有大佬把調用的方法也寫出來了,Java讀取系統默認時區 ,這位老哥寫的確實挺全的,想看的同學可以看下。