mirror of
https://github.com/timescale/timescaledb.git
synced 2025-04-20 13:53:19 +08:00
Fix semantics of time_bucket on DATE input
Previously, date was auto-cast to timestamptz when time_bucket was called. This led to weird behavior with regards to timezones and the return value was a timestamptz. This PR makes time_bucket return a DATE on DATE input and avoids all timezone conversions.
This commit is contained in:
parent
0137c92cdb
commit
3c69e4fc9e
@ -7,6 +7,10 @@ CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAM
|
||||
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ) RETURNS TIMESTAMPTZ
|
||||
AS '$libdir/timescaledb', 'timestamptz_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--bucketing on date should not do any timezone conversion
|
||||
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts DATE) RETURNS DATE
|
||||
AS '$libdir/timescaledb', 'date_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- If an interval is given as the third argument, the bucket alignment is offset by the interval.
|
||||
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAMP, "offset" INTERVAL)
|
||||
RETURNS TIMESTAMP LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
|
||||
@ -20,6 +24,13 @@ $BODY$
|
||||
SELECT public.time_bucket(bucket_width, ts-"offset")+"offset";
|
||||
$BODY$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts DATE, "offset" INTERVAL)
|
||||
RETURNS DATE LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
|
||||
$BODY$
|
||||
SELECT (public.time_bucket(bucket_width, ts-"offset")+"offset")::date;
|
||||
$BODY$;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width BIGINT, ts BIGINT)
|
||||
RETURNS BIGINT LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
|
||||
$BODY$
|
||||
|
50
src/utils.c
50
src/utils.c
@ -6,6 +6,7 @@
|
||||
#include <catalog/pg_type.h>
|
||||
#include <catalog/namespace.h>
|
||||
#include <utils/guc.h>
|
||||
#include <utils/date.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "nodes/nodes.h"
|
||||
@ -323,3 +324,52 @@ timestamptz_bucket(PG_FUNCTION_ARGS)
|
||||
}
|
||||
PG_RETURN_TIMESTAMPTZ(result);
|
||||
}
|
||||
|
||||
static inline void
|
||||
check_period_is_daily(int64 period)
|
||||
{
|
||||
#ifdef HAVE_INT64_TIMESTAMP
|
||||
int64 day = USECS_PER_DAY;
|
||||
#else
|
||||
int64 day = SECS_PER_DAY;
|
||||
#endif
|
||||
if (period < day)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("interval must not have sub-day precision")
|
||||
));
|
||||
}
|
||||
if (period % day != 0)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("interval must be a multiple of a day")
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
PG_FUNCTION_INFO_V1(date_bucket);
|
||||
|
||||
Datum
|
||||
date_bucket(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Interval *interval = PG_GETARG_INTERVAL_P(0);
|
||||
DateADT date = PG_GETARG_DATEADT(1);
|
||||
Datum converted_ts,
|
||||
bucketed;
|
||||
int64 period = -1;
|
||||
|
||||
if (DATE_NOT_FINITE(date))
|
||||
PG_RETURN_DATEADT(date);
|
||||
|
||||
period = get_interval_period(interval);
|
||||
/* check the period aligns on a date */
|
||||
check_period_is_daily(period);
|
||||
|
||||
/* convert to timestamp (NOT tz), bucket, convert back to date */
|
||||
converted_ts = DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1));
|
||||
bucketed = DirectFunctionCall2(timestamp_bucket, PG_GETARG_DATUM(0), converted_ts);
|
||||
return DirectFunctionCall1(timestamp_date, bucketed);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ SELECT count(*)
|
||||
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
|
||||
count
|
||||
-------
|
||||
124
|
||||
126
|
||||
(1 row)
|
||||
|
||||
\c postgres
|
||||
@ -67,7 +67,7 @@ SELECT count(*)
|
||||
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
|
||||
count
|
||||
-------
|
||||
124
|
||||
126
|
||||
(1 row)
|
||||
|
||||
\c single
|
||||
|
@ -239,6 +239,11 @@ SELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';
|
||||
|
||||
SELECT _timescaledb_internal.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');
|
||||
ERROR: timestamp out of range
|
||||
--no time_bucketing of dates not by integer # of days
|
||||
SELECT time_bucket('1 hour', DATE '2012-01-01');
|
||||
ERROR: interval must not have sub-day precision
|
||||
SELECT time_bucket('25 hour', DATE '2012-01-01');
|
||||
ERROR: interval must be a multiple of a day
|
||||
\set ON_ERROR_STOP 1
|
||||
SELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');
|
||||
time_bucket
|
||||
@ -563,3 +568,44 @@ FROM unnest(ARRAY[
|
||||
108 | 108
|
||||
(4 rows)
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '1 day', time::date)
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-05',
|
||||
date '2017-11-06'
|
||||
]) AS time;
|
||||
time | time_bucket
|
||||
------------+-------------
|
||||
11-05-2017 | 11-05-2017
|
||||
11-06-2017 | 11-06-2017
|
||||
(2 rows)
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '4 day', time::date)
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-02',
|
||||
date '2017-11-03',
|
||||
date '2017-11-06',
|
||||
date '2017-11-07'
|
||||
]) AS time;
|
||||
time | time_bucket
|
||||
------------+-------------
|
||||
11-02-2017 | 10-30-2017
|
||||
11-03-2017 | 11-03-2017
|
||||
11-06-2017 | 11-03-2017
|
||||
11-07-2017 | 11-07-2017
|
||||
(4 rows)
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-04',
|
||||
date '2017-11-05',
|
||||
date '2017-11-08',
|
||||
date '2017-11-09'
|
||||
]) AS time;
|
||||
time | time_bucket
|
||||
------------+-------------
|
||||
11-04-2017 | 11-01-2017
|
||||
11-05-2017 | 11-05-2017
|
||||
11-08-2017 | 11-05-2017
|
||||
11-09-2017 | 11-09-2017
|
||||
(4 rows)
|
||||
|
||||
|
@ -144,6 +144,11 @@ SELECT _timescaledb_internal.to_unix_microseconds('294247-01-01 23:59:59.999999'
|
||||
SELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';
|
||||
SELECT _timescaledb_internal.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');
|
||||
|
||||
--no time_bucketing of dates not by integer # of days
|
||||
|
||||
SELECT time_bucket('1 hour', DATE '2012-01-01');
|
||||
SELECT time_bucket('25 hour', DATE '2012-01-01');
|
||||
|
||||
\set ON_ERROR_STOP 1
|
||||
|
||||
SELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');
|
||||
@ -328,3 +333,26 @@ FROM unnest(ARRAY[
|
||||
'107',
|
||||
'108'
|
||||
]::int[]) AS time;
|
||||
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '1 day', time::date)
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-05',
|
||||
date '2017-11-06'
|
||||
]) AS time;
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '4 day', time::date)
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-02',
|
||||
date '2017-11-03',
|
||||
date '2017-11-06',
|
||||
date '2017-11-07'
|
||||
]) AS time;
|
||||
|
||||
SELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')
|
||||
FROM unnest(ARRAY[
|
||||
date '2017-11-04',
|
||||
date '2017-11-05',
|
||||
date '2017-11-08',
|
||||
date '2017-11-09'
|
||||
]) AS time;
|
||||
|
Loading…
x
Reference in New Issue
Block a user