Accounting for timezone in postgres query - sql

In am trying to automatically account for timezone in my postgres query. My goal is to select records that are between 0:00 and 23:59 in EST or UTC - 5:00.
The current query only returns records between 0:00 and 23:59 in UTC time.
select path, start_time from routes where routes.network_id = 1 and routes.start_time between '2017-06-13 00:00:00'::timestamp AND '2017-06-13 23:59:59'::timestamp
the column start_time is a timestamp without timezone, so by default it is in UTC
SELECT pg_typeof("start_time") from routes limit 1;
returns timestamp without timezone
how would one write a query to account for 5 hours difference and convert start_time to UTC - 5?

Try this:
select path, start_time - interval '5 hours' as start_time_est
from routes
where routes.network_id = 1
and routes.start_time between '2017-06-13 00:00:00-5'::timestamp with time zone
AND '2017-06-13 23:59:59-5'::timestamp with time zone;

Related

Group by time with timezone conversion in Postgresql

I am working with time data that is currently stores in UTC but I want it to be in PST, which is 8 hours behind. I have a pretty lengthy and involved query, but the only thing I am interested in is the time right now so I have included those parts. I want to convert the times to PST and then group by the date for the last week of data. The query has the following structure:
select
date_trunc('day', time1) AT TIME ZONE 'US/Pacific'
...
where
time1 AT TIME ZONE 'US/Pacific' > now() AT TIME ZONE current_setting('TimeZone') - INTERVAL '168 HOURS'
...
group by date_trunc('day', time1)
This results in the following time groupings. From my understanding, it groups from the 0:00 UTC, which is 16:00 in PST. However, I want the groupby to start at 0:00 PST. How do I do this? Right now, the counts in each group are misleading for each day because they go from 4 pm to 4 pm instead of 12 am to 12 am. For example, Sundays have uncharacteristically high counts because Sunday includes part of Monday's data in the groupby. I would appreciate any input to fix this issue. Thank you.
The answer depends on whether it is a timestamp with time zone or one without:
If it's a timestamp with time zone, you can convert to PST with select time1 AT TIME ZONE 'US/Pacific' and get the date with select date_trunc('day', time1 AT TIME ZONE 'US/Pacific')
If it's a timestamp without time zone stored in UTC that you want to convert, you first have to tell PostgreSQL to interpret it as UTC, then convert it, like so: select (time1 AT TIME ZONE 'Z') AT TIME ZONE 'US/Pacific' and of course you can get the date with select date_trunc('day', (time1 AT TIME ZONE 'Z') AT TIME ZONE 'US/Pacific')
In either case you have to convert time zones before truncating to the day level or you may end up with inaccurate results.

Oracle SQL sysdate with time

