2011-05-27 61 views
110

我想定義一個在多時區項目的上下文中將時間戳存儲在Postgres數據庫中的最佳實踐。我應該在PostgreSQL數據庫中選擇哪種時間戳類型?

我可以

  1. 選擇TIMESTAMP WITHOUT TIME ZONE記得在插入時使用了哪個時區此字段
  2. 選擇TIMESTAMP WITHOUT TIME ZONE並添加一個字段將包含在插入時間
  3. 使用的時區的名稱
  4. 選擇TIMESTAMP WITH TIME ZONE並插入相應的時間戳

我有選擇的輕微偏好3(帶時區的時間戳),但希望對此問題有教育意見。

回答

133

首先,PostgreSQL的時間處理和算術是太棒了,在一般情況下,選項3是好的。它是,但是,時間和時區的一個不完整的視圖,並可以補充:

  1. 商店中的用戶的時區作爲用戶偏好(例如America/Los_Angeles,不-0700)的名稱。
  2. 將用戶事件/時間數據本地提交到其參考幀(最可能是與UTC的偏移量,如-0700)。
  3. 在應用程序中,將時間轉換爲UTC並使用TIMESTAMP WITH TIME ZONE列存儲。
  4. 返回本地用戶時區的時間請求(即從UTC轉換爲America/Los_Angeles)。
  5. 將您的數據庫的timezone設置爲UTC

此選項並不總是奏效,因爲它可能很難獲得用戶的時區,因此使用TIMESTAMP WITH TIME ZONE輕量級應用對衝意見。這就是說,讓我更詳細地解釋一下這個選項4的一些背景問題。

和選項3一樣,WITH TIME ZONE的原因是因爲發生什麼事的時間是絕對時刻。 WITHOUT TIME ZONE產生相對於時區。永遠不要混合絕對和相對的TIMESTAMPs。

從編程和一致性的角度來看,確保所有計算均使用UTC作爲時區。這不是PostgreSQL的要求,但它與其他編程語言或環境集成時有幫助。在列上設置CHECK以確保寫入時間戳列的時區偏移量爲0是防禦性的位置,可防止幾類錯誤(例如,腳本將數據轉儲到文件中,並使用詞法排序來對其他時間數據進行排序)。同樣,PostgreSQL不需要這個來正確地進行日期計算或者在時區之間轉換(即PostgreSQL非常善於在任意兩個任意時區之間轉換時間)。爲了確保數據將在該數據庫中存儲有0偏移:

CREATE TABLE my_tbl (
    my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
    CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0') 
); 
test=> SET timezone = 'America/Los_Angeles'; 
SET 
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW()); 
ERROR: new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check" 
test=> SET timezone = 'UTC'; 
SET 
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW()); 
INSERT 0 1 

這不是100%完美,但它提供了一個足夠強大的抗footshooting措施,確保該數據已經轉換爲UTC。關於如何做到這一點有很多意見,但這似乎是我的經驗實踐中最好的。對於數據庫時區處理的批評在很大程度上是合理的(有很多數據庫可以處理這種極度的無能),然而PostgreSQL對時間戳和時區的處理非常棒(儘管這裏和那裏有一些「特性」)。例如,一個這樣的功能:

-- Make sure we're all working off of the same local time zone 
test=> SET timezone = 'America/Los_Angeles'; 
SET 
test=> SELECT NOW(); 
       now    
------------------------------- 
2011-05-27 15:47:58.138995-07 
(1 row) 

test=> SELECT NOW() AT TIME ZONE 'UTC'; 
      timezone   
---------------------------- 
2011-05-27 22:48:02.235541 
(1 row) 

注意AT TIME ZONE 'UTC'剝離時區信息和使用的參考(UTC)目標的框架將創建一個相對TIMESTAMP WITHOUT TIME ZONE

當從一個不完整的TIMESTAMP WITHOUT TIME ZONE轉換爲TIMESTAMP WITH TIME ZONE,缺少的時區從您的連接繼承:

