-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.

-- Utility function for grouping/slotting time with a given interval.
CREATE OR REPLACE FUNCTION date_group(
    field           timestamp,
    group_interval  interval
)
    RETURNS timestamp LANGUAGE SQL STABLE AS
$BODY$
    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /
        EXTRACT(EPOCH from group_interval)::int) *
        EXTRACT(EPOCH from group_interval)::int)::timestamp;
$BODY$;

CREATE TABLE PUBLIC."testNs" (
  "timeCustom" TIMESTAMP NOT NULL,
  device_id TEXT NOT NULL,
  series_0 DOUBLE PRECISION NULL,
  series_1 DOUBLE PRECISION NULL,
  series_2 DOUBLE PRECISION NULL,
  series_bool BOOLEAN NULL
);
CREATE INDEX ON PUBLIC."testNs" (device_id, "timeCustom" DESC NULLS LAST) WHERE device_id IS NOT NULL;

\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE SCHEMA "testNs" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
SELECT * FROM create_hypertable('"public"."testNs"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );

\c :TEST_DBNAME
INSERT INTO PUBLIC."testNs"("timeCustom", device_id, series_0, series_1) VALUES
('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),
('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),
('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);

INSERT INTO PUBLIC."testNs"("timeCustom", device_id, series_0, series_1) VALUES
('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),
('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);

SELECT * FROM PUBLIC."testNs";

SET client_min_messages = WARNING;

\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'
SET timezone = 'UTC';
SELECT date_group("timeCustom", '100 days') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;

SET timezone = 'EST';
SELECT date_group("timeCustom", '100 days') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;

\echo 'The rest of the queries will be the same in output between UTC and EST'
SET timezone = 'UTC';
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;

SET timezone = 'EST';
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;

SET timezone = 'UTC';

SELECT *
FROM PUBLIC."testNs"
WHERE "timeCustom" >= TIMESTAMP '2009-11-10T23:00:00'
AND "timeCustom" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY "timeCustom" DESC, device_id, series_1;

SET timezone = 'EST';
SELECT *
FROM PUBLIC."testNs"
WHERE "timeCustom" >= TIMESTAMP '2009-11-10T23:00:00'
AND "timeCustom" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY "timeCustom" DESC, device_id, series_1;

SET timezone = 'UTC';
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC LIMIT 2;

SET timezone = 'EST';
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC LIMIT 2;

------------------------------------
-- Test time conversion functions --
------------------------------------
\set ON_ERROR_STOP 0

SET timezone = 'UTC';

-- Conversion to timestamp using Postgres built-in function taking
-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on
-- Postgres >= 9.6.3.
SELECT to_timestamp(1486480176.236538);

-- extension-specific version taking microsecond UNIX timestamp
SELECT _timescaledb_functions.to_timestamp(1486480176236538);

-- Should be the inverse of the statement above.
SELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');

-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN
-- -Infinity. We keep this notion for UNIX epoch time:
SELECT _timescaledb_functions.to_unix_microseconds('+infinity');
SELECT _timescaledb_functions.to_timestamp(9223372036854775807);
SELECT _timescaledb_functions.to_unix_microseconds('-infinity');
SELECT _timescaledb_functions.to_timestamp(-9223372036854775808);

-- In UNIX microseconds, the largest bigint value below infinity
-- (BIGINT MAX) is smaller than internal date upper bound and should
-- therefore be OK. Further, converting to the internal postgres epoch
-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a
-- later date compared to the UNIX epoch, and is therefore represented
-- by a smaller number
SELECT _timescaledb_functions.to_timestamp(9223372036854775806);

-- Julian day zero is -210866803200000000 microseconds from UNIX epoch
SELECT _timescaledb_functions.to_timestamp(-210866803200000000);

\set VERBOSITY default
-- Going beyond Julian day zero should give out-of-range error
SELECT _timescaledb_functions.to_timestamp(-210866803200000001);

-- Lower bound on date (should return the Julian day zero UNIX timestamp above)
SELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');

-- Going beyond lower bound on date should return out-of-range
SELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');

-- The upper bound for Postgres TIMESTAMPTZ
SELECT timestamp '294276-12-31 23:59:59.999999+00';

-- Going beyond the upper bound, should fail
SELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';

-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp
-- since the Postgres epoch is at a later date than the UNIX epoch.
SELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');

-- Subtracting the difference between the two epochs (10957 days) should bring
-- us within range.
SELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';

SELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');

-- Adding one microsecond should take us out-of-range again
SELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';
SELECT _timescaledb_functions.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');

SELECT time, time_bucket(INTERVAL '2 day ', time)
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-01 01:01:01',
    TIMESTAMP '2011-01-02 01:01:01',
    TIMESTAMP '2011-01-03 01:01:01',
    TIMESTAMP '2011-01-04 01:01:01'
    ]) AS time;


SELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')
FROM unnest(ARRAY[
    INTERVAL '1 millisecond',
    INTERVAL '1 second',
    INTERVAL '1 minute',
    INTERVAL '1 hour',
    INTERVAL '1 day',
    INTERVAL '2 millisecond',
    INTERVAL '2 second',
    INTERVAL '2 minute',
    INTERVAL '2 hour',
    INTERVAL '2 day'
    ]) AS int_def;

\set ON_ERROR_STOP 0
SELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');
SELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');
\set ON_ERROR_STOP 1

SELECT time, time_bucket(INTERVAL '5 minute', time)
FROM unnest(ARRAY[
    TIMESTAMP '1970-01-01 00:59:59.999999',
    TIMESTAMP '1970-01-01 01:01:00',
    TIMESTAMP '1970-01-01 01:04:59.999999',
    TIMESTAMP '1970-01-01 01:05:00'
    ]) AS time;


SELECT time, time_bucket(INTERVAL '5 minute', time)
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-02 01:04:59.999999',
    TIMESTAMP '2011-01-02 01:05:00',
    TIMESTAMP '2011-01-02 01:09:59.999999',
    TIMESTAMP '2011-01-02 01:10:00'
    ]) AS time;

--offset with interval
SELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-02 01:01:59.999999',
    TIMESTAMP '2011-01-02 01:02:00',
    TIMESTAMP '2011-01-02 01:06:59.999999',
    TIMESTAMP '2011-01-02 01:07:00'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-02 01:02:59.999999',
    TIMESTAMP '2011-01-02 01:03:00',
    TIMESTAMP '2011-01-02 01:07:59.999999',
    TIMESTAMP '2011-01-02 01:08:00'
    ]) AS time;

