diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4e09e06aed7bdc3f55a8f6bc2bae5dbff414f129..322d8d6dc78badb5fef3fc6308aa3c9b28178f29 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9811,6 +9811,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    </tgroup>
   </table>
 
+  <para>
+   While most timezone abbreviations represent fixed offsets from UTC,
+   there are some that have historically varied in value
+   (see <xref linkend="datetime-config-files"> for more information).
+   In such cases this view presents their current meaning.
+  </para>
+
  </sect1>
 
  <sect1 id="view-pg-timezone-names">
diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml
index ffd0715128255a2aecbee71eb1951b94d2690f5f..ef9139f9e3860564599cac22cdb3aea68ffe7a3e 100644
--- a/doc/src/sgml/datetime.sgml
+++ b/doc/src/sgml/datetime.sgml
@@ -384,19 +384,38 @@
 
    <para>
     A <replaceable>zone_abbreviation</replaceable> is just the abbreviation
-    being defined.  The <replaceable>offset</replaceable> is the equivalent
-    offset in seconds from UTC, positive being east from Greenwich and
-    negative being west.  For example, -18000 would be five hours west
-    of Greenwich, or North American east coast standard time.  <literal>D</>
-    indicates that the zone name represents local daylight-savings time rather
-    than standard time.  Alternatively, a <replaceable>time_zone_name</> can
-    be given, in which case that time zone definition is consulted, and the
-    abbreviation's meaning in that zone is used.  This alternative is
-    recommended only for abbreviations whose meaning has historically varied,
-    as looking up the meaning is noticeably more expensive than just using
-    a fixed integer value.
+    being defined.  An <replaceable>offset</replaceable> is an integer giving
+    the equivalent offset in seconds from UTC, positive being east from
+    Greenwich and negative being west.  For example, -18000 would be five
+    hours west of Greenwich, or North American east coast standard time.
+    <literal>D</> indicates that the zone name represents local
+    daylight-savings time rather than standard time.
    </para>
 
+   <para>
+    Alternatively, a <replaceable>time_zone_name</> can be given, referencing
+    a zone name defined in the IANA timezone database.  The zone's definition
+    is consulted to see whether the abbreviation is or has been in use in
+    that zone, and if so, the appropriate meaning is used &mdash; that is,
+    the meaning that was currently in use at the timestamp whose value is
+    being determined, or the meaning in use immediately before that if it
+    wasn't current at that time, or the oldest meaning if it was used only
+    after that time.  This behavior is essential for dealing with
+    abbreviations whose meaning has historically varied.  It is also allowed
+    to define an abbreviation in terms of a zone name in which that
+    abbreviation does not appear; then using the abbreviation is just
+    equivalent to writing out the zone name.
+   </para>
+
+   <tip>
+    <para>
+     Using a simple integer <replaceable>offset</replaceable> is preferred
+     when defining an abbreviation whose offset from UTC has never changed,
+     as such abbreviations are much cheaper to process than those that
+     require consulting a time zone definition.
+    </para>
+   </tip>
+
    <para>
     The <literal>@INCLUDE</> syntax allows inclusion of another file in the
     <filename>.../share/timezonesets/</> directory.  Inclusion can be nested,
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 965c3b4ff06870055712c3f0d05f873ac43bd970..45ba7cd906396e8ff485d2c255ca6163c8f0f815 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -56,8 +56,9 @@ static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec,
 				int scale);
 static int DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp,
 								pg_time_t *tp);
-static int DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
-									  pg_tz *tzp, int *isdst);
+static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
+									  const char *abbr, pg_tz *tzp,
+									  int *offset, int *isdst);
 static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
 
 
@@ -1689,19 +1690,40 @@ overflow:
  * This differs from the behavior of DetermineTimeZoneOffset() in that a
  * standard-time or daylight-time abbreviation forces use of the corresponding
  * GMT offset even when the zone was then in DS or standard time respectively.
+ * (However, that happens only if we can match the given abbreviation to some
+ * abbreviation that appears in the IANA timezone data.  Otherwise, we fall
+ * back to doing DetermineTimeZoneOffset().)
  */
 int
 DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp)
 {
 	pg_time_t	t;
+	int			zone_offset;
+	int			abbr_offset;
+	int			abbr_isdst;
 
 	/*
 	 * Compute the UTC time we want to probe at.  (In event of overflow, we'll
 	 * probe at the epoch, which is a bit random but probably doesn't matter.)
 	 */
-	(void) DetermineTimeZoneOffsetInternal(tm, tzp, &t);
+	zone_offset = DetermineTimeZoneOffsetInternal(tm, tzp, &t);
 
-	return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, &tm->tm_isdst);
+	/*
+	 * Try to match the abbreviation to something in the zone definition.
+	 */
+	if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+											  &abbr_offset, &abbr_isdst))
+	{
+		/* Success, so use the abbrev-specific answers. */
+		tm->tm_isdst = abbr_isdst;
+		return abbr_offset;
+	}
+
+	/*
+	 * No match, so use the answers we already got from
+	 * DetermineTimeZoneOffsetInternal.
+	 */
+	return zone_offset;
 }
 
 
