Why does Postgres discard timezone information with 'AT TIME ZONE' - postgresql

I have a postgres database with TIMESTAMP WITH TIME ZONE values. The values could have any arbitrary time zone. I now want to use the date_trunc function on those but in a specific timezone. I though I could use the AT TIME ZONE function to normalize all timestamps to one timezone, but as result I get a TIMESTAMP without time zone information.
e.g:
select now(), now() AT TIME ZONE 'Europe/London';
now | timezone
-------------------------------+----------------------------
2015-08-12 12:21:55.337973+02 | 2015-08-12 11:21:55.337973
What I want is to get the result with the time zone information attached to it like 2015-08-12 11:21:55.337973+01 but I can't seem to find a command for this.
My current solution is to use AT TIME ZONE``, then using date_trunc on theTIMESTAMPwithout timezone andAT TIME ZONE``` again afterwards to re-attach the time zone information. However, it seems ugly to use it in that way:
SELECT
date_trunc('day', '2015-08-01 00:00:00+02'::timestamptz),
date_trunc('day', '2015-08-01 00:00:00+02'::timestamptz AT TIME ZONE 'Europe/London')
AT TIME ZONE 'Europe/London';
date_trunc | timezone
------------------------+------------------------
2015-08-01 00:00:00+02 | 2015-07-31 01:00:00+02
My Questions would be:
Is there a postgresql command that converts a timestamp from one timezone to another without discarding the time zone information
Why does postgres behave in this way? Is there a reason fir discarding the time zone information?
Thank you for your help.

Related

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.

Why datetime without time zone foormatted as it have timezone?

I am experimenting with time zones.
My postgres table created as:
Table "public.xx"
Column | Type | Collation | Nullable | Default
--------+-----------------------------+-----------+----------+---------
dtz | timestamp with time zone | | |
dt | timestamp without time zone | | |
The server timezone is 'UTC'. I know this from show timezone;
Then I insert data:
insert into xx values( TIMESTAMP WITH TIME ZONE '2018-08-01 13:00:00+3', TIMESTAMP WITH TIME ZONE '2018-08-01 13:00:00+3' );
INSERT 0 1
tucha=> select * from xx;
dtz | dt
------------------------+---------------------
2018-08-01 10:00:00+00 | 2018-08-01 10:00:00
(1 row)
Results are easy to understand: the dates are stored in UTC thus 3 is subtracted.
Also notice that dtz have +00
Now when I use at time zone
tucha=> select dtz at time zone 'UTC', dt at time zone 'UTC' from xx;
timezone | timezone
---------------------+------------------------
2018-08-01 10:00:00 | 2018-08-01 10:00:00+00
(1 row)
The +00 is added for field which has no timezone and viceversa: no +00 for dtz field.
Why this happened?
a short answer is - because it is desined this way:
https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-ZONECONVERT
timestamp without time zone AT TIME ZONE zone returns timestamp
with time zone
timestamp with time zone AT TIME ZONE zone returns
timestamp without time zone
Longer:
as you said your client works in UTC timezone and server always stores timestamptz in UTC, thus select times match (you use UTC everywhere).
first select timestamptz is shown with +00 because this is timezone aware field - and it shows you the time zone. dt knows nothing of time zones, thus shows none...
Now when you use AT TIME ZONE you ask to show timestamps at SOME SPECIFIC time zone, thus time zone aware data type shows you the time for specific zone (adding hours and so), but after "moving" time it can't be reused as time zone aware (because it's not server UTC time anymore), thus it hides TZ. Opposite the time that was not aware of time zone, when displayed at some specific time zone, gains that "awareness" and deducting hours it shows +00 so you would know you operated on TZ not aware time stamp. I think that is the logic here.
I have found good article how to work with timezones (RU):
So – timestamp at time zone gives timestamptz which represents the moment in time when local time at given zone was as given.
and timestamptz at time zone gives timestamp, which shows what the time was in given timezone at given point in time.

Convert timestamp in UTC to timestamptz

How do I convert a timestamp that is in UTC to timestamptz?
If my local time zone is GMT-1 and I run:
select '2017-01-01 00:00:00'::timestamptz
I get:
2017-01-01 00:00:00-01
but I want:
2017-01-01 01:00:00-01
Better:
SELECT timestamp '2017-01-01 00:00:00' AT TIME ZONE 'UTC';
No additional cast after the AT TIME ZONE construct. It returns timestamp with time zone (= timestamptz) for timestamp without time zone (= timestamp) input and vice versa.
For the given example, the shortest, most efficient way to provide a timestamp constant is timestamp '2017-01-01'. Or use a cast, almost as good: '2017-01-01'::timestamp. The time component 00:00:00 is assumed when missing.
There is no such thing as a "timestamp that is in UTC". A timestamp carries no time zone information. Only you know that it's supposed to be located in the UTC time zone.
The type name "timestamp with time zone" is a bit misleading. timestamptz does not carry any time zone information, either. The given time zone name, abbreviation or offset is used to compute the corresponding UTC time. Text output (display) is adapted to the current time zone setting of your session. Only the bare value of the corresponding UTC time is stored. The time zone itself is never stored. If you need it, store it additionally in another column. In your particular case, UTC happens to be the time zone used for input as well.
Detailed explanation:
Time zone storage in data type "timestamp with time zone"
Ignoring time zones altogether in Rails and PostgreSQL
Solution: select ('2017-01-01 00:00:00' at time zone 'utc')::timestamptz

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.

Postgres: get local timestamp with time zone of latest midnight

I'm in the Time Zone Europe/Berlin (+02), Postgresql is running at UTC (+00).
I need to get the timestamp with time zone at the latest local midnight date (The start of day date of the current day in my time zone).
So my end result would be something like this if we have 2013-03-03 14:00:00+02
2013-03-03 22:00:00+00
2013-03-04 00:00:00+02 // the same
I tried to get this date with
SELECT TIMESTAMP 'today' AT TIME ZONE 'Europe/Berlin'
Unfortunately this yields the wrong date (the previous day midnight) during 00:00 and 02:00 as the UTC time is stil at the previous day and today seems to use utc to calculate the rest.
If we have 2013-03-03 00:05 at Europe/Berlin this will return
2013-05-01 22:00:00+00
If I want to have the correct date I need to use
SELECT date_trunc('day', now() AT TIME ZONE 'Europe/Berlin') AT TIME ZONE 'Europe/Berlin';
2013-05-02 22:00:00+00
which is correct, but quite ugly.
Is there a cleaner variant of this command?
Use timestamptz. The tz at the end meaning with time zone:
SELECT TIMESTAMPTZ 'today' AT TIME ZONE 'Europe/Berlin'
Or if you like it more explicit:
SELECT TIMESTAMP with time zone 'today' AT TIME ZONE 'Europe/Berlin'
Wrap it in a function:
create function midnight() returns timestamptz as $$
select date_trunc('day', now() AT TIME ZONE 'Europe/Berlin') AT TIME ZONE 'Europe/Berlin';
$$ language sql;
Based on Erwin's answer to a related question, this was the simplest and fastest way I figured out how to do it:
SELECT timezone('Europe/Berlin', now()::date::timestamp) AS local_midnight_in_utc;
The key is the cast to a timestamp, which removes the time zone from the date.
You can test your sample time of '2013-03-03 00:05' with this:
SELECT timezone('Europe/Berlin', '2013-03-03 00:05'::date::timestamp) AS midnight;
and it returns
2013-03-02 23:00:00+00
According to explain analyze, this is about 3x as fast as the datetrunc version. A runtime of .017ms vs 0.006ms based on a best of 5 runs.