Skip to content

Commit 3e59e50

Browse files
committed
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 cc1392d commit 3e59e50

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)