Why is at time zone works not as was expected? - postgresql

I have a server that gives me some information grouped by day, so I tried to use date_trunc(), but because of the timezone I know that 2020-06-05 21:00:00 in my DB is actually 2020-06-06 00:00:00.
So if I just use date_trunc, I got 2020-06-05 00:00:00, but I need 2020-06-06 00:00:00.
I'm trying this:
select tm , date_trunc('day', (tm) at time zone '+3')
from scm.tbl
where (tm BETWEEN '2020-06-05 15:00:00+00:00:00' AND '2020-06-08 20:59:00+00:00:00')
order by tm
And I have this:
2020-06-05 17:59:59 | 2020-06-05 00:00:00
2020-06-05 18:00:10 | 2020-06-06 00:00:00
At 18:00 the date became 2020-06-06, but it shouldn't. Why does it? What am I doing wrong here?

The problem is that AT TIME ZONE will convert a timestamp without time zone into a timestamp with time zone, and that is again handled according to your current timezone setting.
So you need to use AT TIME ZONE twice, first to interpret the timestamp in the correct time zone, and then to extract what an UTC clock would show at that time:
SELECT tm, date_trunc('day', tm AT TIME ZONE '+3' AT TIME ZONE 'UTC')
FROM scm.tbl
WHERE (tm BETWEEN '2020-06-05 15:00:00+00:00:00' AND '2020-06-08 20:59:00+00:00:00')
ORDER BY tm;

I found my answer here timezone aware date_trunc function
When I saw this in the answer:
timestamp with time zone '2001-01-1 00:00:00+0100' at time zone '-02'
It's because tm is +03:00:00 and I'm adding it again...
So what I can do is:
date_trunc('day', tm at time zone '0')
I guess there's must be better way to do it, without '0'

Related

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 a UTC timezone in postgresql to EST (local time)

I am new to PostgreSQL and I was wondering if there is a direct way to just convert the timestamp values in a table to a different timezone using a function. In my case it is UTC to EST.
These are the values for example that I need to convert to EST (not just one value but all the values in the table)
date
-------------------
2015-10-24 16:38:46
2016-01-19 18:27:00
2016-01-24 16:14:34
2016-02-09 23:05:49
2016-02-11 20:46:26
Here in London, we are currently 1 hour ahead of UTC. So - if I take your timezone without timestamp and say it is in UTC I will get it printed for my local timezone.
richardh=> SELECT ((timestamp '2015-10-24 16:38:46') AT TIME ZONE 'UTC');
timezone
------------------------
2015-10-24 17:38:46+01
(1 row)
But you want "EST" which seems to be somewhere in the Americas, judging by the value returned. You can wrap the expression in a little SQL function if you wanted to.
richardh=> SELECT ((timestamp '2015-10-24 16:38:46') AT TIME ZONE 'UTC') AT TIME ZONE 'EST';
timezone
---------------------
2015-10-24 11:38:46
(1 row)
Edit: how to do it in a query
SELECT ((stored_timestamp AT TIME ZONE 'UTC') AT TIME ZONE 'EST') AS local_timestamp
FROM my_table;
Similarly
execute
SELECT '2015-10-24 16:38:46'::timestamp AT time zone 'EST';
timezone
------------------------
2015-10-24 21:38:46+00
(1 row)
I usually leave everything in UTC and convert when it is time to show.
I use something like:
SELECT my_date_utc AT time zone 'utc' at time zone 'est' From ....
If you have problem accessing with your zone, you can simply pass your zone interval also.
To convert timestamp from IST to UTC.
SELECT '2020-12-14 06:38:46'::timestamp AT time zone INTERVAL '+05:30';
timezone
------------------------
2015-10-24 11:38:46+00
(1 row)
To convert timestamp from UTC to IST.
SELECT '2020-12-14 06:38:46'::timestamp AT time zone INTERVAL '-05:30';
timezone
------------------------
2020-12-14 12:08:46+00
(1 row)
It is 12:22 here in Los Angeles now.
I find that I have to reverse the UST and america/los_angeles arguments:
ods=> SELECT NOW(),(NOW() AT TIME ZONE 'america/los_angeles') AT TIME ZONE 'utc';;
now | timezone
-------------------------------+-------------------------------
2022-04-22 19:22:35.943605+00 | 2022-04-22 12:22:35.943605+00
(1 row)
Am I missing something?
You should always store the main reference of a date in UTC and either convert it to a time zone in your queries or store the specific timezone version of the data in another column. The reason for this is that it is quick and easy to convert a date from UTC to another time zone as long as you know that the timezone that it is stored as is UTC. It takes the guess work out of it. Alternatively, you can store the date WITH the timezone.
If you have an operation that automatically populates the date with the system clock of your server, then you can either
A: Change the operation to use UTC time
B: Change the system clock on the server to UTC
I had the same problem, I am working with different regions and timezones, I need to just fix the timezone in the query the way it doesn't effect other customers around the regions and I havent changed the table structure or any thing(Open–closed principle) . What I did In my query:
SELECT TO_CHAR(current_timestamp at time zone 'Australia/Melbourne', 'DD/MM/YYYY hh24:mi AM') as date_of_extract
This worked for me and I could change the 'UTC' defult timezone for my postgressql to the 'Australia/Melbourne'(any time zone you are looking into). hope this is helpful.
Building off of #Leandro Castro's answer...
To get current time in in timezone, use the CURRENT_TIME function:
SELECT CURRENT_TIME(0) AT time zone 'utc' at time zone 'est';

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

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.

