Peculiar time zone handling in a Postgres database - postgresql

My environment
I'm in Paris, France (UTC+1 or CET).
It's 12am (00:00), we're on the 25th of November 2016.
My Postgres database is hosted on Amazon Web Services (AWS RDS) in the eu-west-1 region.
The issue
Querying for the current_date (or current_time) with a specific time zone set seems to deliver results that aren't consistent with... my beliefs.
In particular, querying for the current_date yields a different result when using the CET time zone or the UTC+1 one.
Example
SET TIME ZONE 'UTC+01';
select current_date, current_time;
+------------+--------------------+
| date | timetz |
+------------+--------------------+
| 2016-11-24 | 22:00:01.581552-01 |
+---------------------------------+
Nope, that was yesterday -- two hours ago.
SET TIME ZONE 'CET';
select current_date, current_time;
or
SET TIME ZONE 'Europe/Paris';
select current_date, current_time;
+------------+--------------------+
| date | timetz |
+------------+--------------------+
| 2016-11-25 | 00:00:01.581552-01 |
+---------------------------------+
There's the correct time and date.
Question
What's going on there?
Is it too late for me and I mixed up UTC+1 and UTC-1 or is there something bigger that I'm overlooking?
Does AWS RDS have a role in this?

The issue seems unrelated to Amazon RDS: it has to do with the convention used by PostgreSQL. In this case, you do have the time zone name backwards. You mean 'UTC-01' where you write 'UTC+01'.
From the manual:
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 time zone string used for SET TIME ZONE (and the display of SHOW timezone, accordingly) or the AT TIME ZONE construct use the opposite sign of what's displayed in timestamp (with time zone) literals! That's a very unfortunate disagreement between ISO and SQL standard on the one hand and POSIX on the other. (I think POSIX is to blame.) See:
Oddities with AT TIME ZONE and UTC offsets
Why does PostgreSQL interpret numeric UTC offset as POSIX and not ISO-8601?
But 'CET' or 'UTC-01' are both still potentially wrong for Paris because they are not taking rules for daylight saving time into account.
(DST is one of the most moronic concepts in the history of mankind.)
Paris (like most of Europe) uses CET during winter and CEST during summer. Your tests with 'CET' just happen to work in November. If you try the same in the summer time, you get the wrong result.
To be on the safe side, always use the time zone name 'Europe/Paris', which considers DST rules. The call is more expensive.
The function current_time takes DST rules into account if your time zone setting implies any. But 'UTC-01' is a plain time offset. I never use the data type time with time zone or current_time to begin with. The manual once more:
We do not recommend using the type time with time zone (though it is
supported by PostgreSQL for legacy applications and for compliance
with the SQL standard)
Consider:
SELECT '2016-06-06 00:00+0'::timestamptz AT TIME ZONE 'UTC+01' AS plus_wrong
, '2016-06-06 00:00+0'::timestamptz AT TIME ZONE 'UTC-01' AS minus_right
plus_wrong | minus_right
---------------------+---------------------
2016-06-05 23:00:00 | 2016-06-06 01:00:00
SELECT '2016-01-01 00:00+0'::timestamptz AT TIME ZONE 'CET' AS cet_winter
, '2016-06-06 00:00+0'::timestamptz AT TIME ZONE 'CEST' AS cest_summer
, '2016-06-06 00:00+0'::timestamptz AT TIME ZONE 'CET' AS cet_no_dst -- CET wrong!
cet_winter | cest_summer | cet_no_dst
---------------------+---------------------+---------------------
2016-01-01 01:00:00 | 2016-06-06 02:00:00 | 2016-06-06 01:00:00 -- wrong
SELECT '2016-06-06 00:00+0'::timestamptz AT TIME ZONE 'Europe/Paris' AS paris_summer
, '2016-01-01 00:00+0'::timestamptz AT TIME ZONE 'Europe/Paris' AS paris_winter
paris_summer | paris_winter
----------------------+----------------------
2016-06-06 02:00:00 | 2016-01-01 01:00:00 -- always right
Related:
Ignoring time zones altogether in Rails and PostgreSQL
Time zone names with identical properties yield different result when applied to timestamp
Time zone storage in data type "timestamp with time zone"

Related

Query Postgres Timestamp with Timezone wrong result

