Postgresql – Why does PostgreSQL interpret numeric UTC offset as POSIX and not ISO-8601

postgresqlpostgresql-10timestamptimezone

Setting time zone as numeric offset (without abbrev, thus IMO not in POSIX format) is still interpreted by PostgreSQL as POSIX format:

SET TIME ZONE '+02:00';

At least I assume, because checking the UTF offset via:

SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec';

yields -02:00:00, which means the offset from Greenwich is reversed as noted in docs:

Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.

Setting via named time zone yields positive offset from UTC, which is correct:

SET TIME ZONE 'Europe/Prague'; -- +02:00 DST at time of writing
SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec'; -- `02:00:00`

Postgres docs state there are 3 ways to specify time zone, none of which mention the +00:00 numeric syntax:

PostgreSQL allows you to specify time zones in three different forms:

  • A full time zone name, for example America/New_York. The recognized time zone names are listed in the pg_timezone_names view (see Section 51.90). PostgreSQL uses the widely-used IANA time zone data for this purpose, so the same time zone names are also recognized by much other software.
  • A time zone abbreviation, for example PST. Such a specification merely defines a particular offset from UTC, in contrast to full time zone names which can imply a set of daylight savings transition-date rules as well. The recognized abbreviations are listed in the pg_timezone_abbrevs view (see Section 51.89). You cannot set the configuration parameters TimeZone or log_timezone to a time zone abbreviation, but you can use abbreviations in date/time input values and with the AT TIME ZONE operator.
  • In addition to the timezone names and abbreviations, PostgreSQL will accept POSIX-style time zone specifications of the form STDoffset or STDoffsetDST […]

But since the POSIX (point 3 above) doesn't mention the ±00:00 format anywhere (only that it requires STD abbrev), I assume it does not fall into that category.

Furthermore, elsewhere in the docs the ±00:00 is explicitly specified as ISO 8601. Though I understand that applies to TIMESTAMPTZ datatype, and not runtime timezone configuration, it seems weird for postgres to interpret the same format in two different ways.

On top of that, the ±00:00 format is mentioned on wikipedia as ISO 8601.

Q1: So, does Postgres really interpret the ±00:00 as POSIX (or is there something else going on), and why?

Q2: Also, is there a definitive list of which formats that you can set either in postgresql.conf or at runtime via SET TIME ZONE are interpreted as POSIX, and which as ISO-8601?


(This question is a continuation of my previous: Why does postgresql differentiate between named and numeric time zone notations? Thanks jjanes for pointing me in the right direction.)


EDIT (18-08-26 18:56) to reflect Erwin's answer:

First, the SET docs state (added list numbers, emphasis):

SET TIME ZONE value is an alias for SET timezone TO value. The syntax SET TIME ZONE allows special syntax for the time zone specification. Here are examples of valid values:

  1. 'PST8PDT'

    The time zone for Berkeley, California.

  2. 'Europe/Rome'

    The time zone for Italy.

  3. -7

    The time zone 7 hours west from UTC (equivalent to PDT). Positive values are east from UTC.

  4. INTERVAL '-08:00' HOUR TO MINUTE

    The time zone 8 hours west from UTC (equivalent to PST).

[…]

Timezone settings given as numbers or intervals are internally translated to POSIX timezone syntax. For example, after SET TIME ZONE -7, SHOW TIME ZONE would report <-07>+07.

The emphasized sentence is either wrong, incomplete, or else I don't understand it at all.

  1. does it mean that their POSIX-ish string (1), it being a string, is interpreted as ISO-8601? Let's test that.

    SET TIME ZONE 'PST8PDT';
    SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec';
    

    Yields -07:00:00, which is negative, and negative values are west of Greenwich only in ISO-8601, since the docs state (emphasis mine):

    Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.

    Unless that quote above still only applies timestamp literals.

    If not, it seems that 'PST8PDT' string is indeed interpreted as ISO-8601.

    But,

    SET TIME ZONE 'UTC2';
    SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec';
    

    yields -02:00:00.. So are we back in POSIX again? Or is UTC2 not a POSIX STDoffset format?

  2. The number format -7 is specified to be interpreted as POSIX, so let's test that again:

    SET TIME ZONE 2;
    SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec';
    

    yields 02:00:00. Hmm.. that sounds ISO-8601 enough to me. +2 hours, east of Greenwich.

  3. Where does '+02:00' fall into? It's not a number, not an interval, though it acts as if postgres parses it as interval, because it results in POSIX offsets:

    SET TIME ZONE '+02:00';
    SELECT EXTRACT(TIMEZONE FROM now()) * interval '1sec';
    

    Yields -02:00:00.

So I'm still confused. One other questions that comes to mind:

Q3: does west/east of Greenwich plus/minus reversal for POSIX apply just for timestamp literals, or time zone configuration, too?

Best Answer

Good question, and I agree the manual can easily be misread there. Quoting the same source, but with one more leading sentence:

All timezone-aware dates and times are stored internally in UTC. They are converted to local time in the zone specified by the TimeZone configuration parameter before being displayed to the client.

PostgreSQL allows you to specify time zones in three different forms:

[...]

This part of the manual deals with what's allowed in the TimeZone configuration parameter (and, accordingly, the AT TIME ZONE construct).

The part of the manual you quote further down in your question deals with Time Zone Input in timestamp literals which follows the mentioned ISO-8601 convention.

You need this part of the manual explaining what's allowed for SET TIME ZONE (and, accordingly, the AT TIME ZONE construct):

TIME ZONE

SET TIME ZONE value is an alias for SET timezone TOvalue. The syntax SET TIME ZONE allows special syntax for the time zone specification. Here are examples of valid values:

'PST8PDT'
The time zone for Berkeley, California.

'Europe/Rome'
The time zone for Italy.

-7
The time zone 7 hours west from UTC (equivalent to PDT). Positive values are east from UTC.

INTERVAL '-08:00' HOUR TO MINUTE
The time zone 8 hours west from UTC (equivalent to PST).

[...]

In your example SET TIME ZONE '+02:00'; looks like an interval, but that's deceiving. Only explicitly declaring INTERVAL makes Postgres treat it as interval. '+02:00' is just another case of STDoffset, with no leading STD, hence defaulting to UTC. So:

SET TIME ZONE '+02:00';

... is equivalent to:

SET TIME ZONE 'UTC+02:00';

These quotes from the first manual page seem instrumental:

The default time zone is specified as a constant numeric offset from UTC.

And:

One should be wary that the POSIX-style time zone feature can lead to silently accepting bogus input, since there is no check on the reasonableness of the zone abbreviations. For example, SET TIMEZONE TO FOOBAR0 will work, leaving the system effectively using a rather peculiar abbreviation for UTC. Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.

But the manual does not seem to mention that the STD part can be missing from the specification. Seems like a documentation bug ...