iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 12
1
Postgrsql Timezone 及時間戳記資料型態

今天來探討時區跟小伙伴們.

首先來看怎樣查現在 Server 設定的時區.

select current_setting('TIMEZONE');
+-----------------+
| current_setting |
+-----------------+
| Asia/Taipei     |
+-----------------+

show timezone;
+-------------+
|  TimeZone   |
+-------------+
| Asia/Taipei |
+-------------+

查現在時區與 UTC 的 offset

select extract(timezone from now()) / 3600 as "utc_offset"
     , date_part('timezone', now()) / 3600 as "utc_offset_2";

+------------+--------------+
| utc_offset | utc_offset_2 |
+------------+--------------+
|          8 |            8 |
+------------+--------------+

我們可以透過 pg_catalog.pg_timezone_names 查詢時區相關資料

show intervalstyle;
+---------------+
| IntervalStyle |
+---------------+
| iso_8601      |
+---------------+

select *
  from pg_catalog.pg_timezone_names
 where name ilike '%taipei%';
 
+-------------+--------+------------+--------+
|    name     | abbrev | utc_offset | is_dst |
+-------------+--------+------------+--------+
| Asia/Taipei | CST    | PT8H       | f      |
+-------------+--------+------------+--------+

在 Postgresql 內含的時區資料中, Asia/Taipei 對應的縮寫是CST,UTC偏移使用
iso_8601 表示為 8小時, is_dst 代表是否 日光節約時間.

也可以查詢 pg_timezone_abbrevs 獲得一些時區縮寫的相關資料

select utc_offset, is_dst
     , string_agg(abbrev, ' ' order by abbrev) as abbrevs
     , count(*) as cnt
  from pg_timezone_abbrevs
 group by utc_offset, is_dst
 order by utc_offset, is_dst;

實際上有61筆,僅列出 標準時區 
+-[ RECORD 23 ]--------------------------+
| utc_offset | PT0S                      |
| is_dst     | f                         |
| abbrevs    | GMT UCT UT UTC WET Z ZULU |
| cnt        | 7                         |

接著來探討 timestamp 與 timestamp with time zone 這兩個資料型態.

select pg_column_size(timestamp without time zone 'now')
     , pg_column_size(timestamp with time zone 'now');
+----------------+----------------+
| pg_column_size | pg_column_size |
+----------------+----------------+
|              8 |              8 |
+----------------+----------------+

兩種型態都是 8 bytes.

timestamp  相信大家比較熟悉,以下搭配 timestamp with time zone 
做一些輸入的方式介紹

為了舉例方便,查詢兩個台北附近,有點熱的城市,看她們的時區.

select *  
  from pg_catalog.pg_timezone_names
 where name ~* 'tokyo|Ho_Chi_Minh';

+------------------+--------+------------+--------+
|       name       | abbrev | utc_offset | is_dst |
+------------------+--------+------------+--------+
| Asia/Tokyo       | JST    | PT9H       | f      |
| Asia/Ho_Chi_Minh | +07    | PT7H       | f      |
+------------------+--------+------------+--------+

select '2019-09-26'::timestamp
     , timestamp '2019-09-26 16:41:39';

+---------------------+---------------------+
|      timestamp      |      timestamp      |
+---------------------+---------------------+
| 2019-09-26 00:00:00 | 2019-09-26 16:41:39 |
+---------------------+---------------------+

select '2019-09-26 02:41:39 UTC'::timestamptz
     , timestamptz '2019-09-26 02:41:39 Zulu';

+------------------------+------------------------+
|      timestamptz       |      timestamptz       |
+------------------------+------------------------+
| 2019-09-26 10:41:39+08 | 2019-09-26 10:41:39+08 |
+------------------------+------------------------+

Zulu 是美軍常用的代號. 顯示時是以現在的 timezone 來表示.
因為 Taipei 時間,相對是 UTC +08 , 但是表示法後面的 +08 
有時候會讓人有一個還要再加8的感覺...這個要忍住衝動,不要心算再加上去.

不過別擔心,再搭配以下的例子.

select timestamptz '2019-09-26 10:41:39+08' at time zone 'Asia/Taipei' as "Taiei#1"
     , timestamptz '2019-09-26 02:41:39 Zulu' at time zone 'Asia/Taipei' as "Taiei#2"
     , timestamptz '2019-09-26 10:41:39+08' at time zone 'Asia/Tokyo' as "Tokyo";
