diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4e09e06aed..322d8d6dc7 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
+
+ While most timezone abbreviations represent fixed offsets from UTC,
+ there are some that have historically varied in value
+ (see for more information).
+ In such cases this view presents their current meaning.
+
+
diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml
index ffd0715128..ef9139f9e3 100644
--- a/doc/src/sgml/datetime.sgml
+++ b/doc/src/sgml/datetime.sgml
@@ -384,19 +384,38 @@
A zone_abbreviation is just the abbreviation
- being defined. The offset 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. D>
- indicates that the zone name represents local daylight-savings time rather
- than standard time. Alternatively, a 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 offset 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.
+ D> indicates that the zone name represents local
+ daylight-savings time rather than standard time.
+
+ Alternatively, a 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 — 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.
+
+
+
+
+ Using a simple integer offset 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.
+
+
+
The @INCLUDE> syntax allows inclusion of another file in the
.../share/timezonesets/> directory. Inclusion can be nested,
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 965c3b4ff0..45ba7cd906 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 67f26db204..2bfc13ad72 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 c023095bb8..ce9d1c2fa1 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;