I am using Oracle SQL. I'm trying to use sysdate with time to give the me query result. I have placed a time in the where clause, but I need it to be automatic and using the sysdate and converting to local time is the correct way. Any thoughts?
SELECT RESOURCE, AVG(SALES) AS SALES
FROM Z_HOURLY_RESOURCE
WHERE DATE_TIME_START BETWEEN to_date(to_char(FROM_TZ( CAST( (to_date('2018-08-02T05:00:00','yyyy-MM-dd"T"HH24:mi:ss') )AS TIMESTAMP ), 'America/Los_Angeles') AT TIME ZONE 'UTC', 'yyyy-MM-dd"T"HH24:mi:ss'),'yyyy-MM-dd"T"HH24:mi:ss')
AND to_date(to_char(FROM_TZ( CAST( (to_date('2018-08-02T13:00:00','yyyy-MM-dd"T"HH24:mi:ss')+1) AS TIMESTAMP ), 'America/Los_Angeles') AT TIME ZONE 'UTC', 'yyyy-MM-dd"T"HH24:mi:ss'),'yyyy-MM-dd"T"HH24:mi:ss')
It's slightly hard to work out quite what you need without sample data, but it sounds like you want to convert the time range 05:00 to 13:00 in your local session time zone (e.g. LA) to UTC to compare with the UTC-based timestamps in your table.
You can do that with:
WHERE DATE_TIME_START >= sys_extract_utc(cast(trunc(current_date) + 5/24 as timestamp with time zone))
AND DATE_TIME_START < sys_extract_utc(cast(trunc(current_date) + 13/24 as timestamp with time zone))
I've used >= and < rather than between on the assumption you really want up to 13:00, which is usually the case for time ranges. If you do want to include data form exactly 13:00:00 then change that < to <=, or go back to between.
TO explain what that is doing a but: current_date gives you the date/time in your session time zone. Truncating that sets the time to midnight (by default), so you can then add either 5 or 13 hours to get the times you want. That is still a date, so you can cast to timestamp with time zone so it represents that time in your session time zone again. You can then use sys_extract_utc() to get the UTC-equivalent timestamp.
To demonstrate those steps:
alter session set time_zone = 'America/Los_Angeles';
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF1';
alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS.FF1 TZH:TZM';
select sysdate as a,
current_date as b,
trunc(current_date) as c,
trunc(current_date) + 5/24 as d,
cast(trunc(current_date) + 5/24 as timestamp with time zone) as e,
sys_extract_utc(cast(trunc(current_date) + 5/24 as timestamp with time zone)) as f
from dual
union all
select sysdate as a,
current_date as b,
trunc(current_date) as c,
trunc(current_date) + 13/24 as d,
cast(trunc(current_date) + 13/24 as timestamp with time zone) as e,
sys_extract_utc(cast(trunc(current_date) + 13/24 as timestamp with time zone)) as f
from dual;
A B C D E F
------------------- ------------------- ------------------- ------------------- ---------------------------- ---------------------
2018-08-02 18:56:23 2018-08-02 10:56:23 2018-08-02 00:00:00 2018-08-02 05:00:00 2018-08-02 05:00:00.0 -07:00 2018-08-02 12:00:00.0
2018-08-02 18:56:23 2018-08-02 10:56:23 2018-08-02 00:00:00 2018-08-02 13:00:00 2018-08-02 13:00:00.0 -07:00 2018-08-02 20:00:00.0
First of all you don't have to cast a TIMESTAMP to a CHAR and then back again to a TIMESTAMP.
Assuming DATE_TIME_START is a TIMESTAMP and times are given in UTC you can make it simpler. When Oracle compares TIMESTAMP WITH TIME ZONE values then comparison are always done automatically at UTC time value. Your condition would be like this.
SELECT RESOURCE, AVG(SALES) AS SALES
FROM Z_HOURLY_RESOURCE
WHERE FROM_TZ(DATE_TIME_START, 'UTC')
BETWEEN TO_TIMESTAMP_TZ('2018-08-02T05:00:00 America/Los_Angeles', 'yyyy-MM-dd"T"HH24:mi:ss TZR')
AND TO_TIMESTAMP_TZ('2018-08-02T13:00:00 America/Los_Angeles', 'yyyy-MM-dd"T"HH24:mi:ss TZR')
However, due to function FROM_TZ(DATE_TIME_START, 'UTC') the performance might not be the best, it depends on your data.
If you need condition based on current time it would be like this:
SELECT RESOURCE, AVG(SALES) AS SALES
FROM Z_HOURLY_RESOURCE
WHERE FROM_TZ(DATE_TIME_START, 'UTC')
BETWEEN TRUNC(SYSTIMESTAMP)
AND TRUNC(SYSTIMESTAMP) + INTERVAL '1' DAY
Above query just illustrate time zone handling. You don't have to consider time zone of SYSTIMESTAMP, comparison will work in any case.

Rails/Postgres query all records created after 5 PM in taking into account daylight savings switch?