test=> SET timezone = 'America/Los_Angeles'; 
SET 
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW()); 
date_part 
----------- 
     -7 
(1 row) 
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541'); 
date_part 
----------- 
     -7 
(1 row) 

-- Now change to UTC  
test=> SET timezone = 'UTC'; 
SET 
-- Create an absolute time with timezone offset: 
test=> SELECT NOW(); 
       now    
------------------------------- 
2011-05-27 22:48:40.540119+00 
(1 row) 

-- Creates a relative time in a given frame of reference (i.e. no offset) 
test=> SELECT NOW() AT TIME ZONE 'UTC'; 
      timezone   
---------------------------- 
2011-05-27 22:48:49.444446 
(1 row) 

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW()); 
date_part 
----------- 
     0 
(1 row) 

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541'); 
date_part 
----------- 
     0 
(1 row) 

底線:

  • 存儲用戶的時區爲命名標籤(例如:America/Los_Angeles),而不是與UTC的偏移量(例如:-0700
  • 對所有東西都使用UTC,除非存在令人信服的理由來存儲非易失性存儲器零點偏移
  • 對待所有非零UTC時間作爲輸入錯誤
  • 從未混合和匹配相對和絕對時間戳
  • 也是在數據庫中,如果可能的

隨機編程語言使用UTCtimezone注意:Python的datetime數據類型非常適合保持絕對時間與相對時間之間的差異(儘管起初令人沮喪,直到用像PyTZ這樣的庫來補充它)。


編輯

讓我來解釋相對之差VS絕對多一點。

絕對時間用於記錄事件。示例:「用戶123登錄」或「畢業典禮開始於2011-05-28下午2點PST」。無論您的當地時區如何,如果您可以傳送到事件發生的地方,您都可以目睹事件的發生。大多數時間數據庫中的數據是絕對的(因此應該是TIMESTAMP WITH TIME ZONE,理想情況下+0偏移量和文本標籤代表特定時區的規則 - 而不是偏移量)。

一個相對事件是從一個尚未確定的時區的角度來記錄或安排某事的時間。例如:「我們的商業大門早上8點開門,晚上9點關門」,「每週一早上7點開會,每週早餐會」,或者「每晚8點的萬聖節」。一般來說,相對時間用於事件的模板或工廠,而絕對時間幾乎用於其他所有事情。有一個罕見的例外是值得指出的,它應該說明相對時間的價值。對於未來可能發生某些事情的絕對時間可能存在不確定性的未來事件,請使用相對時間戳。下面是一個真實世界的例子:

假設它是2004年,並且您需要在2008年10月31日美國西海岸下午1點安排交貨(即America/Los_Angeles/PST8PDT)。如果您使用絕對時間使用’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE進行存儲,那麼交貨時間將在下午2點顯示,因爲美國政府通過了Energy Policy Act of 2005,這改變了夏令時的規則。在2004年交付計劃時,日期10-31-2008應該是太平洋標準時間(+8000),但從2005+年開始的時區數據庫確認10-31-2008應該是太平洋夏令時(+0700)。與時區存儲相對時間戳會導致正確的交付時間表,因爲相對時間戳不受國會不明智篡改的影響。在使用相對時間和絕對時間來調度事物之間的界限是一條模糊的線,但我的經驗法則是,對未來比3-6mo更遠的任何事情的時間安排應該使用相對時間戳(計劃=絕對計劃=相對???)。

其他/最後一種相對時間是INTERVAL。例如:「會話在用戶登錄20分鐘後超時」。 INTERVAL可以用絕對時間戳(TIMESTAMP WITH TIME ZONE)或相對時間戳(TIMESTAMP WITHOUT TIME ZONE)正確使用。 「用戶會話在成功登錄20分鐘後(login_utc + session_duration)」或「我們的早餐早餐會議只能持續60分鐘(recurring_start_time + meeting_length)」同樣正確。

最後一點混淆:DATE,TIME,TIME WITHOUT TIME ZONETIME WITH TIME ZONE都是相對數據類型。例如:'2011-05-28'::DATE代表相對日期,因爲您沒有可用於識別午夜的時區信息。同樣,'23:23:59'::TIME是相對的,因爲您不知道時區或由時間表示的DATE。即使有'23:59:59-07'::TIME WITH TIME ZONE,你也不知道DATE會是什麼。最後,DATE與時區實際上不是一個DATE,它是一個TIMESTAMP WITH TIME ZONE

test=> SET timezone = 'America/Los_Angeles'; 
SET 
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC'; 
     timezone  
--------------------- 
2011-05-11 07:00:00 
(1 row) 

test=> SET timezone = 'UTC'; 
SET 
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC'; 
     timezone  
--------------------- 
2011-05-11 00:00:00 
(1 row) 

把日期和時區數據庫中是一件好事,但它是容易得到微妙的不正確的結果。需要額外的努力才能正確完整地存儲時間信息,但這並不意味着需要額外的努力。

+2

如果您準確地告訴postgresql用戶時間戳的正確時區,postgresql將在幕後完成繁重的工作。自己轉換隻是藉口麻煩。 – 2011-05-27 23:08:31

+1

@Sean - 帶着你的檢查約束,你如何在沒有'設置時區爲'UTC''的情況下插入一個時間戳?你知道[所有時區感知日期都以UTC存儲在內部](http://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE- TIMEZONES)? – 2011-08-04 21:35:36

+2

檢查的重點是確保數據與UTC的零點偏移一起存儲。信息的排序和檢索以及非零偏移時間的比較是容易出錯的。通過實施零UTC抵消,您可以始終以零風險的方式從單一角度與數據進行交互,這種方式在所有情況下都具有可預測的行爲。如果時間戳支持時區的文本表示是實用的,那麼我對這個主題的想法就會不同。 :〜] – Sean 2011-08-10 19:48:29

6

我的選擇是選項3,因爲Postgres可以爲您重新計算相對於時區的時間戳,而其他兩個則需要您自己做。用時區存儲時間戳的額外存儲開銷實際上可以忽略不計,除非您正在談論數百萬條記錄,在這種情況下,您可能已經擁有相當豐富的存儲需求。

+14

不正確。沒有開銷... Postgres確實不會存儲時區**(順便說一句,'偏移'是正確的術語,而不是時區)。 'TIMESTAMP WITH TIME ZONE'這個名字有誤導性。這實際上意味着「插入/更新時要注意任何指定的偏移量,並使用該偏移量將日期時間調整爲UTC」。 'TIMESTAMP WITHOUT TIME ZONE'名字的意思是「忽略在插入/更新期間可能存在的任何偏移量,將日期和時間部分視爲UTC而不需要調整」。仔細閱讀[doc](http://www.postgresql.org/docs/current/static/datatype-datetime.html)。 – 2014-02-15 10:35:14

49

肖恩的答案過於複雜和誤導。

事實上,「WITH TIME ZONE」和「WITHOUT TIME ZONE」都將該值存儲爲類似unix的絕對UTC時間戳。不同之處在於時間戳的顯示方式。當「WITH時區」時,顯示的值是轉換到用戶區域的UTC存儲值。當「無時區」時,UTC存儲值被扭曲以便顯示相同的時鐘表面,而不管用戶設置了什麼區域「

」無時區「可用的唯一情況是當時鐘面值適用於任何實際區域。例如,當時間戳指示投票間何時可能關閉(即,無論個人的時區是否在20:00關閉)。

使用選擇3.始終使用「WITH時區」,除非有一個非特定的原因。

+9

David E. Wheeler是Postgres的一位主要專家,他會根據他的發帖,同意你的評估[Always Always Time with Time ZONE](http://justatheory.com/computers/databases/postgresql/use-timestamptz的.html)。 – 2014-02-15 10:28:53

+2

如果您將瀏覽器將UTC時間戳轉換爲本地時區,該怎麼辦?因此,數據庫將永遠不會進行轉換並且只包含UTC。 「沒有時區」是可以接受的嗎? – dman 2016-10-09 02:50:25

相關問題