--offset with infinity
-- timestamp
SELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')
FROM unnest(ARRAY[
    timestamp '-Infinity',
    timestamp 'Infinity'
    ]) AS time;

-- timestamptz
SELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')
FROM unnest(ARRAY[
    timestamp with time zone '-Infinity',
    timestamp with time zone 'Infinity'
    ]) AS time;

-- Date
SELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')
FROM unnest(ARRAY[
    date '-Infinity',
    date 'Infinity'
    ]) AS date;

--example to align with an origin
SELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-02 01:01:59.999999',
    TIMESTAMP '2011-01-02 01:02:00',
    TIMESTAMP '2011-01-02 01:06:59.999999',
    TIMESTAMP '2011-01-02 01:07:00'
    ]) AS time;


--rounding version
SELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'
FROM unnest(ARRAY[
    TIMESTAMP '2011-01-02 01:05:01',
    TIMESTAMP '2011-01-02 01:07:29',
    TIMESTAMP '2011-01-02 01:02:30',
    TIMESTAMP '2011-01-02 01:07:30',
    TIMESTAMP '2011-01-02 01:02:29'
    ]) AS time;

--time_bucket with timezone should mimick date_trunc
SET timezone TO 'UTC';
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
    ]) AS time;

--what happens with a local tz
SET timezone TO 'America/New_York';
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
    ]) AS time;

--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.
SELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
    ]) AS time;

--can force local bucketing with simple cast.
SELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
    ]) AS time;

--can also use interval to correct
SELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
    ]) AS time;

--dst: same local hour bucketed as two different hours.
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'
    ]) AS time;

