sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL verkeerde conversie van tijdstempel zonder tijdzone naar tijdstempel met tijdzone

Belangrijkste dingen om te begrijpen

timestamp without time zone AT TIME ZONE herinterpreteert een timestamp omdat het zich in die tijdzone bevindt met als doel het te converteren naar UTC .

timestamp with time zone AT TIME ZONE converteert een timestamptz in een timestamp in de opgegeven tijdzone.

PostgreSQL gebruikt ISO-8601-tijdzones, die specificeren dat ten oosten van Greenwich positief is ... tenzij u een POSIX-tijdzonespecificatie gebruikt, in welk geval deze POSIX volgt. Er ontstaat waanzin.

Waarom de eerste een onverwacht resultaat oplevert

Tijdstempels en tijdzones in SQL zijn verschrikkelijk. Dit:

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';

interpreteert de onbekende getypte letterlijke '2011-12-30 00:30:00' als timestamp without time zone , waarvan Pg aanneemt dat het zich in de lokale TimeZone bevindt, tenzij anders aangegeven. Wanneer u AT TIME ZONE . gebruikt , het is (volgens de specificatie) opnieuw geïnterpreteerd als een timestamp with time zone in de tijdzone EST5EDT vervolgens opgeslagen als een absolute tijd in UTC - dus het is geconverteerd van EST5EDT naar UTC, d.w.z. de tijdzone-offset wordt afgetrokken . x - (-5) is x + 5 .

Deze tijdstempel, aangepast aan UTC-opslag, wordt vervolgens aangepast voor uw server TimeZone instelling voor weergave zodat deze in lokale tijd wordt weergegeven.

Als je in plaats daarvan wilt zeggen "Ik heb deze tijdstempel in UTC-tijd en wil zien wat de equivalente lokale tijd in EST5EDT is", als je onafhankelijk wilt zijn van de TimeZone-instelling van de server, moet je zoiets schrijven als:

select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE 'EST5EDT';

Dit zegt "Gegeven tijdstempel 2011-12-30 00:30:00, behandel het als een tijdstempel in UTC bij het converteren naar timestamptz, converteer dat timestamptz vervolgens naar een lokale tijd in EST5EDT".

Verschrikkelijk, niet? Ik wil een firma aan het woord laten wie heeft besloten over de gekke semantiek van AT TIME ZONE - het zou eigenlijk zoiets moeten zijn als timestamp CONVERT FROM TIME ZONE '-5' en timestamptz CONVERT TO TIME ZONE '+5' . Ook timestamp with time zone zou eigenlijk zijn tijdzone met zich mee moeten dragen, niet worden opgeslagen in UTC en automatisch worden geconverteerd naar lokale tijd.

Waarom de tweede werkt (zolang TimeZone =UTC)

Uw originele "werkt" versie:

select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';

zal alleen correct zijn als TimeZone is ingesteld op UTC, omdat de tekst-naar-timestamptz-cast TimeZone aanneemt als er geen is gespecificeerd.

Waarom de derde werkt

Twee problemen heffen elkaar op.

De andere versie die lijkt te werken, is TimeZone-onafhankelijk, maar het werkt alleen omdat twee problemen zichzelf opheffen. Ten eerste, zoals hierboven uitgelegd, timestamp without time zone AT TIME ZONE herinterpreteert de tijdstempel als zijnde in die tijdzone voor conversie naar een UTC-tijdstempel; dit trekt effectief af de tijdzone-offset.

Echter, om redenen die mij te boven gaan, gebruikt PostgreSQL tijdstempels met het omgekeerde teken van wat ik gewend ben te zien op de meeste plaatsen. Zie de documentatie:

Een ander probleem om in gedachten te houden is dat in POSIX-tijdzonenamen positieve offsets worden gebruikt voor locaties ten westen van Greenwich. Overal elders volgt PostgreSQL de ISO-8601-conventie dat positieve tijdzone-offsets ten oosten van Greenwich liggen.

Dit betekent dat EST5EDT is hetzelfde als +5 , niet -5 . Dat is waarom het werkt:omdat je de tz-offset aftrekt en niet optelt, maar je trekt een genegeerde offset af!

Wat je nodig hebt om het correct te krijgen is in plaats daarvan:

select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE '+5';



  1. CTE en de verjaardagsparadox

  2. Tijdzoneopslag in tijdstempel van gegevenstype met tijdzone

  3. SQLite UNIEKE beperking

  4. 11 manieren om dubbele rijen te vinden terwijl de primaire sleutel in SQLite wordt genegeerd