Group by time with timezone conversion in Postgresql - sql

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.

Related

How to get all data post midnight of different timezone?

I have a PostgreSQL table named testing with a column named creation_time as timestamp with time zone. The database timezone is UTC
Now I want to get all rows whose time is greater than 00:00 of the current day as per the timezone "America/New_York".
I know how to get all rows after local midnight:
SELECT * FROM testing
WHERE ( creation_time >= now()::date)
ORDER BY id DESC
But how to use this query with a different timezone?
Assuming "the current day" is also defined by NY time, not by the current timezone setting.
SELECT *
FROM testing
WHERE creation_time >= date_trunc('day', now() AT TIME ZONE 'America/New_York') AT TIME ZONE 'America/New_York'
ORDER BY id DESC;
Yes, AT TIME ZONE 'America/New_York' twice. No typo there.
now() AT TIME ZONE 'America/New_York') gets local NY time. date_trunc gets 00:00 of that day. The 2nd AT TIME ZONE 'America/New_York' converts the local time back to timestamptz, which we finally compare to.
If you want NY 00:00 of your local date, it's simpler:
WHERE creation_time >= CURRENT_DATE::timestamp AT TIME ZONE 'America/New_York'
Same time, but can be a different day!
CURRENT_DATE is the local date (date according to the time zone setting of the current session). Effectively the same as now()::date.
Further reading:
Ignoring time zones altogether in Rails and PostgreSQL

How to compute duration between two times in postgresql when the end time is sometimes past midnight

I have to compute the duration of events in postgres based on start_time and end_time, both are in the HH:MM:SS format. Simply end_time - start_time works:
create table test_date as
select sum(eindtijd - starttijd) as tijdsduur
from evenementen_2019;
This results in an interval in HH:MM:SS format. But sometimes the end_time is the next day. Examples:
start_time end_time duration computed
18:00 21:00 3:00 3:00
18:00 0:00 6:00 -18:00
18:00 1:00 7:00 -17:00
I only have times without time zones, no dates.
The solution is conceptually simple: when duration < 0, add 24 hours to it. So I tried:
update test_date
set duration = to_date('24:00:00', 'HHMMSS') - duration
where duration < 0 * interval '1' second;
This generates an error:
ERROR: column "duration" is of type interval but expression is of type timestamp without time zone
That's right and I thought that a timestamp - interval yields a timestamp. I am at a loss on how to solve this problem. Does somebody know what is the best way to solve this problem?
I thought that a timestamp - interval yields a timestamp
It does. And you then try to store that resulting timestamp into a column of type interval, which doesn't work.
Note that if it survived this part, it still wouldn't work, because your to_date function call would fail at run time.
You say you should add 24 hours, but you what you try to do is subtract from 24 hours (of the wrong type), which would be wrong if it did work.
You can translate what you said you want to do almost word for word into SQL:
update test_date
set duration = duration + interval '24 hours'
where duration < 0 * interval '1' second;

Automatically convert dates based on time zone

Objective:
Knowing that my main column dates is in UTC time but is taking data from the Time zone 'Dateline Standard Time', how can I ensure that my query accounts for the offset without having to manually do DATEADD(HOUR,-8,column_name). This is especially important for day light savings periods.
select
, created_at
, created_at at time zone 'Dateline Standard Time' as dates_zone
The output of this is:
created_at created_at_timezone
2019-07-01 00:45:04.000 2019-07-01 00:45:04.000 -12:00
but I'd rather have the end result as such:
(which is basically UTC-8)
created_at_modified
2019-06-30 16:45:04.000
You can convert the date to datetimeoffset:
SELECT CONVERT(datetimeoffset,created_at) AT TIME ZONE 'Dateline Standard Time' created_at_modified
FROM …
;

pgSql not pulling all data for a date