--local alignment changes when bucketing by UTC across dst boundary
SELECT time, time_bucket(INTERVAL '2 hour', time)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'
    ]) AS time;

--local alignment is preserved when bucketing by local time across DST boundary.
SELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)
FROM unnest(ARRAY[
    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',
    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'
    ]) AS time;

-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary
-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26
-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)
SELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,
    timezone := 'Asia/Amman', "offset" := '15 minutes'::interval);

-- GitHub issue #8851: time_bucket with negative offset during DST fall-back
-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26
-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)
SELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,
    timezone := 'Europe/Berlin', "offset" := '-15 seconds'::interval);

-- Additional DST edge cases for coverage of DST direction × offset sign combinations

-- Spring-forward + negative offset
-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)
SELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,
    timezone := 'Asia/Amman', "offset" := '-15 minutes'::interval);

-- Fall-back + positive offset
-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)
SELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,
    timezone := 'Europe/Berlin', "offset" := '15 minutes'::interval);

-- Input exactly at DST spring-forward transition
-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)
-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)
SELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,
    timezone := 'Asia/Amman', "offset" := '15 minutes'::interval);

-- Input exactly at DST fall-back transition
-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)
-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)
SELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,
    timezone := 'Europe/Berlin', "offset" := '15 minutes'::interval);

-- Offset larger than bucket size (1h offset with 30min bucket)
-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)
SELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,
    timezone := 'Asia/Amman', "offset" := '1 hour'::interval);

-- GitHub issue #9136: time_bucket with origin during DST fall-back
-- When origin is in standard time but timestamp is in daylight time,
-- the bucket could incorrectly start AFTER the timestamp.
-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03
-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST
-- Result should have bucket start <= timestamp (bucket in EDT, not EST)
SELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,
    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,
    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',
        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',
        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;

SELECT time,
    time_bucket(10::smallint, time) AS time_bucket_smallint,
    time_bucket(10::int, time) AS time_bucket_int,
    time_bucket(10::bigint, time) AS time_bucket_bigint
FROM unnest(ARRAY[
     '-11',
     '-10',
      '-9',
      '-1',
       '0',
       '1',
      '99',
     '100',
     '109',
     '110'
    ]::smallint[]) AS time;

SELECT time,
    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,
    time_bucket(10::int, time, 2::int) AS time_bucket_int,
    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint
FROM unnest(ARRAY[
      '-9',
      '-8',
      '-7',
       '1',
       '2',
       '3',
     '101',
     '102',
     '111',
     '112'
    ]::smallint[]) AS time;

SELECT time,
    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,
    time_bucket(10::int, time, -2::int) AS time_bucket_int,
    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint
FROM unnest(ARRAY[
    '-13',
    '-12',
    '-11',
     '-3',
     '-2',
     '-1',
     '97',
     '98',
    '107',
    '108'
    ]::smallint[]) AS time;


\set ON_ERROR_STOP 0
SELECT time_bucket(10::smallint, '-32768'::smallint);
SELECT time_bucket(10::smallint, '-32761'::smallint);
select time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);
select time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);
select time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);
\set ON_ERROR_STOP 1

SELECT time, time_bucket(10::smallint, time)
FROM unnest(ARRAY[
    '-32760',
    '-32759',
    '32767'
    ]::smallint[]) AS time;

\set ON_ERROR_STOP 0
SELECT time_bucket(10::int, '-2147483648'::int);
SELECT time_bucket(10::int, '-2147483641'::int);
SELECT time_bucket(1000::int, '-2147483000'::int, 1::int);
SELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);
SELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);
\set ON_ERROR_STOP 1

SELECT time, time_bucket(10::int, time)
FROM unnest(ARRAY[
    '-2147483640',
    '-2147483639',
    '2147483647'
    ]::int[]) AS time;

\set ON_ERROR_STOP 0
SELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);
SELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);
SELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);
SELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);
SELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);
\set ON_ERROR_STOP 1

SELECT time, time_bucket(10::bigint, time)
FROM unnest(ARRAY[
    '-9223372036854775800',
    '-9223372036854775799',
    '9223372036854775807'
    ]::bigint[]) 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-04',
    date '2017-11-05',
    date '2017-11-08',
    date '2017-11-09'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')