I am looking to query for all records on a table with a created_at time of after 5 PM, EST WITH taking into account daylight savings.
My DB is Postgres and all timestamps are stored in UTC as a normal Rails app.
So, say I have four records
ID, created_at, time in EST (-5 offset from UTC),
1, "2017-01-01 22:46:21.829333", 5:46 PM
2, "2017-01-01 21:23:27.259393", 4:23 PM
-------- DST SWITCH -----
ID, created_at, time in EDT (-4 offset from UTC),
3, "2017-03-20 21:52:46.135713", 5:52 PM
4, "2017-06-21 20:08:53.034377", 4:08 PM
My query should return records 1 and 3, but should ignore 2 and 4.
I have tried
SELECT "id",
"created_at"
FROM "orders"
WHERE (created_at::TIME WITHOUT TIME ZONE AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern' > ("created_at"::DATE + TIME '21:00')::TIME AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern')
ORDER BY "orders"."created_at" ASC
But this query will return record 1,2,3 when it should not return 2.
Sorry about my previous wrong answer. I don't do much with USA time.
all timestamps are stored in UTC
So ("created_at"::DATE + TIME '21:00')::TIME AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern' actually says it is utc time 21:00, which does not necessarily guarantee it is 5 PM in EST because the timezone offset changes from 5 to 4. So the compare condition is not correct.
You need to get the local time to make the comparison, rather than the utc time.
Try following code:
SELECT
"id",
"created_at"
FROM "orders"
WHERE (created_at AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern') :: TIME > TIME '5:00 PM'

PostgreSQL: Adding an interval to a timestamp in a different time zone

What is the best way to add a specified interval to a timestamp with time zone, if I don't want to do the calculation in the time zone of the server. This is particularly important around daylight savings transitions.
e.g.
consider the evening that we "spring forward". (Here in Toronto, I think it was 2016-03-13 at 2am).
If I take a time stamp:
2016-03-13 00:00:00-05
and add '1 day' to it, in Canada/Eastern, I would expect to get 2016-03-14 00:00:00-04 -> 1 day later, but actually only 23 hours
But if I add 1 day to it in Saskatchewan (a place that doesn't use DST), I would want it to add 24 hours, so that I'd end up with
2016-03-13 01:00:00-04.
If I have columns / variables
t1 timestamp with time zone;
t2 timestamp with time zone;
step interval;
zoneid text; --represents the time zone
I essentially want to say
t2 = t1 + step; --in a time zone of my choosing
Postgres documentation seems to indicate that timestamp with time zone is internally stored in UTC time, which seems to indicate that a timestamptz column has no reckoning of a time zone in it.
The SQL standard indicates that
datetime + interval operation should maintain the time zone of the first operand.
t2 = (t1 AT TIME ZONE zoneid + step) AT TIME ZONE zoneid;
doesn't seem to work because the first cast turns t1 into a timezone-less timestamp and thus can't reckon DST transitions
t2 = t1 + step;
doesn't seem to work as it does the operation in the time zone of my SQL server
set the postgres time zone before the operation and change it back after?
A better illustration:
CREATE TABLE timestamps (t1 timestamp with time zone, timelocation text);
SET Timezone 'America/Toronto';
INSERT INTO timestamps(t1, timelocation) VALUES('2016-03-13 00:00:00 America/Toronto', 'America/Toronto');
INSERT INTO timestamps(t1, timelocation) VALUES('2016-03-13 00:00:00 America/Regina', 'America/Regina');
SELECT t1, timelocation FROM timestamps; -- shows times formatted in Toronto time. OK
"2016-03-13 00:00:00-05";"America/Toronto"
"2016-03-13 01:00:00-05";"America/Regina"
SELECT t1 + '1 day', timelocation FROM timestamps; -- Toronto timestamp has advanced by 23 hours. OK. Regina time stamp has also advanced by 23 hours. NOT OK.
"2016-03-14 00:00:00-04";"America/Toronto"
"2016-03-14 01:00:00-04";"America/Regina"
How to get around this?
a) Cast the timestamptz to a timestamp tz in the appropriate time zone?
SELECT t1 AT TIME ZONE timelocation + '1 day', timelocation FROM timestamps; --OK. Though my results are timestamps without time zone now.
"2016-03-14 00:00:00";"America/Toronto"
"2016-03-14 00:00:00";"America/Regina"
SELECT t1 AT TIME ZONE timelocation + '4 hours', timelocation FROM timestamps; -- NOT OK. I want the Toronto time to be 5am
"2016-03-13 04:00:00";"America/Toronto"
"2016-03-13 04:00:00";"America/Regina"
b) Change timezone of postgres and proceed.
SET TIMEZONE = 'America/Regina';
SELECT t1 + '1 day', timelocation FROM timestamps; -- Now the Regina time stamp is correct, but toronto time stamp is incorrect (should be 22:00-06)
"2016-03-13 23:00:00-06";"America/Toronto"
"2016-03-14 00:00:00-06";"America/Regina"
SET TIMEZONE = 'America/Toronto';
SELECT t1 + '1 day', timelocation FROM timestamps; -- toronto is correct, regina is not, as before
"2016-03-14 00:00:00-04";"America/Toronto"
"2016-03-14 01:00:00-04";"America/Regina"
This solution will only work if I continually switch the postgres timezone before every operation time interval operation.
It is a combination of two properties that causes your problem:
timestamp with time zone is stored in UTC and does not contain any time zone information. A better name for it would be “UTC timestamp”.
Addition of timestamp with time zone and interval is always performed in the current time zone, i.e. the one set with the configuration parameter TimeZone.
Since what you really need to store is a timestamp and the time zone in which it is valid, you should store a combination of timestamp without time zone and a text representing the time zone.
As you correctly noticed, you would have to switch the current time zone to perform interval addition over the daylight savings time shift correctly (otherwise PostgreSQL does not know how long 1 day is).
But you don't have to do that by hand, you can use a PL/pgSQL function to do it for you:
CREATE OR REPLACE FUNCTION add_in_timezone(
ts timestamp without time zone,
tz text,
delta interval
) RETURNS timestamp without time zone
LANGUAGE plpgsql IMMUTABLE AS
$$DECLARE
result timestamp without time zone;
oldtz text := current_setting('TimeZone');
BEGIN
PERFORM set_config('TimeZone', tz, true);
result := (ts AT TIME ZONE tz) + delta;
PERFORM set_config('TimeZone', oldtz, true);
RETURN result;
END;$$;
That would give you the following, where the result is to be understood in the same time zone as the argument:
test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Toronto', '1 day');
add_in_timezone
---------------------
2016-03-14 00:00:00
(1 row)
test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Regina', '1 day');
add_in_timezone
---------------------
2016-03-14 00:00:00
(1 row)
test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Toronto', '4 hours');
add_in_timezone
---------------------
2016-03-13 05:00:00
(1 row)
test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Regina', '4 hours');
add_in_timezone
---------------------
2016-03-13 04:00:00
(1 row)
You could consider creating a combined type
CREATE TYPE timestampattz AS (
ts timestamp without time zone,
zone text
);
and define operators and casts on it, but that's probably a major project that exceeds what you want for this.
There even is a PostgreSQL extension timestampandtz that does exactly that; maybe that's just what you need (I didn't look what the semantics for addition are).
Based on the informations given by #LaurenzAlbe, I used something different to handle adding an interval to a timestamptz and using DST. I had the problem this 2020-10-25 since in Belgium (timezone "Europe/Brussels" which is UTC+1) we observe DST and on 2020-10-25 at 03:00 we went backward for 1 hour ending at 02:00, i.e. we went from summer time UTC+2 to winter time UTC+1.
The code below which has to find the timestamptz at 16:00 on a given day failed that day and instead ended at 15:00 because we went backward 1h. The problematic line of code was:
select date_trunc('day', myfct.datetime) + interval 'PT16H'
For example, first query is ok, second is not.
select date_trunc('day', timestamptz '2020-10-27 17:00:00+01') + interval 'PT16H';
?column?
------------------------
2020-10-27 16:00:00+01
select date_trunc('day', timestamptz '2020-10-25 17:00:00+01') + interval 'PT16H';
?column?
------------------------
2020-10-25 15:00:00+01
The idea is to make the addition with a timestamp (without time zone) instead of a timestamptz and finally convert it to a timestamptz with the configured time zone of the database.
hydro_dev=> select date_trunc('day', timestamptz '2020-10-25 17:00:00+01');
date_trunc
------------------------
2020-10-25 00:00:00+02
(1 row)
hydro_dev=> select date_trunc('day', timestamptz '2020-10-25 17:00:00+01')::timestamp;
date_trunc
---------------------
2020-10-25 00:00:00
(1 row)
hydro_dev=> select date_trunc('day', timestamptz '2020-10-25 17:00:00+01')::timestamp + interval 'PT16H';
?column?
---------------------
2020-10-25 16:00:00
(1 row)
select (date_trunc('day', timestamptz '2020-10-25 17:00:00+01')::timestamp + interval 'PT16H')::timestamptz;
timestamptz
------------------------
2020-10-25 16:00:00+01

