Efficient time rounding function - postgresql

I am writing a custom postgresql function to round TIMESTAMPTZ fields to an arbitrary interval with the basic algorithm of round(timestamp / interval) * interval and some research, I found a solution:
SELECT to_timestamp(round((extract('epoch' from timestamp)) / interval) * interval)
it works. My question: is there a more efficient way of doing this?

Look this like a template:
Round a timestamp to the nearest 5-minute mark.
The sample usage:
postgres=# select now(), round_time('2010-09-17 16:48');
now | round_time
-------------------------------+------------------------
2010-09-19 08:36:31.701919+02 | 2010-09-17 16:50:00+02
(1 row)
postgres=# select now(), round_time('2010-09-17 16:58');
now | round_time
-------------------------------+------------------------
2010-09-19 08:36:43.860858+02 | 2010-09-17 17:00:00+02
(1 row)
postgres=# select now(), round_time('2010-09-17 16:57');
now | round_time
-------------------------------+------------------------
2010-09-19 08:36:53.273612+02 | 2010-09-17 16:55:00+02
(1 row)
postgres=# select now(), round_time('2010-09-17 23:58');
now | round_time
------------------------------+------------------------
2010-09-19 08:37:09.41387+02 | 2010-09-18 00:00:00+02
(1 row)

Related

Timestamp difference in numeric

I want to get the difference between two timestamps in days with decimals.
i.e. instead of interval, 3 days 20:33:54.937,
the desired output is 3.85 (in numeric format)
+---------------------+---------------------+--+
| date1 | date_2 | |
+---------------------+---------------------+--+
| 2020-12-22 08:10:11 | 2020-12-18 11:36:16 | |
+---------------------+---------------------+--+
Easiest thing is to get a number of seconds (with epoch) and then divide that into days
SELECT extract(epoch from (timestamptz '2021-01-12 13:55:23.631389+00' - timestamp '2021-01-09 09:55:23.631389+00')) / (24*60*60);
?column?
--------------------
3.1666666666666665
(1 row)
demo:db<>fiddle
You can use EXTRACT(epoch FROM your_interval) which returns the seconds of the interval. These can be converted into days:
SELECT EXTRACT(
epoch FROM (
'2020-12-22 08:10:11'::timestamp - '2020-12-18 11:36:16'::timestamp
)
)/3600/24

postgresql - NOW() but less specific

