diff options
-rw-r--r-- | doc/src/sgml/func.sgml | 97 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 7 | ||||
-rw-r--r-- | src/backend/utils/adt/date.c | 15 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 10 | ||||
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 20 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.dat | 9 | ||||
-rw-r--r-- | src/test/regress/expected/timestamptz.out | 56 | ||||
-rw-r--r-- | src/test/regress/expected/timetz.out | 42 | ||||
-rw-r--r-- | src/test/regress/sql/timestamptz.sql | 23 | ||||
-rw-r--r-- | src/test/regress/sql/timetz.sql | 17 |
11 files changed, 294 insertions, 4 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 0769824e46b..affd1254bb7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10611,7 +10611,7 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 </sect2> <sect2 id="functions-datetime-zoneconvert"> - <title><literal>AT TIME ZONE</literal></title> + <title><literal>AT TIME ZONE and AT LOCAL</literal></title> <indexterm> <primary>time zone</primary> @@ -10622,6 +10622,10 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 <primary>AT TIME ZONE</primary> </indexterm> + <indexterm> + <primary>AT LOCAL</primary> + </indexterm> + <para> The <literal>AT TIME ZONE</literal> operator converts time stamp <emphasis>without</emphasis> time zone to/from @@ -10632,7 +10636,7 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 </para> <table id="functions-datetime-zoneconvert-table"> - <title><literal>AT TIME ZONE</literal> Variants</title> + <title><literal>AT TIME ZONE</literal> and <literal>AT LOCAL</literal> Variants</title> <tgroup cols="1"> <thead> <row> @@ -10667,6 +10671,22 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 <row> <entry role="func_table_entry"><para role="func_signature"> + <type>timestamp without time zone</type> <literal>AT LOCAL</literal> + <returnvalue>timestamp with time zone</returnvalue> + </para> + <para> + Converts given time stamp <emphasis>without</emphasis> time zone to + time stamp <emphasis>with</emphasis> the session's + <varname>TimeZone</varname> value as time zone. + </para> + <para> + <literal>timestamp '2001-02-16 20:38:40' at local</literal> + <returnvalue>2001-02-17 03:38:40+00</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> <type>timestamp with time zone</type> <literal>AT TIME ZONE</literal> <replaceable>zone</replaceable> <returnvalue>timestamp without time zone</returnvalue> </para> @@ -10683,6 +10703,22 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 <row> <entry role="func_table_entry"><para role="func_signature"> + <type>timestamp with time zone</type> <literal>AT LOCAL</literal> + <returnvalue>timestamp without time zone</returnvalue> + </para> + <para> + Converts given time stamp <emphasis>with</emphasis> time zone to + time stamp <emphasis>without</emphasis> time zone, as the time would + appear with the session's <varname>TimeZone</varname> value as time zone. + </para> + <para> + <literal>timestamp with time zone '2001-02-16 20:38:40-05' at local</literal> + <returnvalue>2001-02-16 18:38:40</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> <type>time with time zone</type> <literal>AT TIME ZONE</literal> <replaceable>zone</replaceable> <returnvalue>time with time zone</returnvalue> </para> @@ -10696,6 +10732,25 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 <returnvalue>10:34:17+00</returnvalue> </para></entry> </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>time with time zone</type> <literal>AT LOCAL</literal> + <returnvalue>time with time zone</returnvalue> + </para> + <para> + Converts given time <emphasis>with</emphasis> time zone to a new time + zone. Since no date is supplied, this uses the currently active UTC + offset for the session's <varname>TimeZone</varname> value. + </para> + <para> + Assuming the session's <varname>TimeZone</varname> is set to <literal>UTC</literal>: + </para> + <para> + <literal>time with time zone '05:34:17-05' at local</literal> + <returnvalue>10:34:17+00</returnvalue> + </para></entry> + </row> </tbody> </tgroup> </table> @@ -10711,6 +10766,13 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 </para> <para> + The syntax <literal>AT LOCAL</literal> may be used as shorthand for + <literal>AT TIME ZONE <replaceable>local</replaceable></literal>, where + <replaceable>local</replaceable> is the session's + <varname>TimeZone</varname> value. + </para> + + <para> Examples (assuming the current <xref linkend="guc-timezone"/> setting is <literal>America/Los_Angeles</literal>): <screen> @@ -10722,6 +10784,12 @@ SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT TIME ZONE 'America/D SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'America/Chicago'; <lineannotation>Result: </lineannotation><computeroutput>2001-02-16 05:38:40</computeroutput> + +SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT LOCAL; +<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 17:38:40</computeroutput> + +SELECT TIME WITH TIME ZONE '20:38:40-05' AT LOCAL; +<lineannotation>Result: </lineannotation><computeroutput>17:38:40</computeroutput> </screen> The first example adds a time zone to a value that lacks it, and displays the value using the current <varname>TimeZone</varname> @@ -10729,7 +10797,18 @@ SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'A to the specified time zone, and returns the value without a time zone. This allows storage and display of values different from the current <varname>TimeZone</varname> setting. The third example converts - Tokyo time to Chicago time. + Tokyo time to Chicago time. The fourth example shifts the time stamp + with time zone value to the time zone currently specified by the + <varname>TimeZone</varname> setting and returns the value without a + time zone. + </para> + + <para> + The fifth example is a cautionary tale. Due to the fact that there is no + date associated with the input value, the conversion is made using the + current date of the session. Therefore, this static example may show a wrong + result depending on the time of the year it is viewed because + <literal>'America/Los_Angeles'</literal> observes Daylight Savings Time. </para> <para> @@ -10745,6 +10824,18 @@ SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'A <literal><replaceable>time</replaceable> AT TIME ZONE <replaceable>zone</replaceable></literal>. </para> + + <para> + The function <literal><function>timezone</function>(<replaceable>timestamp</replaceable>)</literal> + is equivalent to the SQL-conforming construct <literal><replaceable>timestamp</replaceable> + AT LOCAL</literal>. + </para> + + <para> + The function <literal><function>timezone</function>(<replaceable>time</replaceable>)</literal> + is equivalent to the SQL-conforming construct <literal><replaceable>time</replaceable> + AT LOCAL</literal>. + </para> </sect2> <sect2 id="functions-datetime-current"> diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e56cbe77cb7..50ed504e5a0 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -14508,6 +14508,13 @@ a_expr: c_expr { $$ = $1; } COERCE_SQL_SYNTAX, @2); } + | a_expr AT LOCAL %prec AT + { + $$ = (Node *) makeFuncCall(SystemFuncName("timezone"), + list_make1($1), + COERCE_SQL_SYNTAX, + -1); + } /* * These operators must be called out explicitly in order to make use * of bison's automatic operator-precedence handling. All other diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index ae0f24de2c3..c4da10d47a9 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -3125,3 +3125,18 @@ timetz_izone(PG_FUNCTION_ARGS) PG_RETURN_TIMETZADT_P(result); } + +/* timetz_at_local() + * + * Unlike for timestamp[tz]_at_local, the type for timetz does not flip between + * time with/without time zone, so we cannot just call the conversion function. + */ +Datum +timetz_at_local(PG_FUNCTION_ARGS) +{ + Datum time = PG_GETARG_DATUM(0); + const char *tzn = pg_get_timezone_name(session_timezone); + Datum zone = PointerGetDatum(cstring_to_text(tzn)); + + return DirectFunctionCall2(timetz_zone, zone, time); +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 442205382e3..ed7f40f053c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10347,6 +10347,16 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) appendStringInfoChar(buf, ')'); return true; + case F_TIMEZONE_TIMESTAMP: + case F_TIMEZONE_TIMESTAMPTZ: + case F_TIMEZONE_TIMETZ: + /* AT LOCAL */ + appendStringInfoChar(buf, '('); + get_rule_expr_paren((Node *) linitial(expr->args), context, false, + (Node *) expr); + appendStringInfoString(buf, " AT LOCAL)"); + return true; + case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL: case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ: case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL: diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 0e50aaec5a5..e172e906142 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -5921,3 +5921,23 @@ generate_series_timestamptz_at_zone(PG_FUNCTION_ARGS) { return generate_series_timestamptz_internal(fcinfo); } + +/* timestamp_at_local() + * timestamptz_at_local() + * + * The regression tests do not like two functions with the same proargs and + * prosrc but different proname, but the grammar for AT LOCAL needs an + * overloaded name to handle both types of timestamp, so we make simple + * wrappers for it. + */ +Datum +timestamp_at_local(PG_FUNCTION_ARGS) +{ + return timestamp_timestamptz(fcinfo); +} + +Datum +timestamptz_at_local(PG_FUNCTION_ARGS) +{ + return timestamptz_timestamp(fcinfo); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 7d65feaef0f..473687419c9 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202309251 +#define CATALOG_VERSION_NO 202310131 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index f0b7b9cbd8e..72ea4aa8b8c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -2319,6 +2319,9 @@ { oid => '1159', descr => 'adjust timestamp to new time zone', proname => 'timezone', prorettype => 'timestamp', proargtypes => 'text timestamptz', prosrc => 'timestamptz_zone' }, +{ oid => '9159', descr => 'adjust timestamp to local time zone', + proname => 'timezone', provolatile => 's', prorettype => 'timestamp', + proargtypes => 'timestamptz', prosrc => 'timestamptz_at_local' }, { oid => '1160', descr => 'I/O', proname => 'interval_in', provolatile => 's', prorettype => 'interval', @@ -6095,6 +6098,9 @@ { oid => '2038', descr => 'adjust time with time zone to new zone', proname => 'timezone', prorettype => 'timetz', proargtypes => 'interval timetz', prosrc => 'timetz_izone' }, +{ oid => '9161', descr => 'adjust time to local time zone', + proname => 'timezone', provolatile => 's', prorettype => 'timetz', + proargtypes => 'timetz', prosrc => 'timetz_at_local' }, { oid => '2039', descr => 'hash', proname => 'timestamp_hash', prorettype => 'int4', proargtypes => 'timestamp', prosrc => 'timestamp_hash' }, @@ -6190,6 +6196,9 @@ { oid => '2070', descr => 'adjust timestamp to new time zone', proname => 'timezone', prorettype => 'timestamptz', proargtypes => 'interval timestamp', prosrc => 'timestamp_izone' }, +{ oid => '9160', descr => 'adjust timestamp to local time zone', + proname => 'timezone', provolatile => 's', prorettype => 'timestamptz', + proargtypes => 'timestamp', prosrc => 'timestamp_at_local' }, { oid => '2071', proname => 'date_pl_interval', prorettype => 'timestamp', proargtypes => 'date interval', prosrc => 'date_pl_interval' }, diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 0dd2fe2c82d..2ca2101dd42 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -3136,6 +3136,62 @@ SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; (1 row) -- +-- Test LOCAL time zone +-- +BEGIN; +SET LOCAL TIME ZONE 'Europe/Paris'; +VALUES (CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL); + column1 +-------------------------- + Sat Jul 08 01:38:00 1978 +(1 row) + +VALUES (TIMESTAMP '1978-07-07 19:38' AT LOCAL); + column1 +------------------------------- + Fri Jul 07 19:38:00 1978 CEST +(1 row) + +SET LOCAL TIME ZONE 'Australia/Sydney'; +VALUES (CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL); + column1 +-------------------------- + Sat Jul 08 09:38:00 1978 +(1 row) + +VALUES (TIMESTAMP '1978-07-07 19:38' AT LOCAL); + column1 +------------------------------- + Fri Jul 07 19:38:00 1978 AEST +(1 row) + +SET LOCAL TimeZone TO 'UTC'; +CREATE VIEW timestamp_local_view AS + SELECT CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL AS ttz_at_local, + timezone(CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE)) AS ttz_func, + TIMESTAMP '1978-07-07 19:38' AT LOCAL AS t_at_local, + timezone(TIMESTAMP '1978-07-07 19:38') AS t_func; +SELECT pg_get_viewdef('timestamp_local_view', true); + pg_get_viewdef +---------------------------------------------------------------------------------------------- + SELECT ('Fri Jul 07 23:38:00 1978 UTC'::timestamp with time zone AT LOCAL) AS ttz_at_local,+ + timezone('Fri Jul 07 23:38:00 1978 UTC'::timestamp with time zone) AS ttz_func, + + ('Fri Jul 07 19:38:00 1978'::timestamp without time zone AT LOCAL) AS t_at_local, + + timezone('Fri Jul 07 19:38:00 1978'::timestamp without time zone) AS t_func; +(1 row) + +\x +TABLE timestamp_local_view; +-[ RECORD 1 ]+----------------------------- +ttz_at_local | Fri Jul 07 23:38:00 1978 +ttz_func | Fri Jul 07 23:38:00 1978 +t_at_local | Fri Jul 07 19:38:00 1978 UTC +t_func | Fri Jul 07 19:38:00 1978 UTC + +\x +DROP VIEW timestamp_local_view; +COMMIT; +-- -- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504) -- create temp table tmptz (f1 timestamptz primary key); diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out index be49588b6d3..7293c3bbb7b 100644 --- a/src/test/regress/expected/timetz.out +++ b/src/test/regress/expected/timetz.out @@ -262,3 +262,45 @@ SELECT date_part('epoch', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401- 63025.575401 (1 row) +-- +-- AT LOCAL with timetz +-- +BEGIN; +SET LOCAL TimeZone TO 'UTC'; +CREATE VIEW timetz_local_view AS + SELECT f1 AS dat, + timezone(f1) AS dat_func, + f1 AT LOCAL AS dat_at_local, + f1 AT TIME ZONE current_setting('TimeZone') AS dat_at_time + FROM TIMETZ_TBL + ORDER BY f1; +SELECT pg_get_viewdef('timetz_local_view', true); + pg_get_viewdef +------------------------------------------------------------------------ + SELECT f1 AS dat, + + timezone(f1) AS dat_func, + + (f1 AT LOCAL) AS dat_at_local, + + (f1 AT TIME ZONE current_setting('TimeZone'::text)) AS dat_at_time+ + FROM timetz_tbl + + ORDER BY f1; +(1 row) + +TABLE timetz_local_view; + dat | dat_func | dat_at_local | dat_at_time +----------------+----------------+----------------+---------------- + 00:01:00-07 | 07:01:00+00 | 07:01:00+00 | 07:01:00+00 + 01:00:00-07 | 08:00:00+00 | 08:00:00+00 | 08:00:00+00 + 02:03:00-07 | 09:03:00+00 | 09:03:00+00 | 09:03:00+00 + 08:08:00-04 | 12:08:00+00 | 12:08:00+00 | 12:08:00+00 + 07:07:00-08 | 15:07:00+00 | 15:07:00+00 | 15:07:00+00 + 11:59:00-07 | 18:59:00+00 | 18:59:00+00 | 18:59:00+00 + 12:00:00-07 | 19:00:00+00 | 19:00:00+00 | 19:00:00+00 + 12:01:00-07 | 19:01:00+00 | 19:01:00+00 | 19:01:00+00 + 15:36:39-04 | 19:36:39+00 | 19:36:39+00 | 19:36:39+00 + 15:36:39-05 | 20:36:39+00 | 20:36:39+00 | 20:36:39+00 + 23:59:00-07 | 06:59:00+00 | 06:59:00+00 | 06:59:00+00 + 23:59:59.99-07 | 06:59:59.99+00 | 06:59:59.99+00 | 06:59:59.99+00 +(12 rows) + +DROP VIEW timetz_local_view; +COMMIT; diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index 69b36d04202..cdc57bc1606 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -612,6 +612,29 @@ SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK'; SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; -- +-- Test LOCAL time zone +-- +BEGIN; +SET LOCAL TIME ZONE 'Europe/Paris'; +VALUES (CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL); +VALUES (TIMESTAMP '1978-07-07 19:38' AT LOCAL); +SET LOCAL TIME ZONE 'Australia/Sydney'; +VALUES (CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL); +VALUES (TIMESTAMP '1978-07-07 19:38' AT LOCAL); +SET LOCAL TimeZone TO 'UTC'; +CREATE VIEW timestamp_local_view AS + SELECT CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE) AT LOCAL AS ttz_at_local, + timezone(CAST('1978-07-07 19:38 America/New_York' AS TIMESTAMP WITH TIME ZONE)) AS ttz_func, + TIMESTAMP '1978-07-07 19:38' AT LOCAL AS t_at_local, + timezone(TIMESTAMP '1978-07-07 19:38') AS t_func; +SELECT pg_get_viewdef('timestamp_local_view', true); +\x +TABLE timestamp_local_view; +\x +DROP VIEW timestamp_local_view; +COMMIT; + +-- -- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504) -- create temp table tmptz (f1 timestamptz primary key); diff --git a/src/test/regress/sql/timetz.sql b/src/test/regress/sql/timetz.sql index 93c7bb14288..846006640e3 100644 --- a/src/test/regress/sql/timetz.sql +++ b/src/test/regress/sql/timetz.sql @@ -84,3 +84,20 @@ SELECT date_part('microsecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401- SELECT date_part('millisecond', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); SELECT date_part('second', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); SELECT date_part('epoch', TIME WITH TIME ZONE '2020-05-26 13:30:25.575401-04'); + +-- +-- AT LOCAL with timetz +-- +BEGIN; +SET LOCAL TimeZone TO 'UTC'; +CREATE VIEW timetz_local_view AS + SELECT f1 AS dat, + timezone(f1) AS dat_func, + f1 AT LOCAL AS dat_at_local, + f1 AT TIME ZONE current_setting('TimeZone') AS dat_at_time + FROM TIMETZ_TBL + ORDER BY f1; +SELECT pg_get_viewdef('timetz_local_view', true); +TABLE timetz_local_view; +DROP VIEW timetz_local_view; +COMMIT; |