how to get EST and UTC irrespective of Day

I have to show EST/EDT and GMT/UTC using Oracle 10g.The issue is on Nov 2nd UTC difference will change '+ 05' hour which will cause my jobs to fail.Please let me know how to fix it without manually changing interval to 5 hours.I have tried new_time function but that does not solve the problem. The all columns are DATE datatype.
SELECT PROCESS_NAME,
date_modified as EST_DATE_MODIFIED,
date_modified + interval '+ 04' hour as GMT_DATE_MODIFIED
FROM tbl_process_status ;
Result
EST_DATE_MODIFIED GMT_DATE_MODIFIED
22-OCT-14 11:08:25 22-OCT-14 03:08:25
Thanks Ed but above query is not returning correct UTC time.
EST - 22-OCT-14 12:19:55
Correct UTC- 22-OCT-14 04:19:55
Above Query UTC 22-OCT-14 08:19:55
select (TO_NUMBER(TO_CHAR(CURRENT_TIMESTAMP, 'TZH')) * INTERVAL '1' HOUR) from dual;
-0 4:0:0.0
Thanks Multisync.
I got below result but I need data only in DD-MON-YY HH:MM:SS Format.
Expected 22-OCT-14 12:19:55
Expected 22-OCT-14 04:19:55
Result 22-OCT-14 12.19.55.000000000 PM AMERICA/NEW_YORK
Result 22-OCT-14 04.19.55.000000000 PM UTC
select systimestamp at time zone 'est' for_timestamp,
cast(sysdate as timestamp) at time zone 'est' for_date,
cast(sysdate as timestamp) at time zone 'gmt' gmt_timezone
from dual;
So you can try this (edited according to the comments)
SELECT PROCESS_NAME,
cast(cast(date_modified as timestamp) at time zone 'America/New_York' as date) as EST_DATE_MODIFIED,
cast(cast(date_modified as timestamp) at time zone 'UTC' as date) as GMT_DATE_MODIFIED
FROM tbl_process_status;