Postgres AT TIME ZONE function shows wrong time? - postgresql

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

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.

Postgresql select output date with missing timezone offset

I've got a hard time with postgresql and timezones.
The Postgresql server is in UTC time zone.
I want to perform a query that returns the date in another timezone with the timezone offset.
I can perform a query that returns the date in a timezone but I'm missing the timezone offset
base=# SET timezone to 'utc';
SET
base=# select now();
now
------------------------------
2018-04-20 14:58:22.68038+00
(1 row)
base=# SET timezone to 'Europe/Paris';
SET
base=# select now();
now
-------------------------------
2018-04-20 16:58:29.614383+02
(1 row)
base=# SET timezone to 'utc';
SET
base=# select now() AT TIME ZONE 'Europe/Paris';
timezone
----------------------------
2018-04-20 16:59:03.146917 -- missing timezone offset here
(1 row)
Expected result
base=# SET timezone to 'utc';
SET
base=# select now() AT TIME ZONE 'Europe/Paris'; --I'm missing something here, I guess
timezone
----------------------------
2018-04-20 16:59:03.146917+02 -- That's what I want
(1 row)
Do you have any idea how to do it?
Thanks
If you use AT TIME ZONE, PG returns a timestamp without time zone. If you cast it to back to a timestamp with time zone, you get your expected result:
SELECT (NOW() AT TIME ZONE 'Europe/Paris')::TIMESTAMPTZ;
Result:
2018-04-20 18:15:26.165+02
How do you get the +02 without setting the timezone? Here's the problem: NOW() AT TIME ZONE 'Europe/Paris' returns the current time in the specified time zone (without an offset), and casting it back to TIMESTAMPTZ gives you the offset of the current time zone (of your PG session) based on UTC. Since 'Europe/Paris' is +02 from UTC, if you've set your time zone to 'Europe/Paris', the offset you get in a TIMESTAMPTZ is +02. If your current time zone setting is UTC, your offset is +00.
How to get the offset no matter what your PG session's time zone setting, without explicitly setting the time zone? I would like to think there's a better way to get the offset, but without knowing one, here's one way: calculate the offset, format it, append it to the timestamp without time zone.
SELECT (NOW() AT TIME ZONE 'Europe/Paris')::TEXT || TO_CHAR(EXTRACT(hour FROM NOW() AT TIME ZONE 'Europe/Paris' - NOW() AT TIME ZONE 'UTC'), 'FMSG00')
Result: 2018-04-21 15:12:42.658+02, and I get the same result no matter the current timezone.
Some timezone have an offset in 30 or 45 minutes.
Using TO_CHAR(EXTRACT(hour FROM NOW() AT TIME ZONE 'Europe/Paris' - NOW() AT TIME ZONE 'UTC') exclude this use case.
Did you consider to use SET LOCAL TIME ZONE 'Europe/Paris'; in a dedicated function ? According to the documentation :
If SET LOCAL is used within a function that has a SET option for the same variable (see CREATE FUNCTION), the effects of the SET LOCAL command disappear at function exit
This other reply seem's fitting your requirement : How to convert timestamp field to ISO 8601 string in a given time zone?

How do I specify “the start of today” in a specific time zone?

I have a table with a “timestamp with time zone” column. I would like to find all of the rows whose timestamp is earlier than today, where “today” is determined in a specific time zone.
I know how to use at time zone to interpret a literal timestamp as being in some particular time zone, and I know how to use date_trunc to get the beginning of this day. But I’m not sure how to combine them to get what I need. I tried
select date_trunc('day', current_date at time zone 'cst');
which gave me “2015-03-16 00:00:00”, but it’s unclear to me what time zone is used for this result (or whether it has one at all). How can I select the beginning of the current day according to a specific time zone?
It helped me to reframe the question as follows: what are the current date and time in the Central time zone? Then, what I want is the midnight at the beginning of that day [in the Central time zone]. I found that I could write this as
current_date::timestamp AT TIME ZONE 'cst'
This example may be useful to understand PostgreSQL timezone functionality:
SELECT now() as utc_now, -- 2021-08-29 11:38:08.552247 +00:00
pg_typeof(now()) as type_of_utc_now, -- timestamp with time zone
tehran_local_now, -- 2021-08-29 16:08:08.552247
pg_typeof(tehran_local_now), -- timestamp without time zone
utc_calculated_from_tehran_local_now, -- 2021-08-29 11:38:08.552247 +00:00
pg_typeof(utc_calculated_from_tehran_local_now), -- timestamp with time zone
tehran_local_start_of_today, -- 2021-08-29 00:00:00.000000
pg_typeof(tehran_local_start_of_today), -- timestamp without time zone
utc_start_of_today_at_tehran_timezone, -- 2021-08-28 19:30:00.000000 +00:00
pg_typeof(utc_start_of_today_at_tehran_timezone) -- timestamp with time zone
FROM (SELECT now() at time zone 'Asia/Tehran' as tehran_local_now) as tln
CROSS JOIN
(SELECT (now() at time zone 'Asia/Tehran') at time zone 'Asia/Tehran' as utc_calculated_from_tehran_local_now) as ucftln
CROSS JOIN
(SELECT date_trunc('day', now() at time zone 'Asia/Tehran') as tehran_local_start_of_today) as tlsot
CROSS JOIN
(SELECT date_trunc('day', now() at time zone 'Asia/Tehran') at time zone
'Asia/Tehran' as utc_start_of_today_at_tehran_timezone) as usotatt;
The PostgreSQL function now() (column utc_now) returns the current timestamp in UTC without any timezone. Its type is timestamp with time zone. Its Java equivalent is Instant.now().
When you use now() at time zone 'Asia/Tehran' (column tehran_local_now) it indicates the current date-time in Tehran local. Its type is timestamp without time zone. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).toLocalDateTime()
When you use date_trunc('day', now() at time zone 'Asia/Tehran') (column tehran_local_start_of_today) it indicates the start of today in Tehran local. Its type is timestamp without time zone. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).toLocalDate().atStartOfDay()
When you use date_trunc('day', now() at time zone 'Asia/Tehran') at time zone 'Asia/Tehran' (column utc_start_of_today_at_tehran_timezone) it indicates the start of today in Tehran timezone (not local). Its type is timestamp with time zone. You may need this one. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).truncatedTo(DAYS).toInstant()

