FROM_TZ equivalent in PostgreSQL - postgresql

How can I convert this to work in PostgreSQL?
TO_CHAR(CAST(FROM_TZ(CAST(columnname AS TIMESTAMP), 'GMT') AT TIME ZONE 'US/Eastern' AS DATE),'MM/DD/YY HH:MI AM') AS dt

testdb=# select TO_CHAR(CAST('2020-02-28T18:43' AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'US/Eastern','MM/DD/YY HH:MI AM') as dt;
dt
-------------------
02/28/20 01:43 PM
(1 row)
To make it clear what's going on, we'll start with the cast to TIMESTAMP, show that adding the first AT TIME ZONE makes it a tz-aware timestamp, and then how the 2nd does the timezone conversion.
testdb=# select CAST('2020-02-28T18:43' AS TIMESTAMP),
testdb-# CAST('2020-02-28T18:43' AS TIMESTAMP) AT TIME ZONE 'GMT',
testdb-# CAST('2020-02-28T18:43' AS TIMESTAMP) AT TIME ZONE 'GMT' AT TIME ZONE 'US/Eastern';
timestamp | timezone | timezone
---------------------+------------------------+---------------------
2020-02-28 18:43:00 | 2020-02-28 18:43:00+00 | 2020-02-28 13:43:00
(1 row)
See the timezone conversion docs for more details.

Related

Postgresql extracting 'epoch' from timestamp cuts off last date in date range

My table has the column event_ts with column type numeric.
Here is my query:
select
min(to_timestamp(event_ts)), max(to_timestamp(event_ts))
from
table1
where
event_ts >= extract('epoch' from '2021-07-01'::timestamp) and
event_ts <= extract('epoch' from '2021-07-31'::timestamp)
However, the results are
min: 2021-06-30 20:00:00.000 -0400
max: 2021-07-30 20:00:00.000 -0400
I would think the where clause would include data from 2021-07-01 to 2021-07-31.
There is data for July 31st, 2021.
Why does this query start at 2021-06-30 and end 2021-07-30?
show timezone;
TimeZone
------------
US/Pacific
select extract('epoch' from '2021-07-01'::timestamp);
extract
-------------------
1625097600.000000
select to_timestamp(1625097600);;
to_timestamp
-------------------------
06/30/2021 17:00:00 PDT
select extract('epoch' from '2021-07-01'::timestamptz);
extract
-------------------
1625122800.000000
(1 row)
test(5432)=# select to_timestamp(1625122800);
to_timestamp
-------------------------
07/01/2021 00:00:00 PDT
So by using timestamp you are creating a local time offset by the timezone offset. Using timestamptz will return a timestamp at 0:00:00.
This is because from here:
https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
epoch
For timestamp with time zone values, the number of seconds since 1970-01-01 00:00:00 UTC (negative for timestamps before that); for date and timestamp values, the nominal number of seconds since 1970-01-01 00:00:00, without regard to timezone or daylight-savings rules; for interval values, the total number of seconds in the interval
Epoch is based on UTC timezone.
Not sure why you are using epoch anyway?
Why not?:
...
where
event_ts between '2021-07-01'::timestamptz and '2021-07-31'::timestamptz

Select a date in my time zone when database is in UTC with PostgreSQL

I want to select a day in my timezone for example:
from the 2021-09-09 00:00 +02 to 2021-09-10 00:00 +02. But the following code:
set timezone TO 'Europe/Berlin';
SELECT TIMESTAMP::timestamptz,sensor_id,value
FROM my_table
WHERE sensor_id IN (1,2,3)
AND TIMESTAMP > '2021-09-09' AND TIMESTAMP < '2021-09-10'
ORDER BY TIMESTAMP
gives me the right values but with wrong timezone for example first rows are:
timestamp,sensor_id,value
2021-09-09 02:00 +02,1,21
2021-09-09 02:00 +02,2,34
2021-09-09 02:00 +02,3,54
but should be 2021-09-09 00:00 +02 or 2021-09-08 22:00 +00
The problem is bigger when the difference with utc changes between winter and summer
Anybony can help me?
PostgreSQL v.12
SELECT TIMESTAMP::timestamptz AT TIME ZONE 'CETDST',sensor_id,value
FROM my_table
WHERE sensor_id IN (1,2,3)
AND TIMESTAMP AT TIME ZONE 'CETDST' > '2021-09-09'
AND TIMESTAMP AT TIME ZONE 'CETDST' < '2021-09-10'
ORDER BY TIMESTAMP

