LoginSignup
5
6

More than 5 years have passed since last update.

PostgreSQLのTIMESTAMP WITH TIME ZONE型のデータをクライアントのタイムゾーンで扱いたい

Last updated at Posted at 2016-08-26

確認したいこと

例えば、クライアントが日本にあってデータベース・サーバーが台北にあるとき、TIMESTAMP WITH TIME ZONE型の列に日本のタイムゾーンでデータを取り扱いたい。

準備

データベース・サーバーのタイムゾーンがAsia/Taipei担っていることを確認。

確認のためのテーブルを生成

create_table.sql
CREATE TABLE table1
(
    id          INTEGER
  , date_tz     TIMESTAMP WITH TIME ZONE
  , description TEXT
);

TIMESTAMP WITH TIME ZONE型の列にTO_DATE()でdate型のデータを入れる

insert_data1.sql
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_data2.sql
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_data3.sql
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を使ってやりたいので、文字型にタイムゾーンを付けるやり方はちょっとダメかな。もっと良い方法を考えてみる。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6