Skip to content

Commit 6afc718

Browse files
tglsfdcpull[bot]
authored andcommitted
Refactor datetime functions' timezone lookup code to reduce duplication.
We already had five copies of essentially the same logic, and an upcoming patch introduces yet another use-case. That's past my threshold of pain, so introduce a common subroutine. There's not that much net code savings, but the chance of typos should go down. Inspired by a patch from Przemysław Sztoch, but different in detail. Discussion: https://postgr.es/m/01a84551-48dd-1359-bf7e-f6b0203a6bd0@sztoch.pl
1 parent c73142a commit 6afc718

File tree

4 files changed

+142
-180
lines changed

4 files changed

+142
-180
lines changed

src/backend/utils/adt/date.c

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,38 +3052,23 @@ timetz_zone(PG_FUNCTION_ARGS)
30523052
TimeTzADT *result;
30533053
int tz;
30543054
char tzname[TZ_STRLEN_MAX + 1];
3055-
char *lowzone;
3056-
int dterr,
3057-
type,
3055+
int type,
30583056
val;
30593057
pg_tz *tzp;
3060-
DateTimeErrorExtra extra;
30613058

30623059
/*
3063-
* Look up the requested timezone. First we look in the timezone
3064-
* abbreviation table (to handle cases like "EST"), and if that fails, we
3065-
* look in the timezone database (to handle cases like
3066-
* "America/New_York"). (This matches the order in which timestamp input
3067-
* checks the cases; it's important because the timezone database unwisely
3068-
* uses a few zone names that are identical to offset abbreviations.)
3060+
* Look up the requested timezone.
30693061
*/
30703062
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
30713063

3072-
/* DecodeTimezoneAbbrev requires lowercase input */
3073-
lowzone = downcase_truncate_identifier(tzname,
3074-
strlen(tzname),
3075-
false);
3064+
type = DecodeTimezoneName(tzname, &val, &tzp);
30763065

3077-
dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
3078-
if (dterr)
3079-
DateTimeParseError(dterr, &extra, NULL, NULL, NULL);
3080-
3081-
if (type == TZ || type == DTZ)
3066+
if (type == TZNAME_FIXED_OFFSET)
30823067
{
30833068
/* fixed-offset abbreviation */
30843069
tz = -val;
30853070
}
3086-
else if (type == DYNTZ)
3071+
else if (type == TZNAME_DYNTZ)
30873072
{
30883073
/* dynamic-offset abbreviation, resolve using transaction start time */
30893074
TimestampTz now = GetCurrentTransactionStartTimestamp();
@@ -3093,27 +3078,15 @@ timetz_zone(PG_FUNCTION_ARGS)
30933078
}
30943079
else
30953080
{
3096-
/* try it as a full zone name */
3097-
tzp = pg_tzset(tzname);
3098-
if (tzp)
3099-
{
3100-
/* Get the offset-from-GMT that is valid now for the zone */
3101-
TimestampTz now = GetCurrentTransactionStartTimestamp();
3102-
struct pg_tm tm;
3103-
fsec_t fsec;
3081+
/* Get the offset-from-GMT that is valid now for the zone name */
3082+
TimestampTz now = GetCurrentTransactionStartTimestamp();
3083+
struct pg_tm tm;
3084+
fsec_t fsec;
31043085

3105-
if (timestamp2tm(now, &tz, &tm, &fsec, NULL, tzp) != 0)
3106-
ereport(ERROR,
3107-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3108-
errmsg("timestamp out of range")));
3109-
}
3110-
else
3111-
{
3086+
if (timestamp2tm(now, &tz, &tm, &fsec, NULL, tzp) != 0)
31123087
ereport(ERROR,
3113-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3114-
errmsg("time zone \"%s\" not recognized", tzname)));
3115-
tz = 0; /* keep compiler quiet */
3116-
}
3088+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3089+
errmsg("timestamp out of range")));
31173090
}
31183091

31193092
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));

