1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-13 07:41:39 +03:00

Add a timezone-specific variant of date_trunc().

date_trunc(field, timestamptz, zone_name) performs truncation using
the named time zone as reference, rather than working in the session
time zone as is the default behavior.  It's equivalent to

date_trunc(field, timestamptz at time zone zone_name) at time zone zone_name

but it's faster, easier to type, and arguably easier to understand.

Vik Fearing and Tom Lane

Discussion: https://postgr.es/m/6249ffc4-2b22-4c1b-4e7d-7af84fedd7c6@2ndquadrant.com
This commit is contained in:
Tom Lane
2018-11-14 15:41:07 -05:00
parent 06c723447b
commit 600b04d6b5
6 changed files with 166 additions and 27 deletions

View File

@ -3925,14 +3925,15 @@ timestamp_trunc(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
/* timestamptz_trunc()
* Truncate timestamp to specified units.
/*
* Common code for timestamptz_trunc() and timestamptz_trunc_zone().
*
* tzp identifies the zone to truncate with respect to. We assume
* infinite timestamps have already been rejected.
*/
Datum
timestamptz_trunc(PG_FUNCTION_ARGS)
static TimestampTz
timestamptz_trunc_internal(text *units, TimestampTz timestamp, pg_tz *tzp)
{
text *units = PG_GETARG_TEXT_PP(0);
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1);
TimestampTz result;
int tz;
int type,
@ -3943,9 +3944,6 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
struct pg_tm tt,
*tm = &tt;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@ -3954,7 +3952,7 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
if (type == UNITS)
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, tzp) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@ -4055,7 +4053,7 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
}
if (redotz)
tz = DetermineTimeZoneOffset(tm, session_timezone);
tz = DetermineTimeZoneOffset(tm, tzp);
if (tm2timestamp(tm, fsec, &tz, &result) != 0)
ereport(ERROR,
@ -4071,6 +4069,83 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
result = 0;
}
return result;
}
/* timestamptz_trunc()
* Truncate timestamptz to specified units in session timezone.
*/
Datum
timestamptz_trunc(PG_FUNCTION_ARGS)
{
text *units = PG_GETARG_TEXT_PP(0);
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1);
TimestampTz result;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
result = timestamptz_trunc_internal(units, timestamp, session_timezone);
PG_RETURN_TIMESTAMPTZ(result);
}
/* timestamptz_trunc_zone()
* Truncate timestamptz to specified units in specified timezone.
*/
Datum
timestamptz_trunc_zone(PG_FUNCTION_ARGS)
{
text *units = PG_GETARG_TEXT_PP(0);
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1);
text *zone = PG_GETARG_TEXT_PP(2);
TimestampTz result;
char tzname[TZ_STRLEN_MAX + 1];
char *lowzone;
int type,
val;
pg_tz *tzp;
/*
* timestamptz_zone() doesn't look up the zone for infinite inputs, so we
* don't do so here either.
*/
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);
type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
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)));
}
result = timestamptz_trunc_internal(units, timestamp, tzp);
PG_RETURN_TIMESTAMPTZ(result);
}