FROM unnest(ARRAY[
    date '2017-11-06',
    date '2017-11-07',
    date '2017-11-10',
    date '2017-11-11'
    ]) AS time;

-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,
-- Rather than a Saturday which is the date of the PostgreSQL epoch
SELECT time, time_bucket(INTERVAL '1 week', time::date)
FROM unnest(ARRAY[
    date '2018-09-16',
    date '2018-09-17',
    date '2018-09-23',
    date '2018-09-24'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp without time zone '2018-09-16',
    timestamp without time zone '2018-09-17',
    timestamp without time zone '2018-09-23',
    timestamp without time zone '2018-09-24'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp with time zone '2018-09-16',
    timestamp with time zone '2018-09-17',
    timestamp with time zone '2018-09-23',
    timestamp with time zone '2018-09-24'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp with time zone '-Infinity',
    timestamp with time zone 'Infinity'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp without time zone '-Infinity',
    timestamp without time zone 'Infinity'
    ]) AS time;


SELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp without time zone '4714-11-24 01:01:01.0 BC',
    timestamp without time zone '294276-12-31 23:59:59.9999'
    ]) AS time;

--1000 years later weeks still align.
SELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)
FROM unnest(ARRAY[
    timestamp without time zone '3018-09-14',
    timestamp without time zone '3018-09-20',
    timestamp without time zone '3018-09-21',
    timestamp without time zone '3018-09-22'
    ]) AS time;

--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).
SELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)
FROM unnest(ARRAY[
    timestamp with time zone '3018-09-14',
    timestamp with time zone '3018-09-20',
    timestamp with time zone '3018-09-21',
    timestamp with time zone '3018-09-22'
    ]) AS time;

--check functions with origin
--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point
\x
SELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,
             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,
             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,
             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,
             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,
             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,
             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2
