mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-17 02:53:51 +08:00
Support timezones in time_bucket_ng()
This patch adds support of timezones in time_bucket_ng(). The 'origin' argument can't be used with timezones yet. This will be implemented in a separate pull request.
This commit is contained in:
parent
76ad38636b
commit
22e77a77ad
@ -24,20 +24,30 @@
|
||||
-- [2]: https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-TIMEZONES
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE) RETURNS DATE
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_date' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_date' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE, origin DATE) RETURNS DATE
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_date' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_date' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
|
||||
-- utility functions
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP) RETURNS TIMESTAMP
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamp' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamp' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP, origin TIMESTAMP) RETURNS TIMESTAMP
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamp' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamp' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
|
||||
|
||||
-- The following two versions of time_bucket_ng() are kept for the backward
|
||||
-- compatibility with time_bucket(). They convert 'ts' to UTC instead of treating
|
||||
-- it in the given timezone, which is almost certainly not something you want.
|
||||
-- Future versions may WARN you about this fact, and be completely removed
|
||||
-- eventually.
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ) RETURNS TIMESTAMPTZ
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
|
||||
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ) RETURNS TIMESTAMPTZ
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
|
||||
|
||||
-- TIMESTAMPTZ version of time_bucket_ng().
|
||||
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT) RETURNS TIMESTAMPTZ
|
||||
AS '@MODULE_PATHNAME@', 'ts_time_bucket_ng_timezone' LANGUAGE C STABLE PARALLEL SAFE STRICT;
|
||||
|
||||
|
@ -0,0 +1,2 @@
|
||||
DROP FUNCTION IF EXISTS timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT);
|
||||
|
@ -462,3 +462,28 @@ ts_time_bucket_ng_date(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_DATEADT(date);
|
||||
}
|
||||
|
||||
TS_FUNCTION_INFO_V1(ts_time_bucket_ng_timezone);
|
||||
TSDLLEXPORT Datum
|
||||
ts_time_bucket_ng_timezone(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Timestamp result;
|
||||
Datum timestamp;
|
||||
Datum interval = PG_GETARG_DATUM(0);
|
||||
Datum timestamptz = PG_GETARG_DATUM(1);
|
||||
Datum tzname = PG_GETARG_DATUM(2);
|
||||
|
||||
/*
|
||||
* Convert 'timestamptz' to TIMESTAMP at given 'tzname'.
|
||||
* The code is equal to 'timestamptz AT TIME ZONE tzname'.
|
||||
*/
|
||||
timestamp = DirectFunctionCall2(timestamptz_zone, tzname, timestamptz);
|
||||
|
||||
/* Then treat resulting timestamp as a regular one */
|
||||
result =
|
||||
DatumGetTimestamp(DirectFunctionCall2(ts_time_bucket_ng_timestamp, interval, timestamp));
|
||||
if (TIMESTAMP_NOT_FINITE(result))
|
||||
PG_RETURN_TIMESTAMP(result);
|
||||
|
||||
PG_RETURN_DATUM(DirectFunctionCall2(timestamp_zone, tzname, TimestampGetDatum(result)));
|
||||
}
|
||||
|
@ -21,5 +21,6 @@ extern TSDLLEXPORT int64 ts_time_bucket_by_type(int64 interval, int64 timestamp,
|
||||
extern TSDLLEXPORT Datum ts_time_bucket_ng_date(PG_FUNCTION_ARGS);
|
||||
extern TSDLLEXPORT Datum ts_time_bucket_ng_timestamp(PG_FUNCTION_ARGS);
|
||||
extern TSDLLEXPORT Datum ts_time_bucket_ng_timestamptz(PG_FUNCTION_ARGS);
|
||||
extern TSDLLEXPORT Datum ts_time_bucket_ng_timezone(PG_FUNCTION_ARGS);
|
||||
|
||||
#endif /* TIMESCALEDB_TIME_BUCKET_H */
|
||||
|
@ -1240,6 +1240,9 @@ SELECT timescaledb_experimental.time_bucket_ng('1 day', '2000-01-02' :: date, or
|
||||
ERROR: origin must be before the given date
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 month 3 hours', '2021-11-22' :: timestamp) AS result;
|
||||
ERROR: interval can't combine months with minutes or hours
|
||||
-- timestamp is less than the default 'origin' value
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 day', '1999-01-01 12:34:56 MSK' :: timestamptz, timezone => 'MSK');
|
||||
ERROR: origin must be before the given date
|
||||
\set ON_ERROR_STOP 1
|
||||
-- wrappers
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-11-22' :: timestamp) AS result;
|
||||
@ -1285,6 +1288,12 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz) AS
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz, timezone => 'Europe/Moscow') AS result;
|
||||
result
|
||||
--------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: date, origin => '2021-06-01') AS result;
|
||||
result
|
||||
--------
|
||||
@ -1322,6 +1331,12 @@ SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: ti
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamptz, 'Europe/Moscow') AS result;
|
||||
result
|
||||
--------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12' :: date, origin => '2021-06-01') AS result;
|
||||
result
|
||||
--------
|
||||
@ -1378,6 +1393,12 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamp
|
||||
infinity
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamptz, timezone => 'Europe/Moscow') AS result;
|
||||
result
|
||||
----------
|
||||
infinity
|
||||
(1 row)
|
||||
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: date, origin => '2021-06-01') AS result;
|
||||
result
|
||||
----------
|
||||
@ -1435,6 +1456,17 @@ SELECT timescaledb_experimental.time_bucket_ng('12 hours', '2021-07-12 12:34:56'
|
||||
infinity
|
||||
(1 row)
|
||||
|
||||
-- test for invalid timezone argument
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :: timestamptz, timezone => null) AS result;
|
||||
result
|
||||
--------
|
||||
|
||||
(1 row)
|
||||
|
||||
\set ON_ERROR_STOP 0
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :: timestamptz, timezone => 'Europe/Ololondon') AS result;
|
||||
ERROR: time zone "Europe/Ololondon" not recognized
|
||||
\set ON_ERROR_STOP 1
|
||||
-- Make sure time_bucket_ng() supports seconds, minutes, and hours.
|
||||
-- We happen to know that the internal implementation is the same
|
||||
-- as for time_bucket(), thus there is no reason to execute all the tests
|
||||
@ -1627,6 +1659,39 @@ FROM generate_series('2015-01-01' :: date, '2020-12-01', '6 month') AS ts,
|
||||
2020-07-01 | 2020-06-01 | 2019-12-01 | 2020-06-01 | 2020-06-01 | 2018-06-01
|
||||
(12 rows)
|
||||
|
||||
-- Test timezones support with different bucket sizes
|
||||
BEGIN;
|
||||
-- Timestamptz type is displayed in the session timezone.
|
||||
-- To get consistent results during the test we temporary set the session
|
||||
-- timezone to the known one.
|
||||
SET TIME ZONE '+00';
|
||||
-- Moscow is UTC+3 in the year 2021. Let's say you are dealing with '1 day' bucket.
|
||||
-- In order to calculate the beginning of the bucket you have to take LOCAL
|
||||
-- Moscow time and throw away the time. You will get the midnight. The new day
|
||||
-- starts 3 hours EARLIER in Moscow than in UTC+0 time zone, thus resulting
|
||||
-- timestamp will be 3 hours LESS than for UTC+0.
|
||||
SELECT bs, tz, to_char(ts_out, 'YYYY-MM-DD HH24:MI:SS TZ') as res
|
||||
FROM unnest(array['Europe/Moscow', 'UTC']) as tz,
|
||||
unnest(array['12 hours', '1 day', '1 month', '4 months', '1 year']) as bs,
|
||||
unnest(array['2021-07-12 12:34:56 Europe/Moscow' :: timestamptz]) as ts_in,
|
||||
unnest(array[timescaledb_experimental.time_bucket_ng(bs :: interval, ts_in, timezone => tz)]) as ts_out
|
||||
ORDER BY tz, bs :: interval;
|
||||
bs | tz | res
|
||||
----------+---------------+-------------------------
|
||||
12 hours | Europe/Moscow | 2021-07-12 09:00:00 +00
|
||||
1 day | Europe/Moscow | 2021-07-11 21:00:00 +00
|
||||
1 month | Europe/Moscow | 2021-06-30 21:00:00 +00
|
||||
4 months | Europe/Moscow | 2021-04-30 21:00:00 +00
|
||||
1 year | Europe/Moscow | 2020-12-31 21:00:00 +00
|
||||
12 hours | UTC | 2021-07-12 00:00:00 +00
|
||||
1 day | UTC | 2021-07-12 00:00:00 +00
|
||||
1 month | UTC | 2021-07-01 00:00:00 +00
|
||||
4 months | UTC | 2021-05-01 00:00:00 +00
|
||||
1 year | UTC | 2021-01-01 00:00:00 +00
|
||||
(10 rows)
|
||||
|
||||
-- Restore previously used time zone.
|
||||
ROLLBACK;
|
||||
-------------------------------------
|
||||
--- Test time input functions --
|
||||
-------------------------------------
|
||||
|
@ -610,6 +610,8 @@ SELECT timescaledb_experimental.time_bucket_ng('1 month', '2001-02-03' :: date,
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 month', '2000-01-02' :: date, origin => '2001-01-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 day', '2000-01-02' :: date, origin => '2001-01-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 month 3 hours', '2021-11-22' :: timestamp) AS result;
|
||||
-- timestamp is less than the default 'origin' value
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 day', '1999-01-01 12:34:56 MSK' :: timestamptz, timezone => 'MSK');
|
||||
\set ON_ERROR_STOP 1
|
||||
|
||||
-- wrappers
|
||||
@ -622,6 +624,7 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-11-22' :: timesta
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: date) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamp) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz, timezone => 'Europe/Moscow') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: date, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamp, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz, origin => '2021-06-01') AS result;
|
||||
@ -630,6 +633,7 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', null :: timestamptz, or
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12' :: date) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamp) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamptz) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamptz, 'Europe/Moscow') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12' :: date, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamp, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng(null, '2021-07-12 12:34:56' :: timestamptz, origin => '2021-06-01') AS result;
|
||||
@ -643,6 +647,7 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: date) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamp) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamptz) AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamptz, timezone => 'Europe/Moscow') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: date, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamp, origin => '2021-06-01') AS result;
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', 'infinity' :: timestamptz, origin => '2021-06-01') AS result;
|
||||
@ -657,6 +662,12 @@ SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :
|
||||
-- test for specific code path: hours/minutes/seconds interval and timestamp argument
|
||||
SELECT timescaledb_experimental.time_bucket_ng('12 hours', '2021-07-12 12:34:56' :: timestamp, origin => 'infinity') AS result;
|
||||
|
||||
-- test for invalid timezone argument
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :: timestamptz, timezone => null) AS result;
|
||||
\set ON_ERROR_STOP 0
|
||||
SELECT timescaledb_experimental.time_bucket_ng('1 year', '2021-07-12 12:34:56' :: timestamptz, timezone => 'Europe/Ololondon') AS result;
|
||||
\set ON_ERROR_STOP 1
|
||||
|
||||
-- Make sure time_bucket_ng() supports seconds, minutes, and hours.
|
||||
-- We happen to know that the internal implementation is the same
|
||||
-- as for time_bucket(), thus there is no reason to execute all the tests
|
||||
@ -731,6 +742,28 @@ SELECT to_char(d, 'YYYY-MM-DD') AS d,
|
||||
FROM generate_series('2015-01-01' :: date, '2020-12-01', '6 month') AS ts,
|
||||
unnest(array[ts :: date]) AS d;
|
||||
|
||||
-- Test timezones support with different bucket sizes
|
||||
BEGIN;
|
||||
-- Timestamptz type is displayed in the session timezone.
|
||||
-- To get consistent results during the test we temporary set the session
|
||||
-- timezone to the known one.
|
||||
SET TIME ZONE '+00';
|
||||
|
||||
-- Moscow is UTC+3 in the year 2021. Let's say you are dealing with '1 day' bucket.
|
||||
-- In order to calculate the beginning of the bucket you have to take LOCAL
|
||||
-- Moscow time and throw away the time. You will get the midnight. The new day
|
||||
-- starts 3 hours EARLIER in Moscow than in UTC+0 time zone, thus resulting
|
||||
-- timestamp will be 3 hours LESS than for UTC+0.
|
||||
SELECT bs, tz, to_char(ts_out, 'YYYY-MM-DD HH24:MI:SS TZ') as res
|
||||
FROM unnest(array['Europe/Moscow', 'UTC']) as tz,
|
||||
unnest(array['12 hours', '1 day', '1 month', '4 months', '1 year']) as bs,
|
||||
unnest(array['2021-07-12 12:34:56 Europe/Moscow' :: timestamptz]) as ts_in,
|
||||
unnest(array[timescaledb_experimental.time_bucket_ng(bs :: interval, ts_in, timezone => tz)]) as ts_out
|
||||
ORDER BY tz, bs :: interval;
|
||||
|
||||
-- Restore previously used time zone.
|
||||
ROLLBACK;
|
||||
|
||||
-------------------------------------
|
||||
--- Test time input functions --
|
||||
-------------------------------------
|
||||
|
Loading…
x
Reference in New Issue
Block a user