數據庫時區那些事兒 - Oracle的時區處理

原文地址html

當JVM時區和數據庫時區不一致的時候,會發生什麼?這個問題也許你歷來沒有注意過,可是當把Java程序容器化的時候,問題就浮現出來了,由於目前幾乎全部的Docker Image的時區都是UTC。本文探究了Oracle及其JDBC驅動對於時區的處理方式,並嘗試給出最佳實踐。java

先給總結

  • DATETIMESTAMP類型不支持時區轉換。
  • 若是應用和Oracle的時區不一致,那麼應該使用TIMESTAMP WITH LOCAL TIME ZONEgit

    • 對於JDBC程序來講,JVM時區和用戶時區保持一致就好了。
  • 若是應用和Oracle的時區不一致,並且須要保存時區信息,那麼應該使用TIMESTAMP WITH TIME ZONE
  • 格式化日期時間字符串函數TO_CHARgithub

    • 對於TIMESTAMP WITH TIME ZONE來講,使用TO_CHAR時要注意讓它輸出時區信息(TZH:TZM TZR TZD),不然結果會是截斷的。
    • 對於TIMESTAMP WITH LOCAL TIME ZONE來講,使用TO_CHAR返回的結果會轉換時區。
  • 當前日期時間的函數:sql

    • 除非必要,不要使用SYSDATESYSTIMESTAMP,這個返回的是數據庫所在操做系統的時間。
    • 儘可能使用CURRENT_TIMESTAMP,它返回的是TIMESTAMP WITH TIME ZONE,可以用來安全的比較時間。

日期時間類型的時區

Oracle Datetime Datatypes有這麼幾種:docker

  • DATE,保存YYYY-MM-DD HH24:MI:SS
  • TIMESTAMP,比DATE多存了fractional seconds(FF)。
  • TIMESTAMP WITH TIME ZONE,比TIMESTAMP多了時區偏移量(好比+08:00,TZH:TZM)or 時區區域名稱(好比Asia/Shanghai,TZR)和夏令時標記(TZD)。
  • TIMESTAMP WITH LOCAL TIME ZONE。和TIMESTAMP相似,不過存儲的數據會標準化爲數據庫的時區,用戶獲取它的時候會轉換成用戶時區(對於JDBC來講,就是JVM時區)。
docker run --name oracle-xe-timezone-test \
  -e ORACLE_ALLOW_REMOTE=true \
  -p 1521:1521 \
  -d wnameless/oracle-xe-11g:16.04

而後用system/oracle用戶登陸到oracle,執行下列sql建表:數據庫

create table test (
  date_field date,
  ts_field timestamp,
  ts_tz_field timestamp with time zone,
  ts_ltz_field timestamp with local time zone
);

爲了驗證這個結論,我寫了一段程序來實驗,這個程序作了三件事情:安全

  1. 使用Asia/Shanghai時區構造一個日期java.util.Date:2018-09-14 10:00:00,而後插入到數據庫裏。
  2. 使用Asia/Shanghai時區把這個值再查出來,看看結果。
  3. 使用Asia/Shanghai時區,得到這個字段的格式化字符串(使用DATE_FORMAT()函數)。
  4. 使用Europe/Paris時區重複第2-3步的動做。

運行程序得到如下結果:bash