@@ -1715,19 +1737,41 @@ DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 								pg_tz *tzp, int *isdst)
 {
 	pg_time_t	t = timestamptz_to_time_t(ts);
+	int			zone_offset;
+	int			abbr_offset;
+	int			tz;
+	struct pg_tm tm;
+	fsec_t		fsec;
 
-	return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, isdst);
+	/*
+	 * If the abbrev matches anything in the zone data, this is pretty easy.
+	 */
+	if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+											  &abbr_offset, isdst))
+		return abbr_offset;
+
+	/*
+	 * Else, break down the timestamp so we can use DetermineTimeZoneOffset.
+	 */
+	if (timestamp2tm(ts, &tz, &tm, &fsec, NULL, tzp) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("timestamp out of range")));
+
+	zone_offset = DetermineTimeZoneOffset(&tm, tzp);
+	*isdst = tm.tm_isdst;
+	return zone_offset;
 }
 
 
 /* DetermineTimeZoneAbbrevOffsetInternal()
  *
  * Workhorse for above two functions: work from a pg_time_t probe instant.
- * DST status is returned into *isdst.
+ * On success, return GMT offset and DST status into *offset and *isdst.
  */
-static int
-DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
-									  pg_tz *tzp, int *isdst)
+static bool
+DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
+									  int *offset, int *isdst)
 {
 	char		upabbr[TZ_STRLEN_MAX + 1];
 	unsigned char *p;
@@ -1739,18 +1783,17 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
 		*p = pg_toupper(*p);
 
 	/* Look up the abbrev's meaning at this time in this zone */
-	if (!pg_interpret_timezone_abbrev(upabbr,
-									  &t,
-									  &gmtoff,
-									  isdst,
-									  tzp))
-		ereport(ERROR,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("time zone abbreviation \"%s\" is not used in time zone \"%s\"",
-						abbr, pg_get_timezone_name(tzp))));
-
-	/* Change sign to agree with DetermineTimeZoneOffset() */
-	return (int) -gmtoff;
+	if (pg_interpret_timezone_abbrev(upabbr,
+									 &t,
+									 &gmtoff,
+									 isdst,
+									 tzp))
+	{
+		/* Change sign to agree with DetermineTimeZoneOffset() */
+		*offset = (int) -gmtoff;
+		return true;
+	}
+	return false;
 }
 
 
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 67f26db2048e8c25e94ddc15f420e91a04f7105f..2bfc13ad72d3cd301a3a1a57b9d78497fba8b290 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2603,3 +2603,23 @@ SELECT '2007-12-09 07:30:00 UTC'::timestamptz AT TIME ZONE 'VET';
  Sun Dec 09 03:00:00 2007
 (1 row)
 
+--
+-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
+-- more-or-less working.  We can't test their contents in any great detail
+-- without the outputs changing anytime IANA updates the underlying data,
+-- but it seems reasonable to expect at least one entry per major meridian.
+-- (At the time of writing, the actual counts are around 38 because of
+-- zones using fractional GMT offsets, so this is a pretty loose test.)
+--
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_names;
+ ok 
+----
+ t
+(1 row)
+
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+ ok 
+----
+ t
+(1 row)
+
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c023095bb89b4bf5df74431d0f624020d403ca57..ce9d1c2fa1b0a02aeb109ec647b12993ad470d87 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -468,3 +468,14 @@ SELECT '2007-12-09 07:00:00 UTC'::timestamptz AT TIME ZONE 'VET';
 SELECT '2007-12-09 07:00:01 UTC'::timestamptz AT TIME ZONE 'VET';
 SELECT '2007-12-09 07:29:59 UTC'::timestamptz AT TIME ZONE 'VET';
 SELECT '2007-12-09 07:30:00 UTC'::timestamptz AT TIME ZONE 'VET';
+
+--
+-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
+-- more-or-less working.  We can't test their contents in any great detail
+-- without the outputs changing anytime IANA updates the underlying data,
+-- but it seems reasonable to expect at least one entry per major meridian.
+-- (At the time of writing, the actual counts are around 38 because of
+-- zones using fractional GMT offsets, so this is a pretty loose test.)
+--
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_names;
+select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;