mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-05 16:39:19 +02:00
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>
This commit is contained in:
parent
86ddc12a8d
commit
73a802a411
@ -9755,6 +9755,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
|
|||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</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>
|
||||||
|
|
||||||
<sect1 id="view-pg-timezone-names">
|
<sect1 id="view-pg-timezone-names">
|
||||||
|
@ -384,19 +384,38 @@
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
A <replaceable>zone_abbreviation</replaceable> is just the abbreviation
|
A <replaceable>zone_abbreviation</replaceable> is just the abbreviation
|
||||||
being defined. The <replaceable>offset</replaceable> is the equivalent
|
being defined. An <replaceable>offset</replaceable> is an integer giving
|
||||||
offset in seconds from UTC, positive being east from Greenwich and
|
the equivalent offset in seconds from UTC, positive being east from
|
||||||
negative being west. For example, -18000 would be five hours west
|
Greenwich and negative being west. For example, -18000 would be five
|
||||||
of Greenwich, or North American east coast standard time. <literal>D</>
|
hours west of Greenwich, or North American east coast standard time.
|
||||||
indicates that the zone name represents local daylight-savings time rather
|
<literal>D</> indicates that the zone name represents local
|
||||||
than standard time. Alternatively, a <replaceable>time_zone_name</> can
|
daylight-savings time rather than standard time.
|
||||||
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.
|
|
||||||
</para>
|
</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 — 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>
|
<para>
|
||||||
The <literal>@INCLUDE</> syntax allows inclusion of another file in the
|
The <literal>@INCLUDE</> syntax allows inclusion of another file in the
|
||||||
<filename>.../share/timezonesets/</> directory. Inclusion can be nested,
|
<filename>.../share/timezonesets/</> directory. Inclusion can be nested,
|
||||||
|
@ -52,8 +52,9 @@ static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec,
|
|||||||
int scale);
|
int scale);
|
||||||
static int DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp,
|
static int DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp,
|
||||||
pg_time_t *tp);
|
pg_time_t *tp);
|
||||||
static int DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
|
static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
|
||||||
pg_tz *tzp, int *isdst);
|
const char *abbr, pg_tz *tzp,
|
||||||
|
int *offset, int *isdst);
|
||||||
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
|
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
|
||||||
|
|
||||||
|
|
||||||
@ -1620,19 +1621,40 @@ overflow:
|
|||||||
* This differs from the behavior of DetermineTimeZoneOffset() in that a
|
* This differs from the behavior of DetermineTimeZoneOffset() in that a
|
||||||
* standard-time or daylight-time abbreviation forces use of the corresponding
|
* 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.
|
* 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
|
int
|
||||||
DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp)
|
DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp)
|
||||||
{
|
{
|
||||||
pg_time_t t;
|
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
|
* 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.)
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1646,19 +1668,41 @@ DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
|
|||||||
pg_tz *tzp, int *isdst)
|
pg_tz *tzp, int *isdst)
|
||||||
{
|
{
|
||||||
pg_time_t t = timestamptz_to_time_t(ts);
|
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()
|
/* DetermineTimeZoneAbbrevOffsetInternal()
|
||||||
*
|
*
|
||||||
* Workhorse for above two functions: work from a pg_time_t probe instant.
|
* 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
|
static bool
|
||||||
DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
|
DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
|
||||||
pg_tz *tzp, int *isdst)
|
int *offset, int *isdst)
|
||||||
{
|
{
|
||||||
char upabbr[TZ_STRLEN_MAX + 1];
|
char upabbr[TZ_STRLEN_MAX + 1];
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
@ -1670,18 +1714,17 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
|
|||||||
*p = pg_toupper(*p);
|
*p = pg_toupper(*p);
|
||||||
|
|
||||||
/* Look up the abbrev's meaning at this time in this zone */
|
/* Look up the abbrev's meaning at this time in this zone */
|
||||||
if (!pg_interpret_timezone_abbrev(upabbr,
|
if (pg_interpret_timezone_abbrev(upabbr,
|
||||||
&t,
|
&t,
|
||||||
&gmtoff,
|
&gmtoff,
|
||||||
isdst,
|
isdst,
|
||||||
tzp))
|
tzp))
|
||||||
ereport(ERROR,
|
{
|
||||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
/* Change sign to agree with DetermineTimeZoneOffset() */
|
||||||
errmsg("time zone abbreviation \"%s\" is not used in time zone \"%s\"",
|
*offset = (int) -gmtoff;
|
||||||
abbr, pg_get_timezone_name(tzp))));
|
return true;
|
||||||
|
}
|
||||||
/* Change sign to agree with DetermineTimeZoneOffset() */
|
return false;
|
||||||
return (int) -gmtoff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2538,3 +2538,23 @@ SELECT '2007-12-09 07:30:00 UTC'::timestamptz AT TIME ZONE 'VET';
|
|||||||
Sun Dec 09 03:00:00 2007
|
Sun Dec 09 03:00:00 2007
|
||||||
(1 row)
|
(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)
|
||||||
|
|
||||||
|
@ -449,3 +449,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: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:29:59 UTC'::timestamptz AT TIME ZONE 'VET';
|
||||||
SELECT '2007-12-09 07:30:00 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;
|
||||||
|
Loading…
Reference in New Issue
Block a user