Postgres AT TIME ZONE function shows wrong time?

I am using transformation to new timezone UTC+3 which is equal to EAT timezone, but Postgres (9.1) shows the wrong time
select '2015-01-13 08:40:00.0'::timestamp with time zone AT TIME ZONE 'UTC+03',
'2015-01-13 08:40:00.0'::timestamp with time zone AT TIME ZONE 'EAT';
(default timezone is Stockholm here)
The result is
"2015-01-13 04:40:00",
"2015-01-13 10:40:00"
Why?
it should be 2015-01-13 10:40:00
if using JodaTime with both timezones then it shows the same correct result '2015-01-13 10:40:00'.
From the Postgres documentation there is the option to use ::timestamptz instead of ::timestamp WITH TIME ZONE and I found preferred results when making the conversion; as it is the most concise of the available options while still being readable.
SELECT created_at
,created_at::timestamp AT TIME ZONE 'EDT' -- yields bad result
,created_at::timestamp WITH TIME ZONE AT TIME ZONE 'EDT'
,created_at AT TIME ZONE 'UTC' AT TIME ZONE 'EDT'
,created_at::timestamptz AT TIME ZONE 'EDT'
2019-03-29 18:49:25.250431 -- raw UTC data
2019-03-29 22:49:25.250431 -- erroneous result
2019-03-29 14:49:25.250431 -- accurate result
2019-03-29 14:49:25.250431 -- accurate result
2019-03-29 14:49:25.250431 -- accurate result
I had similar problem, it gave me the wrong date and time but this answer here gave me a clear understanding and fixed my problem. PostgreSQL wrong converting from timestamp without time zone to timestamp with time zone
So what I did was changing from
SELECT timestamp AT TIME ZONE '+08' FROM orders;
to
SELECT timestamp AT TIME ZONE 'UTC' AT TIME ZONE '+08' FROM orders;
A time zone name spelled like 'UTC+3:00' is a POSIX time zone specification.
In this style, zones west of GMT have a positive sign and those east have a negative sign in their name (e.g "Etc/GMT-14" is 14 hours ahead/east of GMT.)
See http://www.postgresql.org/docs/9.3/static/datatype-datetime.html#DATATYPE-TIMEZONES
EAT(East Africa Time) is three hours ahead of UTC (UTC+03:00).
It's represented as UTC+03:00 in ISO-8601 format.
But AT TIME ZONE only supports timezones represented in POSIX-style.
In POSIX-style, the positive sign is used for zones west of Greenwich. (Note that this is the opposite of the ISO-8601 sign convention used elsewhere in PostgreSQL.)
Format
PST
ART
W←UTC→E
EAT
HKT
ISO-8601
UTC-08
UTC-03
UTC+00
UTC+03
UTC+08
POSIX-style
UTC+08
UTC+03
UTC+00
UTC-03
UTC-08
In POSIX-style, the correct representation of EAT can be:
'UTC-03'
'UTC-3'
'-3'
-3
The result of the following SQL statement must be TRUE.
select now() at time zone -3 = now() at time zone 'EAT';
Confusingly, set time zone statement supports ISO-8601, not POSIX-style.
Execute the following SQL statements in sequence to find the difference.
#
SQL
Result
①
set time zone -3;
SET
②
select now();
2022-02-24 04:53:41.921391-03
③
select timestamp '2022-02-24 12:00' at time zone -3;
2022-02-24 06:00:00-03
④
select timestamp '2022-02-24 12:00' at time zone +3;
2022-02-24 12:00:00-03
⑤
set time zone 3;
SET
⑥
select now();
2022-02-24 10:54:10.283953+03
⑦
select timestamp '2022-02-24 12:00' at time zone -3;
2022-02-24 12:00:00+03
⑧
select timestamp '2022-02-24 12:00' at time zone +3;
2022-02-24 18:00:00+03
⑨
set time zone local;
SET
⑩
select now();
2022-02-24 15:54:51.79643+08
⑪
select now() at time zone -3;
2022-02-24 10:55:02.209886
⑫
select now() at time zone 3;
2022-02-24 04:55:09.498461
⚠️Please be careful about this.
References:
AT TIME ZONE
Time Zones
POSIX Time Zone Specifications
SET TIME ZONE

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.