I have a table with a time column defined as
time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
my database and the server have the timezone UTC
Tue Apr 20 18:22:50 UTC 2021
When I ran the following query
SELECT id, time AT TIME ZONE 'utc' AS "TZ: UTC", time AT TIME ZONE 'Europe/Berlin' AS "TZ: Berlin" FROM orders ORDER BY id DESC;
results to
id | TZ: UTC | TZ: Berlin
-----+------------------------------+------------------------------
600 | 2021-04-20 18:18:07.11372+00 | 2021-04-20 16:18:07.11372+00
I would have expected in the TZ: Berlin column is
2021-04-20 20:18:07.11372+00.
When I run the query
SELECT id, time AT TIME ZONE 'utc' AS "TZ: UTC", time AT TIME ZONE 'UTC+2' AS "TZ: UTC+2" FROM orders ORDER BY id DESC
which results to the expected
id | TZ: UTC | TZ: UTC+2
-----+------------------------------+------------------------------
600 | 2021-04-20 18:18:07.11372+00 | 2021-04-20 20:18:07.11372+00
At this time of the year (summer) the timezones UTC+2 and Europe/Berlin are identical.
So what am I missing?
Kind regards
PS: The Version is
$ postgres -V postgres
postgres (PostgreSQL) 12.6
When I run
SELECT NOW() at time zone 'Europe/Berlin';
I get
timezone
----------------------------
2021-04-20 20:41:57.155641
Adding AT TIME ZONE to a timestamp without time zone merely adds the time zone to the timestamp. In your case, the time was stored as:
2021-04-20 18:18:07.11372
Adding the UTC time zone didn't change the hour portion of the time because it matches your session time zone.
Adding the Europe/Berlin time zone had the following effect:
2021-04-20 18:18:07.11372 = the time in Berlin, so we subtract two hours to get the time in UTC.
The case of UTC+2 is even more confusing. I suggest reading more about the posix time specifications.
Note this important bit:
The positive sign is used for zones west of Greenwich.
It worked as you expected, but for the wrong reason.
This might make it more clear:
SELECT t,
t at time zone 'UTC' as utc,
t at time zone 'Europe/Berlin' as berlin,
t at time zone 'UTC+2' as somewhere_west_of_utc,
t at time zone 'UTC-2' as berlinish
FROM (
VALUES ('2021-04-20T12:00:00'::timestamp)
) as v(t);
t | utc | berlin | somewhere_west_of_utc | berlinish
---------------------+------------------------+------------------------+------------------------+------------------------
2021-04-20 12:00:00 | 2021-04-20 12:00:00+00 | 2021-04-20 10:00:00+00 | 2021-04-20 14:00:00+00 | 2021-04-20 10:00:00+00
(1 row)
Again, I would strongly advise you to use timestamp with time zone where all the timestamps will be stored as UTC.
If you execute AT TIME ZONE on a timestamp without time zone, you get the absolute time when a clock in that time zone shows that time.
When a clock in Berlin shows 18:00, it is 16:00 UTC.

converting between time zones in postgresql

I have a date in UTC (timestamp with time zone):
test=# select ('2018-05-31T21:00:00+00'::timestamptz);
timestamptz
------------------------
2018-05-31 21:00:00+00
(1 row)
I want to convert it to another timezone, for example UTC+2. The documentation states that at time zone should do it for me:
Experession
<timestamp with time zone> AT TIME ZONE <zone>
Return type
timestamp without time zone
Description
Convert the given time stamp with time zone to the new time zone, with no time zone designation
See here.
But when I try to do it, I get strange results:
test# select ('2018-05-31T21:00:00+00'::timestamptz) at time zone 'utc+2';
timezone
---------------------
2018-05-31 19:00:00
(1 row)
I am pretty sure that 21 hours in UTC is 23 hours in UTC+2 (that's the reason for +2, after all).
What am I doing wrong here?
Your problem is the POSIX standard that according to the documentation decrees that
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.

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 time zones

I'm trying to understand how the session time zone works in PostgreSQL.
When I set session time zone to '01:00', returned date is in '-01:00'. Sign is always inverted and I do not understand this behavior.
How can this be explained?
postgres=# set time zone '00:00';
SET
postgres=# select timestamp with time zone '2017-11-29 15:00:00.000000+00';
timestamptz
------------------------
2017-11-29 15:00:00+00
(1 row)
postgres=# set time zone '01:00';
SET
postgres=# select timestamp with time zone '2017-11-29 15:00:00.000000+00';
timestamptz
------------------------
2017-11-29 14:00:00-01
(1 row)
postgres=# set time zone '-01:00';
SET
postgres=# select timestamp with time zone '2017-11-29 15:00:00.000000+00';
timestamptz
------------------------
2017-11-29 16:00:00+01
(1 row)
example, here
ts is time (text) - just time as we say it, :
not_aware the time if we don't know its timezone (local to the saying person)
aware the time, but knowing the timezone of it(if server time zone is UTC, then of utc zone)
, so:
s=# with s(city,tz) as (values('Moscow','UTC+3'),('New York','UTC-5'))
, ts as (select '2017-01-01 00:00:00'::text ts)
select *,ts::timestamp at time zone tz not_aware, ts::timestamptz at time zone tz aware
from s
join ts on true;
city | tz | ts | not_aware | aware
----------+-------+---------------------+------------------------+---------------------
Moscow | UTC+3 | 2017-01-01 00:00:00 | 2017-01-01 03:00:00+00 | 2016-12-31 21:00:00
New York | UTC-5 | 2017-01-01 00:00:00 | 2016-12-31 19:00:00+00 | 2017-01-01 05:00:00
(2 rows)
lets say you you come from Lodon and have have watches that show UTC0 time
- local for London (this is your server with timezone UTC0). Now are fly to Moscow, looking at the Kremlin Clock and you see the ts, midnight. Your watches are aware of time difference and they show you that to Ney Year is in three hours (this is timestamptz - aware column). This is why UTC+3 requires to minus the time. Because for your server Moscow time is in future, thus when you see local 2017, for server it is still 2016 and gonna be like it for +3 hours...
Now the behaviour that you expected (not_aware column) is more tricky. The server runs at UTC0, but has to pretend it know nothing about the "real time". As timestamp without time zone is such type. So it behaves as if you were watching new year celebration on Red Square on TV and would just scroll the bar to adjust your watches to Kremlin Clock, so in fact the time you see is your London time +3 (not_aware) column.
I came across this while looking for answers to other questions.
In case you never figured it out:
I think this is a quirk of the timezone parsing code trying to follow POSIX syntax, as noted above. You get the same behavior by e.g. SET TIME ZONE 'CET1:00'; or SET TIME ZONE 'CET1'.
I think the "actual" SQL way of saying what you were trying to say is SET TIME ZONE 1.
You probably don't want to have this be set as your timezone anyhow because it doesn't track DST changes. You probably want to choose a timezone by name from the tz database, for example America/New_York or (going with the GMT+1 example) Europe/Madrid.

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