Why does PostgreSQL apparently coerce a string with timestamp to `TIMESTAMP WITH TIME ZONE` even without the time zone offset? - postgresql

I am on PostgreSQL v11.10, and have the TimeZone set to UTC.
When I say select '2021-02-16 17:45+00' at time zone 'America/New_York';, I get 2021-02-16 12:45:00, which is correct and expected.
However, when I say select '2021-02-16 17:45' at time zone 'America/New_York';, I get the same result.
It seems like both strings are coerced to timestamp with time zone, which seems a bit counterintuitive to me in the case of the latter one. Why does PostgreSQL behave like that? Is it documented in the manual (I looked at the following places: AT TIME ZONE, then Date/Time Types and also Date/Time Input Interpretation and Handling of Invalid or Ambiguous Timestamps, all to no avail).

Because your TimeZone is set to UTC, any timestamp without a specified timezone will be interpreted as local to the UTC timezone. Since you as asking for a timestamp to be interpreted as America/New_York, Postgres will first interpret the timestamp as a UTC timestamp, then do the math to convert it to America/New_York.
Note that if you actually look at the types being sent to Postgres, it is not coercing into timestamp with timezone unless you specify it:
edb=# select pg_typeof('2021-02-16 17:45' at time zone 'America/New_York');
pg_typeof
-----------------------------
timestamp without time zone
(1 row)
edb=# select pg_typeof('2021-02-16 17:45+00' at time zone 'America/New_York');
pg_typeof
-----------------------------
timestamp without time zone
(1 row)
edb=# select pg_typeof('2021-02-16 17:45+00'::timestamptz at time zone 'America/New_York');
pg_typeof
-----------------------------
timestamp without time zone
(1 row)
edb=# select pg_typeof('2021-02-16 17:45+00'::timestamptz);
pg_typeof
--------------------------
timestamp with time zone
(1 row)
edb=# select pg_typeof('2021-02-16 17:45+00'::timestamp);
pg_typeof
-----------------------------
timestamp without time zone
(1 row)

Related

Postgres cast to TIMESTAMPTZ

What is the behavior of PostgreSQL when we cast a DATE to TIMESTAMP to TIMESTAMPTZ
What time zone is used?
PostgreSQL server
Client that run the query (Current session)
If you cast a date to a timestamp, time zones don't play a role, because both data types are without a time zone. The resulting timestamp will be the beginning of the day.
If you cast date to timestamp with time zone, resulting timestamp will be the beginning of the date in the time zone defined by the parameter timezone in your current session.
SHOW timezone;
TimeZone
---------------
Europe/Vienna
(1 row)
SELECT CAST (DATE '2021-01-15' AS timestamp);
timestamp
---------------------
2021-01-15 00:00:00
(1 row)
SELECT CAST (DATE '2021-01-15' AS timestamp with time zone);
timestamptz
------------------------
2021-01-15 00:00:00+01
(1 row)
Casting date to timestamp will append time 00:00:00.0 to the date.
The time zone of the current server session will be used.
By default this is the time zone setting of Postgresql server.
You can change the time zone of the current server session like this:
set time zone 'Europe/Sofia';
I do not think that the time zone of the client has any effect. More on this issue here.
Edit
As Adrian Klaver noticed "When dealing with timestamps it's best to assume the worst". Therefore better set the session time zone explicitly.
Given the situation where one needs to change the column type from integer (as epoc seconds) to timzezonetz ->
casting directly from integer to timezonetz is not possible.
but you can do this:
ALTER TABLE table_name ALTER COLUMN column_name TYPE timestamptz
USING column_name::abstime::timestamptz

date_trunc at time zone with original timestamptz

I have a single timestamptz that I want to date_trunc so it removes the hours:
2019-01-01T17:43-03 => 2019-01-01T00:00-03.
However, because date_trunc removes the timezone, I need to do it like this:
date_trunc('day', '2019-01-01T17:43-03'::timestamptz) at time zone '-03'
However, I do not want to hardcode the time zone, since the query is run with timestamptz in many different timezones (these are input to the query and not stored). So I want the timezone to be extracted from the original timestamp. I tried to do something like this, but it does not work:
date_trunc('day', '2019-01-01T17:43-03'::timestamptz) at time zone EXTRACT(...)
Related, I am trying to extract the timezone from a timestamptz, but just getting 0.
SELECT EXTRACT(timezone FROM TIMESTAMP WITH TIME ZONE '2019-01-01T00:00+03')
0
Can anybody help me with this?
I believe you may have a misconception about how timestamps and timezones are stored in PostgreSQL (because you seem to expect the -03 to be preserved when calling date_trunc and that you "want the timezone to be extracted from the original timestamp"). According to the documentation:
When a timestamp with time zone value is output, it is always converted from UTC to the current timezone zone, and displayed as local time in that zone. To see the time in another time zone, either change timezone or use the AT TIME ZONE construct (see Section 9.9.3).
Therefore, the statement that "different clients [that] have timestampts in many different timezones," while true, are all translated to UTC for storage, and then displayed in your local timezone (or the timezone you specify) for output. As such, calling date_trunc() will essentially truncate the UTC timestamp, and if you want it displayed in a specific timezone, you will need to add the AT TIME ZONE clause.
UPDATE: An example is here:
edb=# select date_trunc('day', '2019-01-01T17:43-03'::timestamptz) ;
date_trunc
------------------------
2019-01-01 00:00:00+00
(1 row)
edb=# set timezone to 'US/Pacific';
SET
edb=# select date_trunc('day', '2019-01-01T17:43-03'::timestamptz) ;
date_trunc
------------------------
2019-01-01 00:00:00-08
(1 row)
As you can see, date_trunc will append the timezone that I defineā€”it is not omitted.

