PostgreSQLのtimestamptzカラムにAT TIME ZONEを使うと型がtimestampに変わる

  • PostgreSQL
  • Database

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

前提

これは timestamptz(timestamp with time zone)型のカラムに対する話。timestamp(タイムゾーンなし)型のカラムに AT TIME ZONE を使う場合は挙動が逆になるので、それは別の話として分けて考える必要がある。

結論

timestamptz 型のカラムに AT TIME ZONE 'zone名' を使うと、内部でUTCとして保持されている値を指定zoneのローカル時刻にシフトして、timestamp without time zone として返す。この時点でタイムゾーン情報は落ちる。

SELECT example_timestamptz_column AT TIME ZONE 'Asia/Tokyo' AS local_time
FROM example_table;

example_timestamptz_column が timestamptz 型なら、これはUTC基準の値をAsia/Tokyoのローカル時刻(壁時計時刻)に変換した結果になる。返ってくる型は timestamp without time zone。

なぜこうなるか

timestamptz は実体としてUTCのインスタント(1つの絶対時刻)しか持っていない。表示するときにセッションの timezone 設定を使って変換しているだけで、値自体にゾーン情報が埋め込まれているわけではない。

AT TIME ZONE はこのセッションタイムゾーンに依存した変換を、明示的に特定のzoneで行うための構文。だから

SELECT example_timestamptz_column AT TIME ZONE 'Asia/Tokyo' FROM example_table;

は「セッションのtimezone設定が何であっても、Asia/Tokyoで見たときの時刻をくれ」という意味になる。

timestamp(タイムゾーンなし)型との違い

逆に timestamp without time zone 型のカラムに AT TIME ZONE 'zone名' を使うと、その素の値を「指定zoneのローカル時刻として解釈」して timestamptz に変換する。タイムゾーン情報がない → ある、への変換なので方向が逆。

SELECT example_naive_column AT TIME ZONE 'Asia/Tokyo' FROM example_table;

example_naive_column が timestamp 型なら、この結果は timestamptz。

つまり同じ構文でも入力の型で意味が真逆になる。

入力の型 AT TIME ZONE後の型 やってること
timestamptz timestamp UTC → 指定zoneのローカル時刻にシフト、zone情報は落ちる
timestamp timestamptz 素の値を指定zoneのローカル時刻とみなして、zone情報を付与

ハマりポイント

timestamp without time zone 型のカラムに実質UTCの値を運用で入れている(PostgreSQL的には非推奨だけど実務ではよくある)場合、そのカラムに AT TIME ZONE 'Asia/Tokyo' を使うと「UTC→Tokyo変換」にはならない。素の値がすでにAsia/Tokyoのローカル時刻として書かれたものだと解釈されてしまうので、意図と逆の処理になる。

このケースでUTC→Tokyo変換をしたいなら、一度UTCとして明示してからシフトする必要がある。

SELECT (example_naive_utc_column AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Tokyo' AS tokyo_time
FROM example_table;

最初の AT TIME ZONE 'UTC' で timestamp → timestamptz に変換(素の値をUTCのローカル時刻とみなす)、次の AT TIME ZONE 'Asia/Tokyo' で timestamptz → timestamp にシフト、という2段階。

セッション設定周り

SHOW TIMEZONE; で現在のセッションのタイムゾーン設定を確認できる。SET TIMEZONE = 'Asia/Tokyo'; はセッション内でのみ有効。恒久的に変えるなら postgresql.conf の timezone パラメータ。

Asia/Tokyoは年間を通してUTC+9固定でDSTはない。この点は他の地域のタイムゾーンを扱うときとは違うので、DSTがあるzoneで同じ処理をするとオフセットが季節によって変わる点は実機で未確認、ドキュメント上の記載を前提にしている。