When I use SELECT NOW(); I'll get an output like this:
now
-------------------------------
2019-09-09 18:55:38.794006-05
(1 row)
I want it like this:
now
-------------------
2019-09-09 18:55:38
(1 row)
How do I make NOW() round up/down accordingly? I tried SELECT NOW()::timestamptz(0); but it keeps adding -05 to the end of the time :(
You can to_char
select to_char(now(), 'YYYY-MM-DD HH:MI:SS')
or use date_truc to retain datatype timestamp with time zone
select date_trunc('minute', now());
Convert NOW() to a timestamp without timezone:
SELECT NOW()::timestamp(0)

PGSQL - How do I Add 5 hours to DATE in WHERE Clause?

I either need to add 5 hours or convert from GMT to EST. The return is currently showing everything from 7p and later yesterday...
WHERE
incident.initial_symptom = 'Chrome Upgrade' AND
DATE(incident.install_completed) = CURRENT_DATE;
Instead of manually adding interval to get wanted time zone, use at time zone, eg:
t=# select now(), now() at time zone 'est';
now | timezone
------------------------------+---------------------------
2017-04-07 07:07:39.17234+00 | 2017-04-07 02:07:39.17234
(1 row)
Depending on your timezone, exactly same statement adding interval to your date gives different result, eg at DST shift hour:
t=# set timezone TO 'WET';
SET
t=# select '2017-03-26 00:00:00'::timestamptz + '1 hour'::interval;
?column?
------------------------
2017-03-26 02:00:00+01
(1 row)
t=# set timezone TO 'EET';
SET
t=# select '2017-03-26 00:00:00'::timestamptz + '1 hour'::interval;
?column?
------------------------
2017-03-26 01:00:00+02
(1 row)

How does Postgresql round half-microseconds in timestamps?

I was under the impression that PostgreSQL rounded half-microseconds in timestamps to the nearest even microsecond. E.g.:
> select '2000-01-01T00:00:00.0000585Z'::timestamptz;
timestamptz
-------------------------------
2000-01-01 01:00:00.000058+01
(1 row)
> select '2000-01-01T00:00:00.0000575Z'::timestamptz;
timestamptz
-------------------------------
2000-01-01 01:00:00.000058+01
(1 row)
Then I discovered that:
> select '2000-01-01T00:00:00.5024585Z'::timestamptz;
timestamptz
-------------------------------
2000-01-01 01:00:00.502459+01
(1 row)
Does anybody know the rounding algorithm Postgresql uses for timestamps?
For your information, here's the version of Postgresql I'm running:
> select version();
version
----------------------------------------------------------------------------------------------------------------
PostgreSQL 9.6.1 on x86_64-apple-darwin15.6.0, compiled by Apple LLVM version 8.0.0 (clang-800.0.42.1), 64-bit
(1 row)
All the PostgreSQL time types have a microsecond resolution, six decimal places. Rounding to the nearest even microsecond would not be microsecond resolution.
Its behavior looks consistent with round half-up to me, the usual way to round. >= 0.5 round up, else round down.
0.5024585 rounded half-up to 6 decimal places rounds up to 0.502459 because the 7th digit is 5.
test=# select '2000-01-01T00:00:00.5024585Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502459
(1 row)
0.5024584999999 rounds down to 0.502458 because the 7th digit is 4.
test=# select '2000-01-01T00:00:00.5024584999999Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502458
(1 row)
Nevermind, the above appears to be anomalous. Stepping through '2000-01-01T00:00:00.5024235Z' to '2000-01-01T00:00:00.5024355Z' is consistent with half-even rounding.
I'm going to guess the anomalies are due to floating point error converting from floating point seconds in the input to the integer microseconds that timestamp uses.
test=# select '2000-01-01T00:00:00.5024235Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502424
(1 row)
test=# select '2000-01-01T00:00:00.5024245Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502425
(1 row)
test=# select '2000-01-01T00:00:00.5024255Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502425
(1 row)
test=# select '2000-01-01T00:00:00.5024265Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502426
(1 row)
test=# select '2000-01-01T00:00:00.5024275Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502428
(1 row)
test=# select '2000-01-01T00:00:00.5024285Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502428
(1 row)
test=# select '2000-01-01T00:00:00.5024295Z'::timestamp;
timestamp
---------------------------
2000-01-01 00:00:00.50243
(1 row)
test=# select '2000-01-01T00:00:00.5024305Z'::timestamp;
timestamp
---------------------------
2000-01-01 00:00:00.50243
(1 row)
test=# select '2000-01-01T00:00:00.5024315Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502432
(1 row)
test=# select '2000-01-01T00:00:00.5024325Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502432
(1 row)
test=# select '2000-01-01T00:00:00.5024335Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502434
(1 row)
test=# select '2000-01-01T00:00:00.5024345Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502434
(1 row)
test=# select '2000-01-01T00:00:00.5024355Z'::timestamp;
timestamp
----------------------------
2000-01-01 00:00:00.502436
(1 row)
This also plays out with interval N microsecond. Less decimal places means less floating point error.
test=# select interval '0.5 microsecond';
interval
----------
00:00:00
(1 row)
test=# select interval '1.5 microsecond';
interval
-----------------
00:00:00.000002
(1 row)
test=# select interval '2.5 microsecond';
interval
-----------------
00:00:00.000002
(1 row)
test=# select interval '3.5 microsecond';
interval
-----------------
00:00:00.000004
(1 row)
test=# select interval '4.5 microsecond';
interval
-----------------
00:00:00.000004
(1 row)
test=# select interval '5.5 microsecond';
interval
-----------------
00:00:00.000006
(1 row)
test=# select interval '6.5 microsecond';
interval
-----------------
00:00:00.000006
(1 row)
A small C program confirms there's a floating point accuracy problem with single precision floats at 7 decimal places that would affect rounding.
#include <math.h>
#include <stdio.h>
int main() {
float nums[] = {
0.5024235f,
0.5024245f,
0.5024255f,
0.5024265f,
0.5024275f,
0.5024285f,
0.5024295f,
0.5024305f,
NAN
};
for( int i = 0; !isnan(nums[i]); i++ ) {
printf("%0.8f\n", nums[i]);
}
}
This produces:
0.50242352
0.50242448
0.50242549
0.50242651
0.50242752
0.50242847
0.50242949
0.50243050
Whereas with doubles, there's no problem.
0.50242350
0.50242450
0.50242550
0.50242650
0.50242750
0.50242850
0.50242950
0.50243050

Getting different results when converting timestamp to text

I'm tying to convert timestamp field in format (YYYY-MM-DD HH:MI:SS.MS) to it's text representation. But for some reason getting different results:
If I'm trying to convert timestamp from table:
create table test_dt (dt timestamp);
insert into test_dt values ('2016-04-14 17:10:33.007');
insert into test_dt values ('2016-04-14 17:10:33');
Timestamps are getting truncated up to the seconds:
select dt::text from test_dt;
dt
---------------------
2016-04-14 17:10:33
2016-04-14 17:10:33
(2 rows)
But if Im using direct select statement, everything works:
select '2016-04-14 17:10:33.007'::timestamp::text;
varchar
-------------------------
2016-04-14 17:10:33.007
(1 row)
The question is not how to convert it to the text from table and include precision, but rather:
what am I doing wrong?
why those 2 approaches returns different result?
what's the rational behind this behaviour?
UPDATE
as #muistooshort suggested the following command gives the correct result:
select c::text from (select '2016-04-14 17:10:33.007'::timestamp union select '2016-04-14 17:10:33'::timestamp ) as t(c);
c
-------------------------
2016-04-14 17:10:33
2016-04-14 17:10:33.007
(2 rows)
and yes test_dt does have .007 :
select * from test_dt;
dt
-------------------------
2016-04-14 17:10:33
2016-04-14 17:10:33.007
(2 rows)
Also to_char gives milliseconds from the table:
select to_char(dt, 'YYYY-MM-DD HH:MI:SS.MS') from test_dt;
to_char
-------------------------
2016-04-14 05:10:33.000
2016-04-14 05:10:33.007
(2 rows)