postgresql `at time zone` incorrect behaviour?

I have a simple postgresql table with the following data:
create table ti (
l text,
t timestamp without time zone
);
insert into ti(l, t) values ('now', now());
I do a select using at time zone, I expect UTC + 5.30 hours
select now(), t, t at time zone 'Asia/Kolkata' from ti;
But this is what I get:
now| t| timezone
2019-06-06T12:11:42.576388Z| 2019-06-06T11:50:48.178689Z| 2019-06-06T06:20:48.178689Z
It subtracted 5.30 hours instead of adding them.
Sqlfiddle here
now() returns a timestamp with time zone. The time zone info will be stripped off when it is cast to a timestamp without time zone when it is saved in your table, but the actual value saved in there will depend on the session time zone.
You can see this behavior pretty easily:
postgres=# begin;
BEGIN
postgres=# set time zone utc;
SET
postgres=# insert into test select now();
INSERT 0 1
postgres=# set time zone 'US/Eastern';
SET
postgres=# insert into test select now();
INSERT 0 1
postgres=# select * from test;
a
----------------------------
2019-06-06 12:46:10.475424
2019-06-06 08:46:10.475424
(2 rows)
A little more explanation after your comment. I think the issue you are having is with converting between timestamp and timestamptz. Timestamps are much less confusing if you stick with just timestamptz. Let's remove now() from the discussion, since that adds an additional layer of complexity because converting from the result of now() to a timestamp without time zone depends on the session time zone.
select '2019-06-06 12:00:00UTC'::timestamp with time zone,
('2019-06-06 12:00:00UTC'::timestamp with time zone) at time zone 'Asia/Kolkata';
timestamptz | timezone
------------------------+---------------------
2019-06-06 12:00:00+00 | 2019-06-06 17:30:00
I believe this is what you expect? We convert from a timestamp with time zone to a timestamp without time zone at a specific time zone.
What you are doing is similar to this, though:
select '2019-06-06 12:00:00UTC'::timestamp with time zone,
(('2019-06-06 12:00:00UTC'::timestamp with time zone)::timestamp without time zone) at time zone 'Asia/Kolkata';
timestamptz | timezone
------------------------+------------------------
2019-06-06 12:00:00+00 | 2019-06-06 06:30:00+00
(1 row)
I think you will find this much less confusing if you can store timestamp with time zone instead of timestamp without time zone.

Retrieving UTC timestamptzs from postgres in the correct time zone

I store timestamps with time zone in my postgres database. The server time zone is UTC. All the timestamptzs are stored as UTC datetimes.
Now, I'd like to retrieve those timestamps, but for a specific time zone (e.g. US/Pacific). Because of daylight savings time, the correct time zone is sometimes PDT and sometimes PST. So I can't just run a query like select t at time zone 'pdt' because this will be wrong for the pst dates.
Is there a way to pull the dates from the database in the correct time zone?
According to the documentation, together with TIME ZONE code you can also specify locales. For your case you can use something like that:
ds=# SELECT current_setting('TIMEZONE');
current_setting
-----------------
UTC
(1 row)
ds=# SELECT now();
now
-------------------------------
2015-11-05 00:35:03.126317+00
(1 row)
pm7=# SELECT now() AT TIME ZONE 'America/Los_Angeles';
timezone
----------------------------
2015-11-04 16:35:06.344367
(1 row)

Converting UTC time in local time in PostgreSQL 8.3

I run a Postgres 8.3 database where times seem to be stored in UTC without time zone.
I am trying to display in local time but not with '+01' suffix :
With select scheduled_start_ts I get :
2014-01-20 05:01:35.663
With select scheduled_start_ts at time zone 'MET' :
2014-01-20 05:01:35.663+01
I would like to get "2014-01-20 06:01:35.663" which is in local time.
The database I am using cannot be modified and I am not allowed to modify how data are stored.
If you want to format times, use the to_char function. See formatting functions in the docs.
regress=> SELECT to_char(
(TIMESTAMP '2014-01-20 05:01:35.663' AT TIME ZONE 'UTC')
AT TIME ZONE 'MET',
'YYYY-MM-DD HH:MI:SS'
);
to_char
---------------------
2014-01-20 06:01:35
(1 row)
The (TIMESTAMP 'xxx' AT TIME ZONE 'UTC') gets me a timestamptz with the correct time, by re-interpreting the TIMESTAMP as being in UTC. The second AT TIME STAMP instead converts the timestamptz into a timestamp in timezone MET. This then gets formatted.
Whatever the SQL standards committe were smoking when they designed this, I never, ever, ever want to be anywhere near it.