JVM Time Zone      : 中國標準時間
Retrieve java.util.Date from DATE column                              : 2018-09-14 10:00:00.0
Retrieve java.util.Date from TIMESTAMP column                         : 2018-09-14 10:00:00.0
Retrieve java.util.Date from TIMESTAMP WITH TIME ZONE column          : 2018-09-14 10:00:00.0
Retrieve java.util.Date from TIMESTAMP WITH LOCAL TIME ZONE column    : 2018-09-14 10:00:00.0
Retrieve formatted string from DATE column                            : 2018-09-14 10:00:00
Retrieve formatted string from TIMESTAMP column                       : 2018-09-14 10:00:00
Retrieve formatted string from TIMESTAMP WITH TIME ZONE column        : 2018-09-14 10:00:00 +08:00 ASIA/SHANGHAI CST
Retrieve formatted string from TIMESTAMP WITH LOCAL TIME ZONE column  : 2018-09-14 10:00:00
--------------------
JVM Time Zone      : 中歐時間
Retrieve java.util.Date from DATE column                              : 2018-09-14 10:00:00.0
Retrieve java.util.Date from TIMESTAMP column                         : 2018-09-14 10:00:00.0
Retrieve java.util.Date from TIMESTAMP WITH TIME ZONE column          : 2018-09-14 04:00:00.0
Retrieve java.util.Date from TIMESTAMP WITH LOCAL TIME ZONE column    : 2018-09-14 04:00:00.0
Retrieve formatted string from DATE column                            : 2018-09-14 10:00:00
Retrieve formatted string from TIMESTAMP column                       : 2018-09-14 10:00:00
Retrieve formatted string from TIMESTAMP WITH TIME ZONE column        : 2018-09-14 10:00:00 +08:00 ASIA/SHANGHAI CST
Retrieve formatted string from TIMESTAMP WITH LOCAL TIME ZONE column  : 2018-09-14 04:00:00

能夠看到,DATETIMESTAMP是不支持時區轉換的,實際上DATETIMESTAMP會丟棄掉時區信息。session

對於TIMESTAMP WITH TIME ZONE來講,使用TO_CHAR時要注意讓它輸出時區信息(TZH:TZM TZR TZD),不然結果會是截斷的。

對於TIMESTAMP WITH LOCAL TIME ZONE來講,使用TO_CHAR返回的結果會轉換時區。

當前日期時間相關函數

Oracle和當前時間有關的函數有這麼幾個:

  • CURRENT_DATE,返回的是DATE類型
  • CURRENT_TIMESTAMP,返回的是TIMESTAMP WITH TIME ZONE類型
  • LOCALTIMESTAMP,返回的是TIMESTAMP類型
  • SYSDATE,返回的是DATE類型
  • SYSTIMESTAMP,返回的是TIMESTAMP類型

寫了一段程序,輸出結果是這樣的:

=========TEST CURRENT DATE/TIME FUNCTIONS===========
JVM Time Zone               : 中國標準時間
Test CURRENT_DATE           : 2018-09-18 10:27:23.0
Test CURRENT_TIMESTAMP      : 2018-09-18 10:27:23.880378 Asia/Shanghai
Test LOCALTIMESTAMP         : 2018-09-18 10:27:23.926375
Test SYSDATE                : 2018-09-18 02:27:23.0
Test SYSTIMESTAMP           : 2018-09-18 02:27:23.929605 +0:00
--------------------
JVM Time Zone               : 中歐時間
Test CURRENT_DATE           : 2018-09-18 04:27:45.0
Test CURRENT_TIMESTAMP      : 2018-09-18 04:27:45.429024 Europe/Paris
Test LOCALTIMESTAMP         : 2018-09-18 04:27:45.482485
Test SYSDATE                : 2018-09-18 02:27:45.0
Test SYSTIMESTAMP           : 2018-09-18 02:27:45.48582 +0:00

能夠發現,CURRENT_DATECURRENT_TIMESTAMPLOCALTIMESTAMP的結果都根據客戶端時區作了轉換。而SYSDATESYSTIMESTAMP返回的則是數據庫所在操做系統所在時區的時間。

在Oracle客戶端操做時區

-- 查詢系統時區和session時區
SELECT DBTIMEZONE, SESSIONTIMEZONE FROM DUAL;

-- 設置session時區
ALTER SESSION SET TIME_ZONE='Asia/Shanghai';

參見Setting the Database Time ZoneSetting the Session Time Zone

參考資料

相關代碼

https://github.com/chanjarste...

相關文章
相關標籤/搜索