summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2005-09-09 02:31:50 +0000
committerTom Lane2005-09-09 02:31:50 +0000
commit3193ddaccfec36a5777d33a4661164a79f9eaae5 (patch)
tree637e6c978c8677792f60a315c1cd97c726cf6a0c
parent3210839b0640986286dff36931686234da7632c3 (diff)
Fix the various forms of AT TIME ZONE to accept either timezones found
in the zic database or zone names found in the date token table. This preserves the old ability to do AT TIME ZONE 'PST' along with the new ability to do AT TIME ZONE 'PST8PDT'. Per gripe from Bricklen Anderson. Also, fix some inconsistencies in usage of TZ_STRLEN_MAX --- the old code had the potential for one-byte buffer overruns, though given alignment considerations it's unlikely there was any real risk.
-rw-r--r--doc/src/sgml/datetime.sgml6
-rw-r--r--doc/src/sgml/func.sgml6
-rw-r--r--src/backend/utils/adt/date.c58
-rw-r--r--src/backend/utils/adt/timestamp.c140
-rw-r--r--src/include/pgtime.h2
-rw-r--r--src/timezone/pgtz.c4
6 files changed, 134 insertions, 82 deletions
diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml
index 8866137441..aeeb129833 100644
--- a/doc/src/sgml/datetime.sgml
+++ b/doc/src/sgml/datetime.sgml
@@ -990,9 +990,7 @@ $PostgreSQL$
<para>
<xref linkend="datetime-timezone-set-table"> shows the time zone
names recognized by <productname>PostgreSQL</productname> as valid
- settings for the <xref linkend="guc-timezone"> parameter, and as
- parameters to the <literal>AT TIME ZONE function</> (see
- <xref linkend="functions-datetime-zoneconvert">). Note that
+ settings for the <xref linkend="guc-timezone"> parameter. Note that
these names are conceptually as well as practically different from
the names shown in <xref linkend="datetime-timezone-input-table">:
most of these names imply a local daylight-savings time rule, whereas
@@ -1006,7 +1004,7 @@ $PostgreSQL$
</para>
<table id="datetime-timezone-set-table">
- <title>Time Zone Names for Setting <varname>timezone</> and <literal>AT TIME ZONE</></title>
+ <title>Time Zone Names for Setting <varname>timezone</></title>
<tgroup cols="1">
<thead>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b0ecbf6ac8..befe4c473b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5730,9 +5730,9 @@ SELECT date_trunc('year', TIMESTAMP '2001-02-16 20:38:40');
In these expressions, the desired time zone <replaceable>zone</> can be
specified either as a text string (e.g., <literal>'PST'</literal>)
or as an interval (e.g., <literal>INTERVAL '-08:00'</literal>).
- In the text case, the available zone names are those shown in
- <xref linkend="datetime-timezone-set-table">. The time zone can
- also be implied using the default time zone for that session.
+ In the text case, the available zone names are those shown in either
+ <xref linkend="datetime-timezone-set-table"> or
+ <xref linkend="datetime-timezone-input-table">.
</para>
<para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1c1d84f017..aa0133492a 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2484,37 +2484,53 @@ timetz_zone(PG_FUNCTION_ARGS)
TimeTzADT *t = PG_GETARG_TIMETZADT_P(1);
TimeTzADT *result;
int tz;
- char tzname[TZ_STRLEN_MAX];
+ char tzname[TZ_STRLEN_MAX + 1];
int len;
pg_tz *tzp;
- struct pg_tm *tm;
- pg_time_t now;
- /* Find the specified timezone */
- len = (VARSIZE(zone) - VARHDRSZ > TZ_STRLEN_MAX) ?
- TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ;
+ /*
+ * Look up the requested timezone. First we look in the timezone
+ * database (to handle cases like "America/New_York"), and if that
+ * fails, we look in the date token table (to handle cases like "EST").
+ */
+ len = Min(VARSIZE(zone) - VARHDRSZ, TZ_STRLEN_MAX);
memcpy(tzname, VARDATA(zone), len);
- tzname[len]=0;
+ tzname[len] = '\0';
tzp = pg_tzset(tzname);
- if (!tzp) {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
- PG_RETURN_NULL();
+ if (tzp)
+ {
+ /* Get the offset-from-GMT that is valid today for the selected zone */
+ pg_time_t now;
+ struct pg_tm *tm;
+
+ now = time(NULL);
+ tm = pg_localtime(&now, tzp);
+ tz = -tm->tm_gmtoff;
}
+ else
+ {
+ char *lowzone;
+ int type,
+ val;
- /* Get the offset-from-GMT that is valid today for the selected zone */
- if ((now = time(NULL)) < 0 ||
- (tm = pg_localtime(&now, tzp)) == NULL) {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine current time")));
- PG_RETURN_NULL();
+ lowzone = downcase_truncate_identifier(VARDATA(zone),
+ VARSIZE(zone) - VARHDRSZ,
+ false);
+ type = DecodeSpecial(0, lowzone, &val);
+
+ if (type == TZ || type == DTZ)
+ tz = val * 60;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized", tzname)));
+ tz = 0; /* keep compiler quiet */
+ }
}
- result = (TimeTzADT *)palloc(sizeof(TimeTzADT));
+ result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
- tz = -tm->tm_gmtoff;
#ifdef HAVE_INT64_TIMESTAMP
result->time = t->time + (t->zone - tz) * USECS_PER_SEC;
while (result->time < INT64CONST(0))
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3b3840bae1..df15e2dc31 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1014,7 +1014,7 @@ dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
* 0 on success
* -1 on out of range
*
- * If attimezone is NULL, the global timezone (including possblly brute forced
+ * If attimezone is NULL, the global timezone (including possibly brute forced
* timezone) will be used.
*/
int
@@ -1113,8 +1113,8 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn,
utime = (pg_time_t) dt;
if ((Timestamp) utime == dt)
{
- struct pg_tm *tx = pg_localtime(&utime, (attimezone != NULL) ?
- attimezone : global_timezone);
+ struct pg_tm *tx = pg_localtime(&utime,
+ attimezone ? attimezone : global_timezone);
tm->tm_year = tx->tm_year + 1900;
tm->tm_mon = tx->tm_mon + 1;
@@ -3948,48 +3948,64 @@ Datum
timestamp_zone(PG_FUNCTION_ARGS)
{
text *zone = PG_GETARG_TEXT_P(0);
- Timestamp timestamp = PG_GETARG_TIMESTAMP(1);
+ Timestamp timestamp = PG_GETARG_TIMESTAMP(1);
TimestampTz result;
int tz;
pg_tz *tzp;
- char tzname[TZ_STRLEN_MAX+1];
+ char tzname[TZ_STRLEN_MAX + 1];
int len;
- struct pg_tm tm;
- fsec_t fsec;
- bool fail;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
- /* Find the specified timezone */
- len = (VARSIZE(zone) - VARHDRSZ>TZ_STRLEN_MAX) ?
- TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ;
+ /*
+ * Look up the requested timezone. First we look in the timezone
+ * database (to handle cases like "America/New_York"), and if that
+ * fails, we look in the date token table (to handle cases like "EST").
+ */
+ len = Min(VARSIZE(zone) - VARHDRSZ, TZ_STRLEN_MAX);
memcpy(tzname, VARDATA(zone), len);
- tzname[len] = 0;
+ tzname[len] = '\0';
tzp = pg_tzset(tzname);
- if (!tzp)
+ if (tzp)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognised",
- tzname)));
- PG_RETURN_NULL();
- }
+ /* Apply the timezone change */
+ struct pg_tm tm;
+ fsec_t fsec;
- /* Apply the timezone change */
- fail = (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0);
- if (!fail)
- {
+ 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);
- fail = (tm2timestamp(&tm, fsec, &tz, &result) != 0);
+ if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert to time zone \"%s\"",
+ tzname)));
}
- if (fail)
+ else
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert to time zone \"%s\"",
- tzname)));
- PG_RETURN_NULL();
+ char *lowzone;
+ int type,
+ val;
+
+ lowzone = downcase_truncate_identifier(VARDATA(zone),
+ VARSIZE(zone) - VARHDRSZ,
+ false);
+ type = DecodeSpecial(0, lowzone, &val);
+
+ if (type == TZ || type == DTZ)
+ tz = -(val * 60);
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized", tzname)));
+ tz = 0; /* keep compiler quiet */
+ }
+
+ result = dt2local(timestamp, tz);
}
PG_RETURN_TIMESTAMPTZ(result);
@@ -4109,37 +4125,59 @@ timestamptz_zone(PG_FUNCTION_ARGS)
Timestamp result;
int tz;
pg_tz *tzp;
- char tzname[TZ_STRLEN_MAX];
+ char tzname[TZ_STRLEN_MAX + 1];
int len;
- struct pg_tm tm;
- fsec_t fsec = 0;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ PG_RETURN_TIMESTAMP(timestamp);
- /* Find the specified zone */
- len = (VARSIZE(zone) - VARHDRSZ > TZ_STRLEN_MAX) ?
- TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ;
+ /*
+ * Look up the requested timezone. First we look in the timezone
+ * database (to handle cases like "America/New_York"), and if that
+ * fails, we look in the date token table (to handle cases like "EST").
+ */
+ len = Min(VARSIZE(zone) - VARHDRSZ, TZ_STRLEN_MAX);
memcpy(tzname, VARDATA(zone), len);
- tzname[len] = 0;
+ tzname[len] = '\0';
tzp = pg_tzset(tzname);
-
- if (!tzp)
+ if (tzp)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
+ /* Apply the timezone change */
+ struct pg_tm tm;
+ fsec_t fsec;
- PG_RETURN_NULL();
+ 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_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert to time zone \"%s\"",
+ tzname)));
}
+ else
+ {
+ char *lowzone;
+ int type,
+ val;
- if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0 ||
- tm2timestamp(&tm, fsec, NULL, &result))
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not to convert to time zone \"%s\"", tzname)));
- PG_RETURN_NULL();
+ lowzone = downcase_truncate_identifier(VARDATA(zone),
+ VARSIZE(zone) - VARHDRSZ,
+ false);
+ type = DecodeSpecial(0, lowzone, &val);
+
+ if (type == TZ || type == DTZ)
+ tz = val * 60;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized", tzname)));
+ tz = 0; /* keep compiler quiet */
+ }
+
+ result = dt2local(timestamp, tz);
}
PG_RETURN_TIMESTAMP(result);
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 8bb5ab0e88..623701df85 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -58,7 +58,7 @@ extern const char *pg_get_timezone_name(pg_tz *tz);
extern pg_tz *global_timezone;
-/* Maximum length of a timezone name */
+/* Maximum length of a timezone name (not including trailing null) */
#define TZ_STRLEN_MAX 255
#endif /* _PGTIME_H */
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index e99a89eb0e..7986d39603 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -974,7 +974,7 @@ init_timezone_hashtable(void)
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = TZ_STRLEN_MAX;
+ hash_ctl.keysize = TZ_STRLEN_MAX + 1;
hash_ctl.entrysize = sizeof(pg_tz);
timezone_cache = hash_create("Timezones",
@@ -997,7 +997,7 @@ pg_tzset(const char *name)
pg_tz *tzp;
pg_tz tz;
- if (strlen(name) >= TZ_STRLEN_MAX)
+ if (strlen(name) > TZ_STRLEN_MAX)
return NULL; /* not going to fit */
if (!timezone_cache)