summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2023-03-17 21:47:15 +0000
committerTom Lane2023-03-17 21:47:19 +0000
commit3e59e5048d0f20debe4ad79b2e02ca2a76c5daed (patch)
tree2180e041471bef69c7f7b515ce65cd01be31c48d
parentcc1392d4aa5206d6b4dcc1b036f7a001bcac4197 (diff)
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/[email protected]
-rw-r--r--src/backend/utils/adt/date.c51
-rw-r--r--src/backend/utils/adt/datetime.c85
-rw-r--r--src/backend/utils/adt/timestamp.c178
-rw-r--r--src/include/utils/datetime.h8
4 files changed, 142 insertions, 180 deletions
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..a163fbb4ab 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -3052,38 +3052,23 @@ timetz_zone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
char tzname[TZ_STRLEN_MAX + 1];
- char *lowzone;
- int dterr,
- type,
+ int type,
val;
pg_tz *tzp;
- DateTimeErrorExtra extra;
/*
- * Look up the requested timezone. First we look in the timezone
- * abbreviation table (to handle cases like "EST"), and if that fails, we
- * look in the timezone database (to handle cases like
- * "America/New_York"). (This matches the order in which timestamp input
- * checks the cases; it's important because the timezone database unwisely
- * uses a few zone names that are identical to offset abbreviations.)
+ * Look up the requested timezone.
*/
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
- /* DecodeTimezoneAbbrev requires lowercase input */
- lowzone = downcase_truncate_identifier(tzname,
- strlen(tzname),
- false);
+ type = DecodeTimezoneName(tzname, &val, &tzp);
- dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
- if (dterr)
- DateTimeParseError(dterr, &extra, NULL, NULL, NULL);
-
- if (type == TZ || type == DTZ)
+ if (type == TZNAME_FIXED_OFFSET)
{
/* fixed-offset abbreviation */
tz = -val;
}
- else if (type == DYNTZ)
+ else if (type == TZNAME_DYNTZ)
{
/* dynamic-offset abbreviation, resolve using transaction start time */
TimestampTz now = GetCurrentTransactionStartTimestamp();
@@ -3093,27 +3078,15 @@ timetz_zone(PG_FUNCTION_ARGS)
}
else
{
- /* try it as a full zone name */
- tzp = pg_tzset(tzname);
- if (tzp)
- {
- /* Get the offset-from-GMT that is valid now for the zone */
- TimestampTz now = GetCurrentTransactionStartTimestamp();
- struct pg_tm tm;
- fsec_t fsec;
+ /* Get the offset-from-GMT that is valid now for the zone name */
+ TimestampTz now = GetCurrentTransactionStartTimestamp();
+ struct pg_tm tm;
+ fsec_t fsec;
- if (timestamp2tm(now, &tz, &tm, &fsec, NULL, tzp) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- }
- else
- {
+ if (timestamp2tm(now, &tz, &tm, &fsec, NULL, tzp) != 0)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
- tz = 0; /* keep compiler quiet */
- }
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
}
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 516ee9c154..be2e55bb29 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -26,6 +26,7 @@
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/scansup.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
@@ -3162,6 +3163,90 @@ DecodeSpecial(int field, const char *lowtoken, int *val)
}
+/* DecodeTimezoneName()
+ * Interpret string as a timezone abbreviation or name.
+ * Throw error if the name is not recognized.
+ *
+ * The return value indicates what kind of zone identifier it is:
+ * TZNAME_FIXED_OFFSET: fixed offset from UTC
+ * TZNAME_DYNTZ: dynamic timezone abbreviation
+ * TZNAME_ZONE: full tzdb zone name
+ *
+ * For TZNAME_FIXED_OFFSET, *offset receives the UTC offset (in seconds,
+ * with ISO sign convention: positive is east of Greenwich).
+ * For the other two cases, *tz receives the timezone struct representing
+ * the zone name or the abbreviation's underlying zone.
+ */
+int
+DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz)
+{
+ char *lowzone;
+ int dterr,
+ type;
+ DateTimeErrorExtra extra;
+
+ /*
+ * First we look in the timezone abbreviation table (to handle cases like
+ * "EST"), and if that fails, we look in the timezone database (to handle
+ * cases like "America/New_York"). This matches the order in which
+ * timestamp input checks the cases; it's important because the timezone
+ * database unwisely uses a few zone names that are identical to offset
+ * abbreviations.
+ */
+
+ /* DecodeTimezoneAbbrev requires lowercase input */
+ lowzone = downcase_truncate_identifier(tzname,
+ strlen(tzname),
+ false);
+
+ dterr = DecodeTimezoneAbbrev(0, lowzone, &type, offset, tz, &extra);
+ if (dterr)
+ DateTimeParseError(dterr, &extra, NULL, NULL, NULL);
+
+ if (type == TZ || type == DTZ)
+ {
+ /* fixed-offset abbreviation, return the offset */
+ return TZNAME_FIXED_OFFSET;
+ }
+ else if (type == DYNTZ)
+ {
+ /* dynamic-offset abbreviation, return its referenced timezone */
+ return TZNAME_DYNTZ;
+ }
+ else
+ {
+ /* try it as a full zone name */
+ *tz = pg_tzset(tzname);
+ if (*tz == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized", tzname)));
+ return TZNAME_ZONE;
+ }
+}
+
+/* DecodeTimezoneNameToTz()
+ * Interpret string as a timezone abbreviation or name.
+ * Throw error if the name is not recognized.
+ *
+ * This is a simple wrapper for DecodeTimezoneName that produces a pg_tz *
+ * result in all cases.
+ */
+pg_tz *
+DecodeTimezoneNameToTz(const char *tzname)
+{
+ pg_tz *result;
+ int offset;
+
+ if (DecodeTimezoneName(tzname, &offset, &result) == TZNAME_FIXED_OFFSET)
+ {
+ /* fixed-offset abbreviation, get a pg_tz descriptor for that */
+ result = pg_tzset_offset(-offset); /* flip to POSIX sign convention */
+ }
+ return result;
+}
+
+
/* ClearPgItmIn
*
* Zero out a pg_itm_in
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index de93db89d4..c266d0d02e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -479,12 +479,7 @@ parse_sane_timezone(struct pg_tm *tm, text *zone)
/*
* Look up the requested timezone. First we try to interpret it as a
* numeric timezone specification; if DecodeTimezone decides it doesn't
- * like the format, we look in the timezone abbreviation table (to handle
- * cases like "EST"), and if that also fails, we look in the timezone
- * database (to handle cases like "America/New_York"). (This matches the
- * order in which timestamp input checks the cases; it's important because
- * the timezone database unwisely uses a few zone names that are identical
- * to offset abbreviations.)
+ * like the format, we try timezone abbreviations and names.
*
* Note pg_tzset happily parses numeric input that DecodeTimezone would
* reject. To avoid having it accept input that would otherwise be seen
@@ -501,11 +496,9 @@ parse_sane_timezone(struct pg_tm *tm, text *zone)
dterr = DecodeTimezone(tzname, &tz);
if (dterr != 0)
{
- char *lowzone;
int type,
val;
pg_tz *tzp;
- DateTimeErrorExtra extra;
if (dterr == DTERR_TZDISP_OVERFLOW)
ereport(ERROR,
@@ -516,34 +509,22 @@ parse_sane_timezone(struct pg_tm *tm, text *zone)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized", 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);
+ type = DecodeTimezoneName(tzname, &val, &tzp);
- if (type == TZ || type == DTZ)
+ if (type == TZNAME_FIXED_OFFSET)
{
/* fixed-offset abbreviation */
tz = -val;
}
- else if (type == DYNTZ)
+ else if (type == TZNAME_DYNTZ)
{
/* dynamic-offset abbreviation, resolve using specified time */
tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp);
}
else
{
- /* try it as a full zone name */
- tzp = pg_tzset(tzname);
- if (tzp)
- tz = DetermineTimeZoneOffset(tm, tzp);
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
+ /* full zone name */
+ tz = DetermineTimeZoneOffset(tm, tzp);
}
}
@@ -4304,12 +4285,7 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS)
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
@@ -4319,37 +4295,11 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamp);
/*
- * Look up the requested timezone (see notes in timestamptz_zone()).
+ * Look up the requested timezone.
*/
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 = DecodeTimezoneNameToTz(tzname);
result = timestamptz_trunc_internal(units, timestamp, tzp);
@@ -5429,12 +5379,9 @@ timestamp_zone(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
char tzname[TZ_STRLEN_MAX + 1];
- char *lowzone;
- int dterr,
- type,
+ int type,
val;
pg_tz *tzp;
- DateTimeErrorExtra extra;
struct pg_tm tm;
fsec_t fsec;
@@ -5442,31 +5389,19 @@ timestamp_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(timestamp);
/*
- * Look up the requested timezone. First we look in the timezone
- * abbreviation table (to handle cases like "EST"), and if that fails, we
- * look in the timezone database (to handle cases like
- * "America/New_York"). (This matches the order in which timestamp input
- * checks the cases; it's important because the timezone database unwisely
- * uses a few zone names that are identical to offset abbreviations.)
+ * Look up the requested timezone.
*/
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
- /* DecodeTimezoneAbbrev requires lowercase input */
- lowzone = downcase_truncate_identifier(tzname,
- strlen(tzname),
- false);
+ type = DecodeTimezoneName(tzname, &val, &tzp);
- dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
- if (dterr)
- DateTimeParseError(dterr, &extra, NULL, NULL, NULL);
-
- if (type == TZ || type == DTZ)
+ if (type == TZNAME_FIXED_OFFSET)
{
/* fixed-offset abbreviation */
tz = val;
result = dt2local(timestamp, tz);
}
- else if (type == DYNTZ)
+ else if (type == TZNAME_DYNTZ)
{
/* dynamic-offset abbreviation, resolve using specified time */
if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
@@ -5478,28 +5413,16 @@ timestamp_zone(PG_FUNCTION_ARGS)
}
else
{
- /* try it as a full zone name */
- tzp = pg_tzset(tzname);
- if (tzp)
- {
- /* Apply the timezone change */
- if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- tz = DetermineTimeZoneOffset(&tm, tzp);
- if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- }
- else
- {
+ /* full zone name, rotate to that zone */
+ if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
- result = 0; /* keep compiler quiet */
- }
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ tz = DetermineTimeZoneOffset(&tm, tzp);
+ if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
}
if (!IS_VALID_TIMESTAMP(result))
@@ -5687,42 +5610,27 @@ timestamptz_zone(PG_FUNCTION_ARGS)
Timestamp result;
int tz;
char tzname[TZ_STRLEN_MAX + 1];
- char *lowzone;
- int dterr,
- type,
+ int type,
val;
pg_tz *tzp;
- DateTimeErrorExtra extra;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
/*
- * Look up the requested timezone. First we look in the timezone
- * abbreviation table (to handle cases like "EST"), and if that fails, we
- * look in the timezone database (to handle cases like
- * "America/New_York"). (This matches the order in which timestamp input
- * checks the cases; it's important because the timezone database unwisely
- * uses a few zone names that are identical to offset abbreviations.)
+ * Look up the requested timezone.
*/
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);
+ type = DecodeTimezoneName(tzname, &val, &tzp);
- if (type == TZ || type == DTZ)
+ if (type == TZNAME_FIXED_OFFSET)
{
/* fixed-offset abbreviation */
tz = -val;
result = dt2local(timestamp, tz);
}
- else if (type == DYNTZ)
+ else if (type == TZNAME_DYNTZ)
{
/* dynamic-offset abbreviation, resolve using specified time */
int isdst;
@@ -5732,30 +5640,18 @@ timestamptz_zone(PG_FUNCTION_ARGS)
}
else
{
- /* try it as a full zone name */
- tzp = pg_tzset(tzname);
- if (tzp)
- {
- /* Apply the timezone change */
- struct pg_tm tm;
- fsec_t fsec;
+ /* full zone name, rotate from that zone */
+ struct pg_tm tm;
+ fsec_t fsec;
- if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- }
- else
- {
+ if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
- result = 0; /* keep compiler quiet */
- }
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
}
if (!IS_VALID_TIMESTAMP(result))
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index c8f78a9f1e..a871e3223d 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -295,6 +295,11 @@ typedef struct DateTimeErrorExtra
const char *dtee_abbrev; /* relevant time zone abbreviation */
} DateTimeErrorExtra;
+/* Result codes for DecodeTimezoneName() */
+#define TZNAME_FIXED_OFFSET 0
+#define TZNAME_DYNTZ 1
+#define TZNAME_ZONE 2
+
extern void GetCurrentDateTime(struct pg_tm *tm);
extern void GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp);
@@ -340,6 +345,9 @@ extern int DecodeTimezoneAbbrev(int field, const char *lowtoken,
extern int DecodeSpecial(int field, const char *lowtoken, int *val);
extern int DecodeUnits(int field, const char *lowtoken, int *val);
+extern int DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz);
+extern pg_tz *DecodeTimezoneNameToTz(const char *tzname);
+
extern int j2day(int date);
extern struct Node *TemporalSimplify(int32 max_precis, struct Node *node);