Postgres има два различни типа данни за времеви печати:
timestamp with time zone
, кратко име:timestamptz
timestamp without time zone
, кратко име:timestamp
timestamptz
е предпочитаният въведете семейството дата/час, буквално. Има typispreferred
зададен в pg_type
, което може да е от значение:
- Генериране на времеви серии между две дати в PostgreSQL
Вътрешно хранилище и епоха
Вътрешно времевите марки заемат 8 байта памет на диск и в RAM. Това е цяло число, представляващо броя на микросекунди от епохата на Postgres, 2000-01-01 00:00:00 UTC.
Postgres също има вградени познания за често използваното UNIX отброяване на секунди от епохата на UNIX, 1970-01-01 00:00:00 UTC и го използва във функциите to_timestamp(double precision)
или EXTRACT(EPOCH FROM timestamptz)
.
Изходният код:
* Timestamps, as well as the h/m/s fields of intervals, are stored as * int64 values with units of microseconds. (Once upon a time they were * double values with units of seconds.)
И:
/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */ #define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */ #define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
Микросекундната разделителна способност се превежда до максимум 6 дробни цифри за секунди.
timestamp
За timestamp
не е посочена изрично часова зона. Postgres игнорира всеки модификатор на часовата зона, добавен към входния литерал по погрешка!
Няма изместване на часове за показване. С всичко, което се случва в една и съща часова зона, това е добре. За различна часова зона значението промени, но стойност и дисплей останете същите.
timestamptz
Обработка на timestamptz
е тънко различен. Цитирам ръководството тук:
За
timestamp with time zone
, вътрешно съхранената стойност е винаги в UTC (Универсално координирано време ...)
Удебелен акцент мой. Самата часова зона никога не се съхранява . Това е входен модификатор, използван за изчисляване на съответното времево клеймо UTC, което се съхранява - или и изходен декоратор, използван за изчисляване на местното време за показване - с добавено изместване на часовата зона. Ако не добавите отместване за timestamptz
при въвеждане се приема текущата настройка на часовата зона на сесията. Всички изчисления се извършват с UTC стойности на времеви отпечатъци. Ако (може) трябва да имате работа с повече от една часова зона, използвайте timestamptz
. С други думи:Ако може да има някакво съмнение или неразбиране относно предполагаемата часова зона, използвайте timestamptz
. Прилага се в повечето случаи на употреба.
Клиенти като psql или pgAdmin или което и да е приложение, комуникиращо чрез libpq (като Ruby с pg gem), се представят с клеймото за време плюс изместване за текущата часова зона или според поискано часова зона (вижте по-долу). Винаги е една и съща точка във времето , варира само форматът на дисплея. Или, както се казва в ръководството:
Всички дати и часове, наясно с часовата зона, се съхраняват вътрешно в UTC. Те се преобразуват в местно време в зоната, определена от
TimeZone
конфигурационен параметър, преди да бъде показан на клиента.
Пример в psql:
db=# SELECT timestamptz '2012-03-05 20:00+03';
timestamptz
------------------------
2012-03-05 18:00:00+01
Какво се случи тук?
Избрах произволно отместване на часовата зона +3
за входния литерал. За Postgres това е само един от многото начини за въвеждане на UTC времеви печат 2012-03-05 17:00:00
. Резултатът от заявката се показва за текущата настройка на часовата зона Виена/Австрия в моя тест, който има отместване +1
през зимата и +2
през лятното часово време ("лятно часово време", DST). Така че 2012-03-05 18:00:00+01
тъй като DST започва само по-късно.
Postgres веднага забравя входния литерал. Всичко, което помни, е стойността за типа данни. Точно както с десетичното число. numeric '003.4'
или numeric '+3.4'
- и двете водят до една и съща вътрешна стойност.
AT TIME ZONE
Всичко, което липсва сега, е инструмент за интерпретиране или представяне на литерали за времеви отпечатъци според конкретна часова зона. Това е мястото, където AT TIME ZONE
construct идва. Има два различни случая на употреба. timestamptz
се преобразува в timestamp
и обратно.
За да въведете UTC timestamptz
2012-03-05 17:00:00+0
:
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC'
... което е еквивалентно на:
SELECT timestamptz '2012-03-05 17:00:00 UTC'
За показване на същия момент във времето като EST timestamp
(Източно стандартно време):
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' AT TIME ZONE 'EST'
Точно така, AT TIME ZONE 'UTC'
два пъти . Първият интерпретира timestamp
стойност като (предоставена) UTC времеви печат, връщащ типа timestamptz
. Вторият преобразува timestamptz
до timestamp
в дадена часова зона „EST“ – какво показва стенен часовник в часовата зона EST в този момент.
Примери
SELECT ts AT TIME ZONE 'UTC'
FROM (
VALUES
(1, timestamptz '2012-03-05 17:00:00+0')
, (2, timestamptz '2012-03-05 18:00:00+1')
, (3, timestamptz '2012-03-05 17:00:00 UTC')
, (4, timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6')
, (5, timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC')
, (6, timestamp '2012-03-05 07:00:00' AT TIME ZONE 'US/Hawaii') -- ①
, (7, timestamptz '2012-03-05 07:00:00 US/Hawaii') -- ①
, (8, timestamp '2012-03-05 07:00:00' AT TIME ZONE 'HST') -- ①
, (9, timestamp '2012-03-05 18:00:00+1') -- ② loaded footgun!
) t(id, ts);
Връща 8 (или 9) идентични редове с времеви отпечатък, колони, съдържащи същото времево клеймо UTC 2012-03-05 17:00:00
. 9-ти ред изглежда работи в моята часова зона, но е зъл капан. Вижте по-долу.
① Редове 6 - 8 с име на часовата зона и съкращение на часовата зона за Хавайското време са обект на DST (лятно часово време) и може да се различават, макар и не в момента. Име на часовата зона като 'US/Hawaii'
е наясно с правилата за DST и всички исторически смени автоматично, докато съкращение като HST
е просто тъп код за фиксирано изместване. Може да се наложи да добавите различно съкращение за лятно/стандартно време. Името правилно интерпретира всяко времева марка в дадена часова зона. Съкращение е евтин, но трябва да е правилният за дадената времева марка:
- Имена на часови зони с идентични свойства дават различен резултат, когато се прилагат към времеви печат
Лятното часово време не е сред най-ярките идеи, които човечеството някога е измисляло.
② Ред 9, маркиран като зареден крак работи при мен , но само по стечение на обстоятелствата. Ако изрично предадете литерал към timestamp [without time zone]
, всяко изместване на часовата зона се игнорира! Използва се само голата времева марка. След това стойността автоматично се принуждава към timestamptz
в примера, за да съответства на типа колона. За тази стъпка, timezone
се приема настройката на текущата сесия, която се оказва в същата часова зона +1
в моя случай (Европа/Виена). Но вероятно не във вашия случай - което ще доведе до различна стойност. Накратко:Не предавайте timestamptz
литерали към timestamp
или губите изместването на часовата зона.
Вашите въпроси
Потребителят съхранява време, да речем 17 март 2012 г., 19:00 ч. Не искам преобразуванията на часовата зона или часовата зона да се съхраняват.
Самата часова зона никога не се съхранява. Използвайте един от методите по-горе, за да въведете времева марка за UTC.
Използвам само посочената от потребителите часова зона, за да получавам записи „преди“ или „след“ текущото време в местната часова зона на потребителите.
Можете да използвате една заявка за всички клиенти в различни часови зони.
За абсолютно глобално време:
SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE 'UTC')::time
За времето според местния часовник:
SELECT * FROM tbl WHERE time_col > now()::time
Все още не сте уморени от основна информация? Има още в ръководството.