diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml index 9610dc36c5..2cfda15f09 100644 --- a/doc/src/sgml/datetime.sgml +++ b/doc/src/sgml/datetime.sgml @@ -1,5 +1,5 @@ @@ -990,9 +990,7 @@ $PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.46 2005/06/15 00:34:08 momjian shows the time zone names recognized by PostgreSQL as valid - settings for the parameter, and as - parameters to the AT TIME ZONE function (see - ). Note that + settings for the parameter. Note that these names are conceptually as well as practically different from the names shown in : most of these names imply a local daylight-savings time rule, whereas @@ -1006,7 +1004,7 @@ $PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.46 2005/06/15 00:34:08 momjian - Time Zone Names for Setting <varname>timezone</> and <literal>AT TIME ZONE</> + Time Zone Names for Setting <varname>timezone</> diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9bef5e1378..41ca4a8cc9 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -5730,9 +5730,9 @@ SELECT date_trunc('year', TIMESTAMP '2001-02-16 20:38:40'); In these expressions, the desired time zone zone can be specified either as a text string (e.g., 'PST') or as an interval (e.g., INTERVAL '-08:00'). - In the text case, the available zone names are those shown in - . 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 + or + . diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 477d7993e6..b36ee18092 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.119 2005/07/23 14:25:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.120 2005/09/09 02:31:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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; + + 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 */ + } } - /* 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(); - } - - 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 2b15e64e06..b2d6f774c2 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.151 2005/08/25 05:01:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.152 2005/09/09 02:31:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 ab4bdef1f6..95f2139393 100644 --- a/src/include/pgtime.h +++ b/src/include/pgtime.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.9 2005/07/22 03:46:34 momjian Exp $ + * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.10 2005/09/09 02:31:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 305bea2e5e..2512061222 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.36 2005/06/26 23:32:34 tgl Exp $ + * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.37 2005/09/09 02:31:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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)