web-dev-qa-db-ja.com

PostgreSQLがタイムゾーンのないタイムスタンプからタイムゾーンのあるタイムスタンプに誤って変換する

今朝、次の問題に直面しました。

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';

私を返します2011-12-30 05:30:00+00魔女は間違っています。

しかし、次のクエリは次のとおりです。

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'UTC-5';
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';

正しい日付が表示されます2011-12-29 19:30:00

私のローカルタイムゾーンについての質問を防ぐ:

SELECT  current_setting('TIMEZONE');
current_setting
-----------------
     UTC
(1 row)

Postgresqlがtimestamp without time zoneいくつかの奇妙な方法で、代わりに5時間を費やしますか?

47
saicheg

理解すべき重要なこと

_timestamp without time zone AT TIME ZONE_ re-interprets a timestampはそのタイムゾーンにあるとTCに変換するために

_timestamp with time zone AT TIME ZONE_ converts a指定されたタイムゾーンでtimestamptztimestampに変換します。

PostgreSQLはISO-8601タイムゾーンを使用します。これは、グリニッジの東が正であることを指定します... POSIXタイムゾーン指定子を使用しない限り、その場合はPOSIXに従います。狂気が続きます。

最初のものが予期しない結果を生成する理由

SQLのタイムスタンプとタイムゾーンは恐ろしいものです。この:

_select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
_

未知の型のリテラル_'2011-12-30 00:30:00'_を_timestamp without time zone_として解釈します。Pgは、特に指定がない限り、ローカルのTimeZoneにあると想定します。 _AT TIME ZONE_を使用すると、(仕様により)re-interpretedとしてタイムゾーン_timestamp with time zone_として_EST5EDT_になり、UTCの絶対時間として保存されます-したがって、変換されますfrom _EST5EDT_ to UTC、つまりタイムゾーンオフセットは減算になります。 x - (-5)は_x + 5_です。

UTCストレージに調整されたこのタイムスタンプは、サーバーのTimeZone設定に合わせて調整され、ローカル時間で表示されます。

代わりに「UTC時間でこのタイムスタンプがあり、EST5EDTでの同等の現地時間を確認したい」と言いたい場合、サーバーのTimeZone設定から独立したい場合は、次のように書く必要があります。

_select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE 'EST5EDT';
_

これは、「タイムスタンプ2011-12-30 00:30:00を指定し、timestamptzに変換するときにUTCのタイムスタンプとして処理し、EST5EDTでそのtimestamptzを現地時間に変換します」と言います。

恐ろしいですね。 _AT TIME ZONE_のクレイジーなセマンティクスを決定した人は誰でも会社と話し合うを送りたい-それは本当に_timestamp CONVERT FROM TIME ZONE '-5'_や_timestamptz CONVERT TO TIME ZONE '+5'_のようなものであるべきだ。また、_timestamp with time zone_は実際にタイムゾーンを保持する必要があります。UTCに保存されず、ローカル時間に自動変換されます。

2番目が機能する理由(TimeZone = UTCの場合)

オリジナルの「作品」バージョン:

_select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
_

timeZoneがUTCに設定されている場合のみ、テキストからタイムスタンプへのキャストは、指定されていない場合はTimeZoneを想定するため、正確になります。

なぜ3番目のものが機能するのか

2つの問題が互いに相殺します。

動作しているように見えるもう1つのバージョンはTimeZoneに依存しませんが、2つの問題が解消されるためにのみ動作します。まず、上で説明したように、_timestamp without time zone AT TIME ZONE_ re-interprets UTCタイムスタンプに変換するためのタイムゾーンにあるタイムスタンプ。これは実質的にsubtractsタイムゾーンオフセットです。

しかし、私は私の理由を超えて、PostgreSQLはほとんどの場所を見るのに慣れているものとは逆符号のタイムスタンプを使用します。 ドキュメント を参照してください:

留意すべきもう1つの問題は、POSIXタイムゾーン名では、グリニッジの西の場所に正のオフセットが使用されることです。他のすべての場所で、PostgreSQLは正のタイムゾーンオフセットがグリニッジの東であるというISO-8601規則に従います。

つまり、_EST5EDT_は_+5_ではなく_-5_と同じです。これが機能する理由です:tzオフセットを減算するのは加算ではなく、無効なオフセットを減算するからです!

それを正しくするために必要なのは代わりに:

_select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE '+5';
_
121
Craig Ringer