+---------------------+---------------------+---------------------+
|       Taiei#1       |       Taiei#2       |        Tokyo        |
+---------------------+---------------------+---------------------+
| 2019-09-26 10:41:39 | 2019-09-26 10:41:39 | 2019-09-26 11:41:39 |
+---------------------+---------------------+---------------------+

注意這次是用 timestamp 

select timestamp '2019-09-26 10:41:39' at time zone 'Asia/Taipei' as "Taiei"
     , timestamp '2019-09-26 11:41:39' at time zone 'Asia/Tokyo' as "Tokyo";

+------------------------+------------------------+
|         Taiei          |         Tokyo          |
+------------------------+------------------------+
| 2019-09-26 10:41:39+08 | 2019-09-26 10:41:39+08 |
+------------------------+------------------------+

通常的說法,東京比台北快一小時.也就是說當東京11點時,台北是10點.
有沒有注意到,這個 at time zone 子句, 會將有時區的轉換成無時區的當地時間,反之亦然.

select timestamp '2019-09-26 10:41:39' at time zone 'Asia/Taipei' at time zone 'Asia/Taipei' as "Taiei Local Again";
+---------------------+
|  Taiei Local Again  |
+---------------------+
| 2019-09-26 10:41:39 |
+---------------------+

昨天在討論 current_timestamp, clock_timestamp() 時,有注意到 current_timestamp 是以 transaction 時刻.

current 開頭的日期時間函數,還有 local 開頭的日期時間函數,都是 start time of the current transaction.
這些都是 SQL 標準的.
Postgresql 另外有 now(), 這是等於 transaction_timestamp() 也等於 CURRENT_TIMESTAMP,方便使用時可以少打字.
另外有statement_timestamp(),顧名思義,就是每道語句的.當然還有 clock_timestamp().

接著我們來看 transaction 裡面,變更 timezone, 然後輸入時間戳,會是怎樣的情況.

create table ithelp190926 (
  purets timestamp
, tstz timestamp with time zone
);

commit;

begin;

insert into ithelp190926 (purets, tstz)
select statement_timestamp(), statement_timestamp();

insert into ithelp190926 (purets, tstz)
select now(), now();

set timezone to 'Asia/Tokyo';

insert into ithelp190926 (purets, tstz)
select now(), now();

set timezone to 'Asia/Ho_Chi_Minh';

insert into ithelp190926 (purets, tstz)
select now(), now();

set timezone to 'Asia/Taipei';

commit;

-- transaction end

來看一下輸入的資料

select * from ithelp190926;

+----------------------------+-------------------------------+
|           purets           |             tstz              |
+----------------------------+-------------------------------+
| 2019-09-26 21:22:49.068371 | 2019-09-26 21:22:49.068371+08 |
| 2019-09-26 21:21:59.462832 | 2019-09-26 21:21:59.462832+08 |
| 2019-09-26 22:21:59.462832 | 2019-09-26 21:21:59.462832+08 |
| 2019-09-26 20:21:59.462832 | 2019-09-26 21:21:59.462832+08 |
+----------------------------+-------------------------------+
(4 rows)

注意到 statement_timestamp() 的時刻,比較晚. now() 就是 transaction 開始的時刻.
有 with time zone 不管時區怎樣改變,實際上的時刻都是一樣的,
表示法是以最後設定回來的台北來表示.

現在把時區調成東京.

set timezone to 'Asia/Tokyo';

select * from ithelp190926;

+----------------------------+-------------------------------+
|           purets           |             tstz              |
+----------------------------+-------------------------------+
| 2019-09-26 21:22:49.068371 | 2019-09-26 22:22:49.068371+09 |
| 2019-09-26 21:21:59.462832 | 2019-09-26 22:21:59.462832+09 |
| 2019-09-26 22:21:59.462832 | 2019-09-26 22:21:59.462832+09 |
| 2019-09-26 20:21:59.462832 | 2019-09-26 22:21:59.462832+09 |
+----------------------------+-------------------------------+

可以觀察到, 有時區的很方便.
還是要記得調回台北喔

set timezone to 'Asia/Taipei';

根據上面的研討,兩種所佔用的空間是一樣的,都是 8 bytes.
有時區的會自動調整,使用上很方便.
有些人主張,全部存成 UTC 的,然後再自行計算調整.這也是一種方法.
若以使用 Postgresql 為主的情況下,使用有時區的格式,佔用空間相同,
而且帶來許多便利,是不錯的選擇.


上一篇
Postgresql 的日期時間資料型態
下一篇
Postgresql 日期時間資料型態的interval
系列文
以Postgresql為主,聊聊資料庫.31

1 則留言

我要留言

立即登入留言