On Mon, Jan 30, 2023 at 4:07 PM Tom Lane <t...@sss.pgh.pa.us> wrote: > Gurjeet Singh <gurj...@singh.im> writes: > > [ generate_series_with_timezone.v6.patch ] > > The cfbot isn't terribly happy with this. It looks like UBSan > is detecting some undefined behavior. Possibly an uninitialized > variable?
It was the classical case of out-of-bounds access. I was trying to access 4th argument, even in the case where the 3-argument variant of generate_series() was called. Please see attached v7 of the patch. It now checks PG_NARGS() before accessing the optional parameter. This mistake would've been caught early if there were assertions preventing access beyond the number of arguments passed to the function. I'll send the assert_enough_args.patch, that adds these checks, in a separate thread to avoid potentially confusing cfbot. Best regards, Gurjeet http://Gurje.et
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e09e289a43..aa15407936 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -9231,6 +9231,22 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <function>date_add</function> ( <type>timestamp with time zone</type>, <type>interval</type> <optional>, <type>text</type> </optional> ) + <returnvalue>timestamp with time zone</returnvalue> + </para> + <para> + Add <type>interval</type> to a <type>timestamp with time zone</type> value, + at the time zone specified by the third parameter. The time zone value + defaults to current <xref linkend="guc-timezone"/> setting. + </para> + <para> + <literal>date_add('2021-10-31 00:00:00+02'::timestamptz, '1 day'::interval, 'Europe/Warsaw')</literal> + <returnvalue>2021-10-31 23:00:00</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <function>date_bin</function> ( <type>interval</type>, <type>timestamp</type>, <type>timestamp</type> ) @@ -9278,6 +9294,22 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <function>date_subtract</function> ( <type>timestamp with time zone</type>, <type>interval</type> <optional>, <type>text</type> </optional> ) + <returnvalue>timestamp with time zone</returnvalue> + </para> + <para> + Subtract <type>interval</type> from a <type>timestamp with time zone</type> value, + at the time zone specified by the third parameter. The time zone value + defaults to the current <xref linkend="guc-timezone"/> setting. + </para> + <para> + <literal>date_subtract('2021-10-31 00:00:00+02'::timestamptz, '1 day'::interval, 'Europe/Warsaw')</literal> + <returnvalue>2021-10-29 22:00:00</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> @@ -21968,13 +22000,14 @@ AND <returnvalue>setof timestamp</returnvalue> </para> <para role="func_signature"> - <function>generate_series</function> ( <parameter>start</parameter> <type>timestamp with time zone</type>, <parameter>stop</parameter> <type>timestamp with time zone</type>, <parameter>step</parameter> <type>interval</type> ) + <function>generate_series</function> ( <parameter>start</parameter> <type>timestamp with time zone</type>, <parameter>stop</parameter> <type>timestamp with time zone</type>, <parameter>step</parameter> <type>interval</type> <optional>, <parameter>timezone</parameter> <type>text</type> </optional> ) <returnvalue>setof timestamp with time zone</returnvalue> </para> <para> Generates a series of values from <parameter>start</parameter> to <parameter>stop</parameter>, with a step size - of <parameter>step</parameter>. + of <parameter>step</parameter>. <parameter>timezone</parameter> + defaults to the current <xref linkend="guc-timezone"/> setting. </para></entry> </row> </tbody> diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 47e059a409..bd85f6421e 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -69,6 +69,7 @@ typedef struct TimestampTz finish; Interval step; int step_sign; + pg_tz *attimezone; } generate_series_timestamptz_fctx; @@ -78,6 +79,8 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod, Node *escontext); static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); +static pg_tz* lookup_timezone(text *zone); +static Datum generate_series_timestamptz_internal(FunctionCallInfo fcinfo); /* common code for timestamptypmodin and timestamptztypmodin */ @@ -550,6 +553,54 @@ parse_sane_timezone(struct pg_tm *tm, text *zone) return tz; } +/* + * Look up the requested timezone (see notes in timestamptz_zone()). + */ +static pg_tz * +lookup_timezone(text *zone) +{ + char tzname[TZ_STRLEN_MAX + 1]; + char *lowzone; + int type, + dterr, + val; + pg_tz *tzp; + + DateTimeErrorExtra extra; + + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + /* DecodeTimezoneAbbrev requires lowercase input */ + lowzone = downcase_truncate_identifier(tzname, + strlen(tzname), + false); + + dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); + if (dterr) + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); + + if (type == TZ || type == DTZ) + { + /* fixed-offset abbreviation, get a pg_tz descriptor for that */ + tzp = pg_tzset_offset(-val); + } + else if (type == DYNTZ) + { + /* dynamic-offset abbreviation, use its referenced timezone */ + } + else + { + /* try it as a full zone name */ + tzp = pg_tzset(tzname); + if (!tzp) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", tzname))); + } + + return tzp; +} + /* * make_timestamp_internal * workhorse for make_timestamp and make_timestamptz @@ -3014,97 +3065,112 @@ timestamp_mi_interval(PG_FUNCTION_ARGS) } +/* + * timestamptz_pl_interval_internal() + * Add an interval to timestamptz, in the given (or session) timezone + * + * Note that interval has provisions for qualitative year/month and day + * units, so try to do the right thing with them. + * To add a month, increment the month, and use the same day of month. + * Then, if the next month has fewer days, set the day of month + * to the last day of month. + * To add a day, increment the mday, and use the same time of day. + * Lastly, add in the "quantitative time". + */ +static TimestampTz +timestamptz_pl_interval_internal(TimestampTz timestamp, + Interval *span, + pg_tz *attimezone) +{ + int tz; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + return timestamp; + + /* Use session timezone if caller asks for default */ + if (attimezone == NULL) + attimezone = session_timezone; + + if (span->month != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + tm->tm_mon += span->month; + if (tm->tm_mon > MONTHS_PER_YEAR) + { + tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; + tm->tm_mon = ((tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1; + } + else if (tm->tm_mon < 1) + { + tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1; + tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR; + } + + /* adjust for end of month boundary problems... */ + if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) + tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]); + + tz = DetermineTimeZoneOffset(tm, attimezone); + + if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (span->day != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int julian; + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* Add days by converting to and from Julian */ + julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day; + j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + + tz = DetermineTimeZoneOffset(tm, attimezone); + + if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + timestamp += span->time; + + if (!IS_VALID_TIMESTAMP(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return timestamp; +} + + /* timestamptz_pl_interval() - * Add an interval to a timestamp with time zone data type. - * Note that interval has provisions for qualitative year/month - * units, so try to do the right thing with them. - * To add a month, increment the month, and use the same day of month. - * Then, if the next month has fewer days, set the day of month - * to the last day of month. - * Lastly, add in the "quantitative time". + * Add an interval to a timestamptz, in session timezone. */ Datum timestamptz_pl_interval(PG_FUNCTION_ARGS) { TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); Interval *span = PG_GETARG_INTERVAL_P(1); - TimestampTz result; - int tz; - if (TIMESTAMP_NOT_FINITE(timestamp)) - result = timestamp; - else - { - if (span->month != 0) - { - struct pg_tm tt, - *tm = &tt; - fsec_t fsec; - - if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - tm->tm_mon += span->month; - if (tm->tm_mon > MONTHS_PER_YEAR) - { - tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; - tm->tm_mon = ((tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1; - } - else if (tm->tm_mon < 1) - { - tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1; - tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR; - } - - /* adjust for end of month boundary problems... */ - if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) - tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]); - - tz = DetermineTimeZoneOffset(tm, session_timezone); - - if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - } - - if (span->day != 0) - { - struct pg_tm tt, - *tm = &tt; - fsec_t fsec; - int julian; - - if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - /* Add days by converting to and from Julian */ - julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day; - j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); - - tz = DetermineTimeZoneOffset(tm, session_timezone); - - if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - } - - timestamp += span->time; - - if (!IS_VALID_TIMESTAMP(timestamp)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - result = timestamp; - } - - PG_RETURN_TIMESTAMP(result); + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, span, NULL)); } Datum @@ -3118,11 +3184,38 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS) tspan.day = -span->day; tspan.time = -span->time; - return DirectFunctionCall2(timestamptz_pl_interval, - TimestampGetDatum(timestamp), - PointerGetDatum(&tspan)); + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, &tspan, NULL)); } +/* timestamptz_pl_interval_at_zone() + * Add an interval to a timestamp with time zone data type in specified timezone. + */ +Datum +timestamptz_pl_interval_at_zone(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + text *zone = PG_GETARG_TEXT_PP(2); + pg_tz *attimezone = lookup_timezone(zone); + + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, span, attimezone)); +} + +Datum +timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + text *zone = PG_GETARG_TEXT_PP(2); + pg_tz *attimezone = lookup_timezone(zone); + Interval tspan; + + tspan.month = -span->month; + tspan.day = -span->day; + tspan.time = -span->time; + + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, &tspan, attimezone)); +} Datum interval_um(PG_FUNCTION_ARGS) @@ -4300,13 +4393,7 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS) TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); text *zone = PG_GETARG_TEXT_PP(2); TimestampTz result; - char tzname[TZ_STRLEN_MAX + 1]; - char *lowzone; - int dterr, - type, - val; pg_tz *tzp; - DateTimeErrorExtra extra; /* * timestamptz_zone() doesn't look up the zone for infinite inputs, so we @@ -4315,38 +4402,7 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS) if (TIMESTAMP_NOT_FINITE(timestamp)) PG_RETURN_TIMESTAMP(timestamp); - /* - * Look up the requested timezone (see notes in timestamptz_zone()). - */ - text_to_cstring_buffer(zone, tzname, sizeof(tzname)); - - /* DecodeTimezoneAbbrev requires lowercase input */ - lowzone = downcase_truncate_identifier(tzname, - strlen(tzname), - false); - - dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); - if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL, NULL); - - if (type == TZ || type == DTZ) - { - /* fixed-offset abbreviation, get a pg_tz descriptor for that */ - tzp = pg_tzset_offset(-val); - } - else if (type == DYNTZ) - { - /* dynamic-offset abbreviation, use its referenced timezone */ - } - else - { - /* try it as a full zone name */ - tzp = pg_tzset(tzname); - if (!tzp) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone \"%s\" not recognized", tzname))); - } + tzp = lookup_timezone(zone); result = timestamptz_trunc_internal(units, timestamp, tzp); @@ -5675,6 +5731,9 @@ timestamptz2timestamp(TimestampTz timestamp) /* timestamptz_zone() * Evaluate timestamp with time zone type at the specified time zone. * Returns a timestamp without time zone. + * + * Note: If you change anything here, also review the code in + * lookup_timezone(). */ Datum timestamptz_zone(PG_FUNCTION_ARGS) @@ -5881,6 +5940,22 @@ generate_series_timestamp(PG_FUNCTION_ARGS) */ Datum generate_series_timestamptz(PG_FUNCTION_ARGS) +{ + return generate_series_timestamptz_internal(fcinfo); +} + +/* generate_series_timestamptz_at_zone() + * Generate the set of timestamps from start to finish by step, in the + * specified timezone. + */ +Datum +generate_series_timestamptz_at_zone(PG_FUNCTION_ARGS) +{ + return generate_series_timestamptz_internal(fcinfo); +} + +static Datum +generate_series_timestamptz_internal(FunctionCallInfo fcinfo) { FuncCallContext *funcctx; generate_series_timestamptz_fctx *fctx; @@ -5892,8 +5967,11 @@ generate_series_timestamptz(PG_FUNCTION_ARGS) TimestampTz start = PG_GETARG_TIMESTAMPTZ(0); TimestampTz finish = PG_GETARG_TIMESTAMPTZ(1); Interval *step = PG_GETARG_INTERVAL_P(2); - MemoryContext oldcontext; - const Interval interval_zero = {0}; + text *zone = (PG_NARGS() == 4 && !PG_ARGISNULL(3)) + ? PG_GETARG_TEXT_PP(3) : NULL; + + MemoryContext oldcontext; + const Interval interval_zero = {0}; /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); @@ -5914,6 +5992,7 @@ generate_series_timestamptz(PG_FUNCTION_ARGS) fctx->current = start; fctx->finish = finish; fctx->step = *step; + fctx->attimezone = zone ? lookup_timezone(zone) : NULL; /* Determine sign of the interval */ fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero); @@ -5941,9 +6020,7 @@ generate_series_timestamptz(PG_FUNCTION_ARGS) timestamp_cmp_internal(result, fctx->finish) >= 0) { /* increment current in preparation for next iteration */ - fctx->current = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval, - TimestampTzGetDatum(fctx->current), - PointerGetDatum(&fctx->step))); + fctx->current = timestamptz_pl_interval_internal(fctx->current, &fctx->step, fctx->attimezone); /* do when there is more left to send */ SRF_RETURN_NEXT(funcctx, TimestampTzGetDatum(result)); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index c0f2a8a77c..79300f1317 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -2426,6 +2426,26 @@ proname => 'timestamptz_pl_interval', provolatile => 's', prorettype => 'timestamptz', proargtypes => 'timestamptz interval', prosrc => 'timestamptz_pl_interval' }, +{ oid => '8800', + descr => 'add interval to timestamp with time zone', + proname => 'date_add', provolatile => 's', + prorettype => 'timestamptz', proargtypes => 'timestamptz interval', + prosrc => 'timestamptz_pl_interval' }, +{ oid => '8801', + descr => 'add interval to timestamp with time zone in specified time zone', + proname => 'date_add', + prorettype => 'timestamptz', proargtypes => 'timestamptz interval text', + prosrc => 'timestamptz_pl_interval_at_zone' }, +{ oid => '8802', + descr => 'subtract interval from timestamp with time zone', + proname => 'date_subtract', provolatile => 's', + prorettype => 'timestamptz', proargtypes => 'timestamptz interval', + prosrc => 'timestamptz_mi_interval' }, +{ oid => '8803', + descr => 'subtract interval from timestamp with time zone in specified time zone', + proname => 'date_subtract', + prorettype => 'timestamptz', proargtypes => 'timestamptz interval text', + prosrc => 'timestamptz_mi_interval_at_zone' }, { oid => '1190', proname => 'timestamptz_mi_interval', provolatile => 's', prorettype => 'timestamptz', proargtypes => 'timestamptz interval', @@ -8232,6 +8252,11 @@ provolatile => 's', prorettype => 'timestamptz', proargtypes => 'timestamptz timestamptz interval', prosrc => 'generate_series_timestamptz' }, +{ oid => '8804', descr => 'non-persistent series generator', + proname => 'generate_series', prorows => '1000', proretset => 't', + prorettype => 'timestamptz', + proargtypes => 'timestamptz timestamptz interval text', + prosrc => 'generate_series_timestamptz_at_zone' }, # boolean aggregates { oid => '2515', descr => 'aggregate transition function', diff --git a/src/include/fmgr.h b/src/include/fmgr.h index b120f5e7fe..a445ac56b9 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -206,7 +206,7 @@ extern void fmgr_symbol(Oid functionId, char **mod, char **fn); * If function is not marked "proisstrict" in pg_proc, it must check for * null arguments using this macro. Do not try to GETARG a null argument! */ -#define PG_ARGISNULL(n) (fcinfo->args[n].isnull) +#define PG_ARGISNULL(n) (AssertMacro(n < PG_NARGS()), fcinfo->args[n].isnull) /* * Support for fetching detoasted copies of toastable datatypes (all of @@ -265,7 +265,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum); /* Macros for fetching arguments of standard types */ -#define PG_GETARG_DATUM(n) (fcinfo->args[n].value) +#define PG_GETARG_DATUM(n) (AssertMacro(n < PG_NARGS()), fcinfo->args[n].value) #define PG_GETARG_INT32(n) DatumGetInt32(PG_GETARG_DATUM(n)) #define PG_GETARG_UINT32(n) DatumGetUInt32(PG_GETARG_DATUM(n)) #define PG_GETARG_INT16(n) DatumGetInt16(PG_GETARG_DATUM(n)) diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 00379fd0fd..3d2d479723 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -2459,6 +2459,60 @@ select * from generate_series('2020-01-01 00:00'::timestamptz, '2020-01-02 03:00'::timestamptz, '0 hour'::interval); ERROR: step size cannot equal zero +-- Interval crossing time shift for Europe/Warsaw timezone (with DST) +SET TimeZone to 'UTC'; +SELECT date_add('2022-10-30 00:00:00+01'::timestamptz, + '1 day'::interval); + date_add +------------------------------ + Sun Oct 30 23:00:00 2022 UTC +(1 row) + +SELECT date_add('2021-10-31 00:00:00+02'::timestamptz, + '1 day'::interval, + 'Europe/Warsaw'); + date_add +------------------------------ + Sun Oct 31 23:00:00 2021 UTC +(1 row) + +SELECT date_subtract('2022-10-30 00:00:00+01'::timestamptz, + '1 day'::interval); + date_subtract +------------------------------ + Fri Oct 28 23:00:00 2022 UTC +(1 row) + +SELECT date_subtract('2021-10-31 00:00:00+02'::timestamptz, + '1 day'::interval, + 'Europe/Warsaw'); + date_subtract +------------------------------ + Fri Oct 29 22:00:00 2021 UTC +(1 row) + +SELECT * FROM generate_series('2020-12-31 23:00:00+00'::timestamptz, + '2021-12-31 23:00:00+00'::timestamptz, + '1 month'::interval, + 'Europe/Warsaw'); + generate_series +------------------------------ + Thu Dec 31 23:00:00 2020 UTC + Sun Jan 31 23:00:00 2021 UTC + Sun Feb 28 23:00:00 2021 UTC + Wed Mar 31 22:00:00 2021 UTC + Fri Apr 30 22:00:00 2021 UTC + Mon May 31 22:00:00 2021 UTC + Wed Jun 30 22:00:00 2021 UTC + Sat Jul 31 22:00:00 2021 UTC + Tue Aug 31 22:00:00 2021 UTC + Thu Sep 30 22:00:00 2021 UTC + Sun Oct 31 23:00:00 2021 UTC + Tue Nov 30 23:00:00 2021 UTC + Fri Dec 31 23:00:00 2021 UTC +(13 rows) + +RESET TimeZone; -- -- Test behavior with a dynamic (time-varying) timezone abbreviation. -- These tests rely on the knowledge that MSK (Europe/Moscow standard time) diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index 4905dd0831..1a98aa64c3 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -455,6 +455,25 @@ select * from generate_series('2020-01-01 00:00'::timestamptz, '2020-01-02 03:00'::timestamptz, '0 hour'::interval); +-- Interval crossing time shift for Europe/Warsaw timezone (with DST) +SET TimeZone to 'UTC'; + +SELECT date_add('2022-10-30 00:00:00+01'::timestamptz, + '1 day'::interval); +SELECT date_add('2021-10-31 00:00:00+02'::timestamptz, + '1 day'::interval, + 'Europe/Warsaw'); +SELECT date_subtract('2022-10-30 00:00:00+01'::timestamptz, + '1 day'::interval); +SELECT date_subtract('2021-10-31 00:00:00+02'::timestamptz, + '1 day'::interval, + 'Europe/Warsaw'); +SELECT * FROM generate_series('2020-12-31 23:00:00+00'::timestamptz, + '2021-12-31 23:00:00+00'::timestamptz, + '1 month'::interval, + 'Europe/Warsaw'); +RESET TimeZone; + -- -- Test behavior with a dynamic (time-varying) timezone abbreviation. -- These tests rely on the knowledge that MSK (Europe/Moscow standard time)