src/backend/utils/adt/datetime.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "funcapi.h"
2727
#include "miscadmin.h"
2828
#include "nodes/nodeFuncs.h"
29+
#include "parser/scansup.h"
2930
#include "utils/builtins.h"
3031
#include "utils/date.h"
3132
#include "utils/datetime.h"
@@ -3162,6 +3163,90 @@ DecodeSpecial(int field, const char *lowtoken, int *val)
31623163
}
31633164

31643165

3166+
/* DecodeTimezoneName()
3167+
* Interpret string as a timezone abbreviation or name.
3168+
* Throw error if the name is not recognized.
3169+
*
3170+
* The return value indicates what kind of zone identifier it is:
3171+
* TZNAME_FIXED_OFFSET: fixed offset from UTC
3172+
* TZNAME_DYNTZ: dynamic timezone abbreviation
3173+
* TZNAME_ZONE: full tzdb zone name
3174+
*
3175+
* For TZNAME_FIXED_OFFSET, *offset receives the UTC offset (in seconds,
3176+
* with ISO sign convention: positive is east of Greenwich).
3177+
* For the other two cases, *tz receives the timezone struct representing
3178+
* the zone name or the abbreviation's underlying zone.
3179+
*/
3180+
int
3181+
DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz)
3182+
{
3183+
char *lowzone;
3184+
int dterr,
3185+
type;
3186+
DateTimeErrorExtra extra;
3187+
3188+
/*
3189+
* First we look in the timezone abbreviation table (to handle cases like
3190+
* "EST"), and if that fails, we look in the timezone database (to handle
3191+
* cases like "America/New_York"). This matches the order in which
3192+
* timestamp input checks the cases; it's important because the timezone
3193+
* database unwisely uses a few zone names that are identical to offset
3194+
* abbreviations.
3195+
*/
3196+
3197+
/* DecodeTimezoneAbbrev requires lowercase input */
3198+
lowzone = downcase_truncate_identifier(tzname,
3199+
strlen(tzname),
3200+
false);
3201+
3202+
dterr = DecodeTimezoneAbbrev(0, lowzone, &type, offset, tz, &extra);
3203+
if (dterr)
3204+
DateTimeParseError(dterr, &extra, NULL, NULL, NULL);
3205+
3206+
if (type == TZ || type == DTZ)
3207+
{
3208+
/* fixed-offset abbreviation, return the offset */
3209+
return TZNAME_FIXED_OFFSET;
3210+
}
3211+
else if (type == DYNTZ)
3212+
{
3213+
/* dynamic-offset abbreviation, return its referenced timezone */
3214+
return TZNAME_DYNTZ;
3215+
}
3216+
else
3217+
{
3218+
/* try it as a full zone name */
3219+
*tz = pg_tzset(tzname);
3220+
if (*tz == NULL)
3221+
ereport(ERROR,
3222+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3223+
errmsg("time zone \"%s\" not recognized", tzname)));
3224+
return TZNAME_ZONE;
3225+
}
3226+
}
3227+
3228+
/* DecodeTimezoneNameToTz()
3229+
* Interpret string as a timezone abbreviation or name.
3230+
* Throw error if the name is not recognized.
3231+
*
3232+
* This is a simple wrapper for DecodeTimezoneName that produces a pg_tz *
3233+
* result in all cases.
3234+
*/
3235+
pg_tz *
3236+
DecodeTimezoneNameToTz(const char *tzname)
3237+
{
3238+
pg_tz *result;
3239+
int offset;
3240+
3241+
if (DecodeTimezoneName(tzname, &offset, &result) == TZNAME_FIXED_OFFSET)
3242+
{
3243+
/* fixed-offset abbreviation, get a pg_tz descriptor for that */
3244+
result = pg_tzset_offset(-offset); /* flip to POSIX sign convention */
3245+
}
3246+
return result;
3247+
}
3248+
3249+
31653250
/* ClearPgItmIn
31663251
*
31673252
* Zero out a pg_itm_in

0 commit comments

Comments
 (0)