The following query against Postgres database some how leaves few rows for the date: 2017-10-01. I have added time zone also. Is there a way to solve this issues?
select min(p.start_timestamp AT TIME ZONE p.timezone AT TIME ZONE 'America/Phoenix') as Date,
'America/Phoenix' AS Timezone, sum(GREATEST(0, p.value)) as Value, p.uom as UnitOfMeasurement
from main.production_ts_2017_10 p
where p.start_timestamp AT TIME ZONE p.timezone >= to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day'
and p.start_timestamp AT TIME ZONE p.timezone <= '2017-10-30'
and p.serial_number = '5T7842974Z'
group by date_trunc('hour', p.start_timestamp AT TIME ZONE p.timezone AT TIME ZONE 'America/Phoenix'), p.uom
order by Date
Let's simplify it down to just this part of the query:
p.start_timestamp AT TIME ZONE p.timezone >= to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day'
This is a conditional expression around the >= operator. It has a left-hand side (p.start_timestamp AT TIME ZONE p.timezone) and a right-hand side: (to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day') that are each evaluated separately, so the final conditional result (true/false) can be determined for each record.
Postgres database some how leaves few rows for the date: 2017-10-01
Look again at the right-hand side of the expression:
to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day'
That reduces down to this value:
'2017-10-02'
In other words, you're limited to records that come after '2017-10-01'. Do you really want that INTERVAL section in there? Did you want to subtract two days instead of add?
Let's assume two things:
You're asking "why does this query show a few rows for October 1st when it should show only rows for October 2nd or later"
p.start_timestamp is of type TIMESTAMP WITHOUT TIME ZONE (I feel safe in assuming this, because otherwise the double-timezone casting of it in other parts of the query makes no sense).
You have this WHERE clause:
where p.start_timestamp AT TIME ZONE p.timezone >= to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day'
Now, the left-hand side of the query is p.start_timestamp AT TIME ZONE p.timezone which will evaluate to a TIMESTAMP WITH TIME ZONE. The right-hand side of the query, however, is to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day' which evaluates to a TIMESTAMP WITHOUT TIMEZONE.
Whenever you compare a TIMESTAMP WITH TIMEZONE to a TIMESTAMP WITHOUT TIMEZONE, the results are going to be dependent on your current TimeZone setting in your psql session, because the TIMESTAMP-no-TZ will be evaluated as if it is in the psql TimeZone. For example, 12:31AM in 'America/Phoenix' on October 2nd can be 11:31AM in 'America/Los_Angeles', depending on the date (and BTW, you chose the worst possible time zone as your default, because America/Phoenix is just wierd). This would result in you seeing records from October 1st, depending on the value of p.timezone.
You're compounding the problem with this:
select min(p.start_timestamp AT TIME ZONE p.timezone AT TIME ZONE 'America/Phoenix') as Date
... so now you're displaying all timestamps as America/Phoenix timestamps regardless of what timezone they were originally in, or what timezone they were compared with. If your TimeZone setting is UTC, you'll see a LOT of October 1st records, because Oct 2nd in UTC overlaps with Oct 1st in America/Phoenix for six or seven hours depending on the date.
I suspect, given the rest of the query, what you want for that WHERE clause is:
where ( p.start_timestamp AT TIME ZONE p.timezone )
>=
( to_date('2017-09-30','YYYY-MM-DD') + INTERVAL '2 day' )
AT TIME ZONE 'America/Phoenix'
AND p.start_timestamp AT TIME ZONE p.timezone
<=
( TIMESTAMP '2017-10-30' AT TIME ZONE 'America/Phoenix' )
All of this is an illustrative lesson in why you should be storing all of your timestamp data as TIMESTAMP WITH TIME ZONE, instead of what you're doing here. As long as you're storing the timezone in a separate field an using AT TIME ZONE all the time, you're going to keep breaking queries. Also, with all of this casting time zones, any indexes you have on those time columns are unlikely to be used.

In Postgres, how do you extract the month (according to specific timezone) from a given TIMESTAMP WITH TIME ZONE column?

I have a column called login_timestamp, which is of type TIMESTAMP WITH TIME ZONE.
To retrieve the month for this timestamp, I would do: EXTRACT(MONTH FROM login_timestamp).
However, I would like to retrieve the month for a specific time zone (in my case, Pakistan), but can't figure out how to do that.
Documentation for this is under Date/Time Functions and Operators. Search that page for "at time zone".
select extract(month from login_timestamp at time zone 'Asia/Karachi');
You can change the time zone for a single session or for a single transaction with set session... or set local.... For example, this changes the time zone for the current session.
set session time zone 'Asia/Karachi';
Use the AT TIME ZONE construct:
SELECT EXTRACT(MONTH FROM login_timestamp AT TIME ZONE '-5');
-5 is the constant offset for Pakistan.
Details:
Ignoring timezones altogether in Rails and PostgreSQL
Try applying AT TIME ZONE. Demo
select extract(month from cast ('2017-07-01 01:00+03' as TIMESTAMP WITH TIME ZONE) AT TIME ZONE '+08') as monthNo
returns
monthno
1 6