FROM unnest(ARRAY[
    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',
    timestamp with time zone '2000-01-01 00:00:00+0',
    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',
    timestamp with time zone '2000-01-03 00:00:00+0',
    timestamp with time zone '2000-01-01',
    timestamp with time zone '2000-01-02',
    timestamp with time zone '2000-01-03',
    timestamp with time zone '3018-09-12',
    timestamp with time zone '3018-09-13',
    timestamp with time zone '3018-09-14',
    timestamp with time zone '3018-09-15'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,
             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,
             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,
             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,
             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,
             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2
FROM unnest(ARRAY[
    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',
    timestamp without time zone '2000-01-01 00:00:00',
    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',
    timestamp without time zone '2000-01-03 00:00:00',
    timestamp without time zone '2000-01-01',
    timestamp without time zone '2000-01-02',
    timestamp without time zone '2000-01-03',
    timestamp without time zone '3018-09-12',
    timestamp without time zone '3018-09-13',
    timestamp without time zone '3018-09-14',
    timestamp without time zone '3018-09-15'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,
             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,
             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,
             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,
             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,
             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2
FROM unnest(ARRAY[
    date '1999-12-31',
    date '2000-01-01',
    date '2000-01-02',
    date '2000-01-03',
    date '3018-09-12',
    date '3018-09-13',
    date '3018-09-14',
    date '3018-09-15'
    ]) AS time;
\x

--really old origin works if date around that time
SELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')
FROM unnest(ARRAY[
    timestamp without time zone '4710-11-24 01:01:01.0 BC',
    timestamp without time zone '4710-11-25 01:01:01.0 BC',
    timestamp without time zone '2001-01-01',
    timestamp without time zone '3001-01-01'
    ]) AS time;

SELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')
FROM unnest(ARRAY[
    timestamp without time zone '294270-12-29 23:59:59.9999',
    timestamp without time zone '294270-12-30 23:59:59.9999',
    timestamp without time zone '294270-12-31 23:59:59.9999',
    timestamp without time zone '2001-01-01',
    timestamp without time zone '3001-01-01'
    ]) AS time;

\set ON_ERROR_STOP 0
--really old origin + very new data + long period errors
SELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')
FROM unnest(ARRAY[
    timestamp without time zone '294270-12-31 23:59:59.9999'
    ]) AS time;
SELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')
FROM unnest(ARRAY[
    timestamp with time zone '294270-12-30 23:59:59.9999'
    ]) AS time;

--really high origin + old data + long period errors out
SELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')
FROM unnest(ARRAY[
    timestamp without time zone '4710-11-24 01:01:01.0 BC'
    ]) AS time;
SELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')
FROM unnest(ARRAY[
    timestamp with time zone '4710-11-24 01:01:01.0 BC'
    ]) AS time;
\set ON_ERROR_STOP 1

-------------------------------------------
--- Test time_bucket with month periods ---
-------------------------------------------

SET datestyle TO ISO;

SELECT
  time::date,
  time_bucket('1 month', time::date) AS "1m",
  time_bucket('2 month', time::date) AS "2m",
  time_bucket('3 month', time::date) AS "3m",
  time_bucket('1 month', time::date, '2000-02-01'::date) AS "1m origin",
  time_bucket('2 month', time::date, '2000-02-01'::date) AS "2m origin",
  time_bucket('3 month', time::date, '2000-02-01'::date) AS "3m origin"
FROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;

SELECT
  time,
  time_bucket('1 month', time) AS "1m",
  time_bucket('2 month', time) AS "2m",
  time_bucket('3 month', time) AS "3m",
  time_bucket('1 month', time, '2000-02-01'::timestamp) AS "1m origin",
  time_bucket('2 month', time, '2000-02-01'::timestamp) AS "2m origin",
  time_bucket('3 month', time, '2000-02-01'::timestamp) AS "3m origin"
FROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;

SELECT
  time,
  time_bucket('1 month', time) AS "1m",
  time_bucket('2 month', time) AS "2m",
  time_bucket('3 month', time) AS "3m",
  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS "1m origin",
  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS "2m origin",
  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS "3m origin"
FROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;

---------------------------------------
--- Test time_bucket with timezones ---
---------------------------------------

-- test NULL args
SELECT
time_bucket(NULL::interval,now(),'Europe/Berlin'),
time_bucket('1day',NULL::timestamptz,'Europe/Berlin'),
time_bucket('1day',now(),NULL::text),
time_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),
time_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),
time_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),
time_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',"offset":=NULL::interval),
time_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);

SET datestyle TO ISO;
SELECT
  time_bucket('1day', ts) AS "UTC",
  time_bucket('1day', ts, 'Europe/Berlin') AS "Berlin",
  time_bucket('1day', ts, 'Europe/London') AS "London",
  time_bucket('1day', ts, 'America/New_York') AS "New York",
  time_bucket('1day', ts, 'PST') AS "PST",
  time_bucket('1day', ts, current_setting('timezone')) AS "current"

FROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;

SELECT
  time_bucket('1month', ts) AS "UTC",
  time_bucket('1month', ts, 'Europe/Berlin') AS "Berlin",
  time_bucket('1month', ts, 'America/New_York') AS "New York",
  time_bucket('1month', ts, current_setting('timezone')) AS "current",
  time_bucket('2month', ts, current_setting('timezone')) AS "2m",
  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS "2m origin",
  time_bucket('2month', ts, current_setting('timezone'), "offset":='14 day'::interval) AS "2m offset",
  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS "2m offset + origin"

FROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;

RESET datestyle;

-------------------------------------
--- Test time input functions --
-------------------------------------
\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT
AS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER

SELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');
SELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);
---should give warning
SELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);

SELECT test.interval_to_internal('TIMESTAMP'::regtype);
SELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);

-- Default interval for integer is supported as part of
-- hypertable generalization
SELECT test.interval_to_internal('INT'::regtype);
SELECT test.interval_to_internal('SMALLINT'::regtype);
SELECT test.interval_to_internal('BIGINT'::regtype);
SELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);
SELECT test.interval_to_internal('TIMESTAMP'::regtype);
SELECT test.interval_to_internal('DATE'::regtype);

\set VERBOSITY terse
\set ON_ERROR_STOP 0
SELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);
SELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);
SELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);
SELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');
\set ON_ERROR_STOP 1
