確認したいこと
例えば、クライアントが日本にあってデータベース・サーバーが台北にあるとき、TIMESTAMP WITH TIME ZONE型の列に日本のタイムゾーンでデータを取り扱いたい。
準備
データベース・サーバーのタイムゾーンがAsia/Taipei担っていることを確認。
確認のためのテーブルを生成
CREATE TABLE table1
(
id INTEGER
, date_tz TIMESTAMP WITH TIME ZONE
, description TEXT
);
TIMESTAMP WITH TIME ZONE型の列にTO_DATE()でdate型のデータを入れる
INSERT INTO table1 (id, date_tz, description) VALUES (
1
, TO_DATE('2016-01-01','YYYY-MM-DD')
, 'TO_DATE(''2016-01-01'')'
);
確認すると
SELECT id, date_tz, description FROM table1 ORDER BY id;
id | date_tz | description
----+------------------------+----------------------------------
1 | 2016-01-01 00:00:00+08 | TO_DATE('2016-01-01')
(1 行)
となり、サーバーのタイムゾーンでデータが入っている。
でも、ここで入れたいのは、日本時間の2016年1月1日0時であって、台北時間の2016年1月1日0時ではない。
タイムゾーンを指定してデータを入れてみる
AT TIME ZONEを使って、タイムゾーンを指定してデータを入れてみた。
INSERT INTO table1 (id, date_tz, description) VALUES (
2
, TO_DATE('2016-01-01','YYYY-MM-DD') AT TIME ZONE 'Asia/Tokyo'
, 'TO_DATE(''2016-01-01'') AT TIME ZONE ''Asia/Tokyo'''
);
確認すると
SELECT id, date_tz, description FROM table1 ORDER BY id;
id | date_tz | description
----+------------------------+-------------------------------------------------
1 | 2016-01-01 00:00:00+08 | TO_DATE('2016-01-01')
2 | 2016-01-01 01:00:00+08 | TO_DATE('2016-01-01') AT TIME ZONE 'Asia/Tokyo'
(2 行)
id=2のdate_tzは、台北時間の2016年1月1日1時になってしまった。
やりたかったことは、台北(Asia/Taipei)にあるデータベースサーバーのTIMESTAMP WITH TIME ZONE型の列に、日本時間の2016年1月1日0時のデータを書き込みたかったので、id=2にdate_tzには
2016-12-31 23:00:00+08
が入って欲しかったのだけど。
なぜこうなったのか?
まず、TO_DATE('2016-01-01') AT TIME ZONE 'Asia/Tokyo'
がどうなるのか。
SELECT TO_DATE('2016-01-01','YYYY-MM-DD') AT TIME ZONE 'Asia/Tokyo';
timezone
---------------------
2016-01-01 01:00:00
(1 行)
結果の後ろに+09とか+08が付いてないので、きっとこれはTIMESTAMP WITHOUT TIME ZONE型になっているのではないか。
TIMESTAMP WITHOUT TIME ZONE型をTIMESTAMP WITH TIME ZONE型にCASTされてdate_tz列に入れられるのだろうから、
SELECT (TO_DATE('2016-01-01','YYYY-MM-DD') AT TIME ZONE 'Asia/Tokyo')::TIMESTAMP WITH TIME ZONE;
timezone
------------------------
2016-01-01 01:00:00+08
(1 行)
この結果がdate_tz列に入れられているはず。
insert_data2.sqlの結果とも一致しているので、きっとそうだろう。
つまり、TO_DATE('2016-01-01','YYYY-MM-DD') AT TIME ZONE 'Asia/Tokyo'
は、台北時間2016年1月1日0時の日本時間は?ということで、結果として2016年1月1日1時という答えが返ってきている。
この結果がAsia/TaipeiのTIMESTAMP WITH TIME ZONE型にCASTされるので、
2016-01-01 01:00:00+08
という最終結果になる。
なので、このやり方は間違い。
たぶんこうすればいいのかな?
文字型の'2016-01-01 Asia/Tokyo'をTIMESTAMP WITH TIME ZONE型にCASTしてやると良さそう。
SELECT '2016-01-01 Asia/Tokyo'::TIMESTAMP WITH TIME ZONE;
timestamptz
------------------------
2015-12-31 23:00:00+08
(1 行)
これでINSERTしてみると
INSERT INTO table1 (id, date_tz, description) VALUES (
3
, '2016-01-01 Asia/Tokyo'
, '''2016-01-01 Asia/Tokyo'''
);
結果は
SELECT id, date_tz, description FROM table1 ORDER BY id;
id | date_tz | description
----+------------------------+-------------------------------------------------
1 | 2016-01-01 00:00:00+08 | TO_DATE('2016-01-01')
2 | 2016-01-01 01:00:00+08 | TO_DATE('2016-01-01') AT TIME ZONE 'Asia/Tokyo'
3 | 2015-12-31 23:00:00+08 | '2016-01-01 Asia/Tokyo'
(3 行)
id=3のdate_tzは、台北時間の2015年12月31日23時となり、狙った結果になった。
結論
タイムゾーンの異なるデータベースサーバーにTIMESTAMP WITH TIME ZONE型のデータを入れるときには、AT TIME ZONEで指定するのではなく、文字型にタイムゾーンの情報を加えてからTIMESTAMP WITH TIME ZONE型にCASTする。
でも...
最終的にはADO.NETでDataAdapterを使ってやりたいので、文字型にタイムゾーンを付けるやり方はちょっとダメかな。もっと良い方法を考えてみる。