aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/datetime.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2016-09-02 17:29:31 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2016-09-02 17:30:02 -0400
commit39b691f251167bbb3d49203abfb39d430f68f411 (patch)
tree6fb45f064b63f89fa1ac81f6f037aa770819c617 /src/backend/utils/adt/datetime.c
parentec136d19b21791c845b1deeff43df137add0639e (diff)
downloadpostgresql-39b691f251167bbb3d49203abfb39d430f68f411.tar.gz
postgresql-39b691f251167bbb3d49203abfb39d430f68f411.zip
Don't require dynamic timezone abbreviations to match underlying time zone.
Previously, we threw an error if a dynamic timezone abbreviation did not match any abbreviation recorded in the referenced IANA time zone entry. That seemed like a good consistency check at the time, but it turns out that a number of the abbreviations in the IANA database are things that Olson and crew made up out of whole cloth. Their current policy is to remove such names in favor of using simple numeric offsets. Perhaps unsurprisingly, a lot of these made-up abbreviations have varied in meaning over time, which meant that our commit b2cbced9e and later changes made them into dynamic abbreviations. So with newer IANA database versions that don't mention these abbreviations at all, we fail, as reported in bug #14307 from Neil Anderson. It's worse than just a few unused-in-the-wild abbreviations not working, because the pg_timezone_abbrevs view stops working altogether (since its underlying function tries to compute the whole view result in one call). We considered deleting these abbreviations from our abbreviations list, but the problem with that is that we can't stay ahead of possible future IANA changes. Instead, let's leave the abbreviations list alone, and treat any "orphaned" dynamic abbreviation as just meaning the referenced time zone. It will behave a bit differently than it used to, in that you can't any longer override the zone's standard vs. daylight rule by using the "wrong" abbreviation of a pair, but that's better than failing entirely. (Also, this solution can be interpreted as adding a small new feature, which is that any abbreviation a user wants can be defined as referencing a time zone name.) Back-patch to all supported branches, since this problem affects all of them when using tzdata 2016f or newer. Report: <20160902031551.15674.67337@wrigleys.postgresql.org> Discussion: <6189.1472820913@sss.pgh.pa.us>
Diffstat (limited to 'src/backend/utils/adt/datetime.c')
-rw-r--r--src/backend/utils/adt/datetime.c85
1 files changed, 64 insertions, 21 deletions
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 965c3b4ff06..45ba7cd9063 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;
}