diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml
index 9610dc36c5cada51e4d6c12f98bb37c2fcf75e24..2cfda15f097491ad195d3be18988e512683fc4e2 100644
--- a/doc/src/sgml/datetime.sgml
+++ b/doc/src/sgml/datetime.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.46 2005/06/15 00:34:08 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.47 2005/09/09 02:31:48 tgl Exp $
 -->
 
  <appendix id="datetime-appendix">
@@ -990,9 +990,7 @@ $PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.46 2005/06/15 00:34:08 momjian
    <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: pgsql/doc/src/sgml/datetime.sgml,v 2.46 2005/06/15 00:34:08 momjian
    </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 9bef5e137839cac9d1eb5cb768972f6c6818bed0..41ca4a8cc9b7aa28745c39f8f22e876c3736f1e8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.283 2005/08/25 01:29:55 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.284 2005/09/09 02:31:48 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -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 477d7993e6ff048c63d69b6268aaa2f00532d762..b36ee180929b628e0dcf87f216cb9a74bc1e5d45 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;
 
-	/* 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 2b15e64e06172a6c0b21b4bc379b67baab3dd86d..b2d6f774c2fb9f59ab21cd0b01f409a5436c2adc 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 ab4bdef1f6813eb892b3ce4ad7097ee18ef7ccc5..95f21393939e0373c9db4b574257abb955139c73 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 305bea2e5ebda486f0b4a8bdf68c6ae7a583412b..2512061222accb1704ccced1fa852364a8843106 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)