0

I am trying to understand the behavior of AT TIME ZONE and I get some strange behavioral differences between SQL Server and PostgreSQL in this regard.

For demonstration purposes I produced two queries, one for SQL Server and one for PostgreSQL.

The SQL Server query:

SELECT
  [LocalTime],
  [LocalTime] AT TIME ZONE 'W. Europe Standard Time' AS [LocalTimeWithOffset],
  ([LocalTime] AT TIME ZONE 'W. Europe Standard Time') AT TIME ZONE 'UTC' AS [UniversalTime]
FROM
  (VALUES
    (CAST('2020-06-20 12:34:56.789' AS DATETIME2)),
    (CAST('2020-12-20 12:34:56.789' AS DATETIME2))) AS [X] ([LocalTime])

produces this result (in SQL Server Management Studio):

LocalTime                    LocalTimeWithOffset                 UniversalTime
---------                    -------------------                 ----------------
2020-06-20 12:34:56.7890000  2020-06-20 12:34:56.7890000 +02:00  2020-06-20 10:34:56.7890000 +00:00
2020-12-20 12:34:56.7890000  2020-12-20 12:34:56.7890000 +01:00  2020-12-20 11:34:56.7890000 +00:00

which seems fine to me, since I live in the Netherlands: in the summertime, we are 2 hours ahead of UTC (due to daylight saving) and in the wintertime we are only 1 hour ahead of UTC.

BUT... the PostgreSQL query:

SELECT
  "LocalTime",
  "LocalTime" AT TIME ZONE 'WET' AS "LocalTimeWithOffset",
  ("LocalTime" AT TIME ZONE 'WET') AT TIME ZONE 'UTC' AS "UniversalTime"
FROM
  (VALUES
    (timestamp '2020-06-20 12:34:56.789'),
    (timestamp '2020-12-20 12:34:56.789')) AS "X" ("LocalTime")

produces the following result (in pgAdmin):

LocalTime                    LocalTimeWithOffset         UniversalTime
timestamp without time zone  timestamp with time zone    timestamp without time zone
---------------------------  ------------------------    ---------------------------
2020-06-20 12:34:56.789      2020-06-20 14:34:56.789+02  2020-06-20 12:34:56.789
2020-12-20 12:34:56.789      2020-12-20 13:34:56.789+01  2020-12-20 12:34:56.789

which seems incorrect to me.

After struggling with this for a little while, I came up with another PostgreSQL query, which seems to yield the same results as the SQL Server query:

SELECT
  "LocalTime",
  "LocalTime"::timestamptz AS "LocalTimeWithOffset",
  "LocalTime"::timestamptz AT TIME ZONE 'UTC' AS "UniversalTime"
FROM
  (VALUES
    (timestamp '2020-06-20 12:34:56.789'),
    (timestamp '2020-12-20 12:34:56.789')) AS "X" ("LocalTime")

But this query seems to be dependent on the PostgreSQL server's time zone. :-(

Is there any (preferably elegant) way to mimic the SQL Server logic/behavior in PostgreSQL without a dependency on the server's time zone (and use an explicitly specified time zone in the query)?

Edit

I am aware that it is a good practice to store UTC date/time values in the database and convert them (preferably in/by the client) to local date/time values when presented to end users. I am intending to do so as well, of course, but my current scenario is a database migration from SQL Server to PostgreSQL (as part of a big application overhaul), where the DATETIME2 table columns in the SQL Server database contain local date/time values, which I want to convert to UTC date/time values in corresponding timestamp columns in the PostgreSQL tables. My migration script will work fine with the queries above, so actually I do not have a blocking issue. The reason for this question is that I am just curious about the apparent differences between SQL Server and PostgreSQL regarding the AT TIME ZONE behavior and the ways to overcome those behavioral differences.

Bart Hofland
  • 3,700
  • 1
  • 13
  • 22
  • What is your `timezone` setting? – Laurenz Albe Jul 20 '21 at 18:47
  • @LaurenzAlbe . . . Executing `SHOW TIMEZONE` in PostgreSQL gives the value `CET`. I guess that is not the correct time zone... – Bart Hofland Jul 20 '21 at 18:54
  • Well, the display of a `timestamp with timezone` is determined by your setting of `timezone`. – Laurenz Albe Jul 20 '21 at 20:19
  • @LaurenzAlbe . . . Ah yes. I think I know what you mean. Values of type `timestamp with time zone` do not contain time zone information. Such values are simply stored internally as UTC date/time values. When executing the query `SELECT timestamptz '2020-12-20 12:00:00+02', timestamptz '2020-12-20 14:00:00+04'` for example, I would get the values `2020-12-20 11:00:00+01` and `2020-12-20 11:00:00+01`. Completely different behavior compared to SQL Server's `DATETIMEOFFSET` data type. I will look into this somewhat more. Perhaps changing the `TIMEZONE` setting makes my last query "configurable". – Bart Hofland Jul 20 '21 at 23:00
  • I would say that `timestamp with timezone` works differently in PostgreSQL than in other databases. I suspect that trying to make them behave the same will fail, and you will have to take a broader view. – Laurenz Albe Jul 21 '21 at 04:46
  • @LaurenzAlbe . . . Yes, I agree. I made some wrong assumptions regarding the behavior of PostgreSQL here. I will continue to investigate it. Thank you very much for your help. – Bart Hofland Jul 21 '21 at 08:54

1 Answers1

0

In postgresql, AT TIME ZONE allows time zones to be added to date/time values that lack them (TIMESTAMP WITHOUT TIME ZONE, ::timestamp), and allows TIMESTAMP WITH TIME ZONE values (::timestamptz) to be shifted to non-local time zones and the time zone designation removed.

So, in the first query you've written for postgres, you're trying to cast '2020-06-20 12:34:56.789' to timestamp, and you've applied AT TIME ZONE on it for LocalTimeWithOffset. This is the case where postgres considered the input as timestamp in UTC and converted to the corresponding time zone, in your case WET, which is UTC+2:00 in Summer and UTC+1:00 in winter.

Now, in the third case, you're first applying the AT TIME ZONE 'WET' to timestamp '2020-06-20 12:34:56.789', hence the input is considered as UTC and converted to WET, the output is timstamptz, and this output is then fed again to AT TIME ZONE 'UTC', so as per behavior of AT TIME ZONE, it gets converted to UTC and the timezone tag is removed, which is, the input we given.

So, AT TIME ZONE did not behaved weird in postgresql rather did exactly what it was supposed to do, according to the documentation.

Hope your confusion is cleared.

devReddit
  • 2,696
  • 1
  • 5
  • 20
  • Hmmm. I'm still confused. I do understand your explanation, but I fail to understand why applying `AT TIME ZONE` in the second column of my first query assumes that the input as timestamp is in UTC... It's just a `timestamp` value and I want to indicate that it's a WET time zone value, not a UTC time zone value. How should I accomplish that? (As you said in the first paragraph, in PostgreSQL, `AT TIME ZONE` should allow time zones to be added to date/time values that lack them (TIMESTAMP WITHOUT TIME ZONE, ::timestamp). As far as I can see, that's the case here.) – Bart Hofland Jul 20 '21 at 18:59