Converting timestamp to timestamp in a specific time zone in Postgres

I have a table that has a column whose data type is timestamp without time zone , I'm trying to convert it to timestamp without time zone at a given time zone.
These are my Postgres 9.3 settings
select current_setting('TIMEZONE');
current_setting
-----------------
Hongkong
select * from pg_timezone_names where name = 'Hongkong';
name | abbrev | utc_offset | is_dst
----------+--------+------------+--------
Hongkong | HKT | 08:00:00 | f
Here is what I did to convert it to HKT:
-- this SQL gives me what I expected
select '2015-01-05 12:00:00'::timestamp without time zone
at time zone 'UTC'
at time zone 'HKT';
---------------------
2015-01-05 20:00:00
-- Shouldn't this produce the same result with the above one?
-- How do I make this work?
-- Don't tell me to change it to UTC-08:00 ...
select '2015-01-05 12:00:00'::timestamp without time zone
at time zone 'UTC'
at time zone 'UTC+08:00';
---------------------
2015-01-05 04:00:00 -- WHY?
The reason behind this is why you really shouldn't use time-zone offsets in PostgreSQL (unless you are know exactly what you do).
The time zone 'UTC+08:00' is a POSIX-style time zone specification, 'Hongkong' is the exact time zone name, and 'HKT' is (one of) its abbreviation(s).
The pg_timezone_names system view's utc_offset column is defined to be the Offset from UTC (positive means east of Greenwich).
But in the POSIX-style time zone specification, the offset part is different:
... Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.
So, instead of using offsets (as intervals), or POSIX-style time zone specifications, you should use:
Time zone abbreviations, if you want to deal with daylight saving rules on your own,
Exact time zone names, everywhere else (preferred).
In short, this is the difference between abbreviations and full names: abbreviations represent a specific offset from UTC, whereas many of the full names imply a local daylight-savings time rule, and so have two possible UTC offsets.
To complicate matters, some jurisdictions have used the same timezone abbreviation to mean different UTC offsets at different times; for example, in Moscow MSK has meant UTC+3 in some years and UTC+4 in others. PostgreSQL interprets such abbreviations according to whatever they meant (or had most recently meant) on the specified date; but, as with the EST example above, this is not necessarily the same as local civil time on that date.
But the most simple solution is to use timestamp with time zone: you already set your TimeZone setting to 'Hongkong', so timestamp with time zone values will be displayed in that time zone to your (PostgreSQL) client.
set time zone 'Hongkong';
select current_setting('TimeZone') TimeZone,
dt original,
dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC+08:00' "UTC+08:00",
dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC+8' "UTC+8",
dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC-8' "UTC-8",
dt AT TIME ZONE 'UTC' AT TIME ZONE INTERVAL '+08:00' "INTERVAL +08:00",
dt AT TIME ZONE 'UTC' AT TIME ZONE 'HKT' "HKT",
dt AT TIME ZONE 'UTC' AT TIME ZONE 'Hongkong' "Hongkong",
dt AT TIME ZONE 'UTC' "with time zone"
from (values (timestamp '2015-01-05 12:00:00')) v(dt);
-- TIMEZONE | ORIGINAL | UTC+08:00
-- ---------+---------------------+--------------------
-- Hongkong | 2015-01-05 12:00:00 | 2015-01-05 04:00:00
-- UTC+8 | UTC-8 | INTERVAL +08:00
-- --------------------+---------------------+--------------------
-- 2015-01-05 04:00:00 | 2015-01-05 20:00:00 | 2015-01-05 20:00:00
-- HKT | HONGKONG | WITH TIME ZONE
-- --------------------+---------------------+-----------------------
-- 2015-01-05 20:00:00 | 2015-01-05 20:00:00 | 2015-01-05 20:00:00+08
SQLFiddle

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.