Is INTERVAL '1' DAY always equal to INTERVAL '24' HOURS? - sql

Intuitively these two intervals represent the same amount of time. But the difference shows around daylight-saving time changes, in which case "1 day" can mean either "23 hours" in the spring or "25 hours" in the autumn.
I tested with PostgreSQL and there these two intervals don't mean the same amount of time:
set timezone TO 'CET';
SELECT timestamp with time zone'2020-03-29 0:00 Europe/Bratislava' + INTERVAL '1' DAY,
timestamp with time zone'2020-03-29 0:00 Europe/Bratislava' + INTERVAL '24' HOUR;
returns this:
2020-03-29T22:00:00.000Z 2020-03-29T23:00:00.000Z
The client's time zone was UTC, that's why the returned values are in UTC. But important is that they are not the same.
I also tried with MySQL but seems to me that it doesn't support time zones, only time zone offsets. And zone offsets don't have DST changes so a day is always 24 hours.
On the other hand Apache Calcite, that backs many SQL implementations such as Apache Drill, Apache Flink, Apache Beam and many more, represents interval literals as java's BigDecimal: the day-second interval is converted to milliseconds and day is assumed to always be 24 hours.
My question is: which approach is correct according to the SQL standard?
EDIT:
Checked more DBs:
Oracle: SELECT INTERVAL '1' DAY FROM DUAL returns +01 00:00:00. Adding either 1 day or 24 hours to 2020-03-29 0:00 CET provides the same result: 24 hours are added.
SQL Server and DB2: as far as I can tell, only time zone offsets are supported. So same case as MySql: they don't support time zones with DST changes.
Conclusion: PostgreSQL seems to be the only exception to have 1 day different from 24 hours.

I think your answer lies in this resource: https://www.postgresql.org/docs/9.1/functions-datetime.html
When adding an interval value to (or subtracting an interval value
from) a timestamp with time zone value, the days component advances
(or decrements) the date of the timestamp with time zone by the
indicated number of days. Across daylight saving time changes (with
the session time zone set to a time zone that recognizes DST), this
means interval '1 day' does not necessarily equal interval '24 hours'.
For example, with the session time zone set to CST7CDT, timestamp with
time zone '2005-04-02 12:00-07' + interval '1 day' will produce
timestamp with time zone '2005-04-03 12:00-06', while adding interval
'24 hours' to the same initial timestamp with time zone produces
timestamp with time zone '2005-04-03 13:00-06', as there is a change
in daylight saving time at 2005-04-03 02:00 in time zone CST7CDT.

Related

Oracle SQL : how to specify Time Zone Region

to_date('30/03/2022', 'DD/MM/YYYY')
Underlined, as hours are not specified, that means that hour is '00:00'
I would like to specify that this is for Europe/Paris time zone region.
Can you help me set-up this ?
Thanks
A DATE data type has the components: year, month, day, hour, minute and second. It ALWAYS has those components and NEVER stores anything else (such as a time zone); so it is impossible to store a time zone in a DATE data type.
A TIMESTAMP data type has the components: year, month, day, hour, minute and second and, optionally, can store fractional seconds.
A TIMESTAMP WITH TIME ZONE data type has the components: year, month, day, hour, minute, second and time zone and, optionally, can store fractional seconds information.
Therefore, if you want to store a time zone then you should use TIMESTAMP WITH TIME ZONE and not DATE.
Your code would then be:
TO_TIMESTAMP_TZ('30/03/2022 Europe/Paris', 'DD/MM/YYYY TZR')
or using a timestamp literal:
TIMESTAMP '2022-03-30 00:00:00 Europe/Paris'
or, if you want to pass in your date in that format and add the time zone in a two-step process:
FROM_TZ(TO_TIMESTAMP('30/03/2022', 'DD/MM/YYYY'), 'Europe/Paris')
db<>fiddle here

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;

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.

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