PostgreSQL grouping by day with timezone and DST

I'm working on an existing database which date+time values are stored in BIGINT column (milliseconds since EPOCH). For each entry I can get the corresponding time zone from another table. But to make things easier to understand I will explain my problem by simulating the problem.
The timestamp 1609534800000 is equal to 2021-01-01 21:00:00 at GMT (00-00)
Now if I run the following queries (with DBeaver)
set time zone 'America/Asuncion'; -- (UTC-3)
select
to_timestamp(1609534800000 / 1000) as "1"
, to_timestamp(1609534800000 / 1000) at TIME zone 'America/New_York' as "2"
, date_trunc('day', (to_timestamp(1609534800000 / 1000) at TIME zone 'America/New_York')) as "3"
, to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake' as "4"
, date_trunc('day', (to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake')) as "5"
, date_part('epoch', (to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake')) as "6"
, date_part('epoch', (date_trunc('day', (to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake') ) ) ) as "7"
I'm getting
1 |2 |3 |4 |5 |6 |7 |
-------------------|-------------------|-------------------|-------------------|-------------------|----------|----------|
2021-01-01 16:00:00|2021-01-01 16:00:00|2021-01-01 00:00:00|2021-01-02 09:00:00|2021-01-02 00:00:00|1609578000|1609545600|
I don't understand the result at all. According to the documentation, the function to_timestamp is supposed to return a timestamp with time zone ? In this case the time zone applied should be the one in my session America/Asuncion (UTC-3). If at GMT the time is 2021-01-01 21:00:00, I should get 2021-01-01 18:00:00. Because (21:00 - 3h = 18:00). Why 16h ?
From my understanding this result is OK as 2021-01-01 21:00:00 at UTC -5h for timezone America/New_York is equal to 2021-01-01 16:00.
Here I'm asking the same thing as the #2 but I want to discard the time of the day. So 2021-01-01 16:00:00 is 2021-01-01 00:00:00. The result is OK.
This result is still OK as 2021-01-01 21:00:00 at UTC + 12h for timezone Pacific/Wake is equal to 2021-01-02 09:00:00.
5 I'm asking the same thing as the #4 but I want to discard the time of the day. So 2021-01-02 09:00:00 is 2021-01-02 00:00:00. The result is OK.
I want to extract the unix EPOCH time in seconds of this timestamp. If I well understand, the timestamp pass to the date_part function is now a timestamp without time zone. Now if I use an online converter to convert the resulting value 1609578000 to GMT time then I'm getting 2021-01-02 9:00:00. Which is OK for me.
This is the same operation as the #6 but I want the unix epoch from the beginning of the day of that local time. The resulting value 1609545600 correspond to the GMT time 2021-01-02 00:00:00. Which is NOT correct as I should get 2021-01-02 12:00:00 as 'Pacific/Wake' is 12h past GMT.
(UPDATED)
Also why Montreal locale time is not correct here ? I'm supposed to have 2021-01-01 00:00:00-05
select ((to_timestamp(1609477200000 /1000) at time zone 'America/Asuncion') at time zone 'America/Asuncion') as asuncion
, ((to_timestamp(1609477200000 /1000) at time zone 'America/Montreal') at time zone 'America/Montreal') as montreal
asuncion | montreal
------------------------+------------------------
2021-01-01 02:00:00-03 | 2021-01-01 02:00:00-03
How could I get
asuncion | montreal
------------------------+------------------------
2021-01-01 02:00:00-03 | 2021-01-01 00:00:00-05
Is there a way to see the time WITHOUT the configured session timeszone ?
PS : My Windows OS timezone is set at America/New_York and I'm using PostgreSQL 10.
Best regards,
I can't replicate 1). I get:
set time zone 'America/Asuncion';
select to_timestamp(1609534800000 / 1000);
to_timestamp
------------------------
2021-01-01 18:00:00-03
As to 6), you did not account for the SET timezone:
select date_part('epoch', date_trunc('day', to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake') at TIME zone 'Pacific/Wake');
date_part
------------
1609502400
select to_timestamp(1609502400);
to_timestamp
------------------------
2021-01-01 09:00:00-03
--What happened
select to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake';
timezone
---------------------
2021-01-02 09:00:00
(1 row)
--- Note you lopped off 9 hours and the returned timestamp has no time zone offset
--- so it is now local time 'America/Asuncion'
select date_trunc('day', to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake');
date_trunc
---------------------
2021-01-02 00:00:00
--- This gives it back a timezone offset.
--- Running the date_part on this then gets you the proper value.
select date_trunc('day', to_timestamp(1609534800000 / 1000) at TIME zone 'Pacific/Wake') at TIME zone 'Pacific/Wake';
timezone
------------------------
2021-01-01 09:00:00-03

where with two colums time without zone: greatest

I have two columns one is a time the other a timestamp
ALTER TABLE public.tour
ADD COLUMN reprocess_toupdate timestamp without time zone DEFAULT NOW();
ALTER TABLE public.tour
ADD COLUMN reprocess_updated time without time zone DEFAULT NOW();
when I execute:
select reprocess_toupdate, reprocess_updated
from tour
where reprocess_toupdate::date > reprocess_updated::date;
I get an error:
ERROR: cannot cast type time without time zone to date
without ::date, I get this error:
ERROR: operator does not exist: timestamp without time zone > time without time zone
That is because a TIME column does not have a date component. It's range of values is 00:00:00 - 24:00:00. see Documentation Section 8.5 Date/Time Types. Since it does not have a date component you cannot cast it as date. The proper solution would to change the type to "timestamp without time zone". If that is not possible then compare just the times or to "reattach" the date then compare:
with dateset as
(select '2019-06-02 13:00:00'::time without time zone tm, (now() - interval '1 day')::timestamp without time zone dt)
select tm, dt, date_trunc('day', dt)+tm redt from dateset
Works here:
create temporary table so (id serial primary key, ts timestamp default now());
insert into so (ts) values (now());
select * from so where ts::date < now();
Output:
+------+----------------------------+
| id | ts |
|------+----------------------------|
| 1 | 2019-07-01 10:16:43.093662 |
+------+----------------------------+

date at time zone related syntax and semantic differences

Question: How is query 1 "semantically" different than the query 2?
Background:
To extract data from the table in a db which is at my localtime zone (AT TIME ZONE 'America/New_York').
The table has data for various time zones such as the 'America/Los_Angeles', America/North_Dakota/New_Salem and such time zones.
(Postgres stores the table data for various timezones in my local timezone)
So, everytime I retrieve data for a different location other than my localtime, I convert it to its relevant timezone for evaluation purposes..
Query 1:
test_db=# select count(id) from click_tb where date::date AT TIME ZONE 'America/Los_Angeles' = '2017-05-22'::date AT TIME ZONE 'America/Los_Angeles';
count
-------
1001
(1 row)
Query 2:
test_db=# select count(id) from click_tb where (date AT TIME ZONE 'America/Los_Angeles')::date = '2017-05-22'::date;
count
-------
5
(1 row)
Table structure:
test_db=# /d+ click_tb
Table "public.click_tb"
Column | Type | Modifiers | Storage | Stats target | Description
-----------------------------------+--------------------------+-------------------------------------------------------------+----------+--------------+-------------
id | integer | not null default nextval('click_tb_id_seq'::regclass) | plain | |
date | timestamp with time zone | | plain | |
Indexes:
"click_tb_id" UNIQUE CONSTRAINT, btree (id)
"click_tb_date_index" btree (date)
The query 1 and query 2 do not produce consistent results.
As per my tests, the below query 3, semantically addresses my requirement.
Your critical feedback is welcome.
Query 3:
test_db=# select count(id) from click_tb where ((date AT TIME ZONE 'America/Los_Angeles')::timestamp with time zone)::date = '2017-05-22'::date;
Do not convert the timestamp field. Instead, do a range query. Since your data is already using a timestamp with time zone type, just set the time zone of your query accordingly.
set TimeZone = 'America/Los_Angeles';
select count(id) from click_tb
where date >= '2017-01-02'
and date < '2017-01-03';
Note how this uses a half open interval of the dates (at start of day in the set time zone). If you want to compute the second date from your first date, then:
set TimeZone = 'America/Los_Angeles';
select count(id) from click_tb
where date >= '2017-01-02'
and date < (timestamp with time zone '2017-01-02' + interval '1 day');
This properly handles daylight saving time, and sargability.