Trước hết, khả năng xử lý thời gian và số học của PostgreSQL rất tuyệt vời và Tùy chọn 3 là tốt trong trường hợp chung. Tuy nhiên, đây là một cái nhìn chưa đầy đủ về thời gian và múi giờ và có thể được bổ sung:
- Lưu tên múi giờ của người dùng dưới dạng tùy chọn của người dùng (ví dụ:
America/Los_Angeles
không phải -0700
).
- Yêu cầu dữ liệu sự kiện / thời gian của người dùng được gửi cục bộ tới hệ quy chiếu của họ (rất có thể là phần bù từ UTC, chẳng hạn như
-0700
).
- Trong ứng dụng, chuyển đổi thời gian thành
UTC
và được lưu trữ bằng cách sử dụng một TIMESTAMP WITH TIME ZONE
cột.
- Trả lại yêu cầu thời gian cục bộ cho múi giờ của người dùng (tức là chuyển đổi từ
UTC
sang America/Los_Angeles
).
- Đặt cơ sở dữ liệu của bạn
timezone
thành UTC
.
Tùy chọn này không phải lúc nào cũng hoạt động vì có thể khó lấy múi giờ của người dùng và do đó, lời khuyên phòng ngừa là sử dụng TIMESTAMP WITH TIME ZONE
cho các ứng dụng nhẹ. Điều đó nói rằng, hãy để tôi giải thích một số khía cạnh cơ bản của Tùy chọn 4 này chi tiết hơn.
Giống như Phương án 3, lý do WITH TIME ZONE
là vì thời điểm mà điều gì đó đã xảy ra là một thời điểm tuyệt đối về thời gian. WITHOUT TIME ZONE
mang lại múi giờ tương đối . Đừng bao giờ, đừng bao giờ kết hợp TIMESTAMP tuyệt đối và tương đối.
Từ quan điểm có lập trình và nhất quán, hãy đảm bảo tất cả các tính toán được thực hiện bằng cách sử dụng UTC làm múi giờ. Đây không phải là yêu cầu của PostgreSQL, nhưng nó giúp ích khi tích hợp với các ngôn ngữ hoặc môi trường lập trình khác. Đặt một CHECK
trên cột để đảm bảo ghi vào cột tem thời gian có độ lệch múi giờ 0
là một vị trí phòng thủ ngăn chặn một số loại lỗi (ví dụ: một tập lệnh kết xuất dữ liệu vào một tệp và thứ gì đó khác sắp xếp dữ liệu thời gian bằng cách sử dụng sắp xếp từ vựng). Một lần nữa, PostgreSQL không cần điều này để thực hiện các phép tính ngày một cách chính xác hoặc chuyển đổi giữa các múi giờ (tức là PostgreSQL rất thành thạo trong việc chuyển đổi thời gian giữa hai múi giờ tùy ý bất kỳ). Để đảm bảo dữ liệu đi vào cơ sở dữ liệu được lưu trữ với độ lệch bằng 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
Nó không hoàn hảo 100%, nhưng nó cung cấp một biện pháp chống giật chân đủ mạnh để đảm bảo dữ liệu đã được chuyển đổi sang UTC. Có rất nhiều ý kiến về cách làm điều này, nhưng đây có vẻ là cách tốt nhất trong thực tế từ kinh nghiệm của tôi.
Những lời chỉ trích về việc xử lý múi giờ cơ sở dữ liệu phần lớn là có lý do (có rất nhiều cơ sở dữ liệu xử lý điều này với sự kém cỏi lớn), tuy nhiên việc xử lý dấu thời gian và múi giờ của PostgreSQL khá tuyệt vời (mặc dù có một vài "tính năng" ở đây và ở đó). Ví dụ, một trong những tính năng như vậy:
-- 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)
Lưu ý rằng AT TIME ZONE 'UTC'
phân chia thông tin múi giờ và tạo một tương đối TIMESTAMP WITHOUT TIME ZONE
bằng cách sử dụng hệ quy chiếu của mục tiêu ( UTC
).
Khi chuyển đổi từ không hoàn TIMESTAMP WITHOUT TIME ZONE
thành sang a TIMESTAMP WITH TIME ZONE
, múi giờ bị thiếu sẽ được kế thừa từ kết nối của bạn:
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)
Điểm mấu chốt:
- lưu trữ múi giờ của người dùng dưới dạng nhãn được đặt tên (ví dụ
America/Los_Angeles
) và không phải là phần bù từ UTC (ví dụ -0700
)
- sử dụng UTC cho mọi thứ trừ khi có lý do thuyết phục để lưu trữ phần bù khác 0
- coi tất cả thời gian UTC khác 0 là lỗi đầu vào
- không bao giờ trộn và kết hợp các dấu thời gian tương đối và tuyệt đối
- cũng sử dụng
UTC
làm timezone
cơ sở dữ liệu nếu có thể
Lưu ý về ngôn ngữ lập trình ngẫu nhiên: datetime
Kiểu dữ liệu của Python rất tốt trong việc duy trì sự khác biệt giữa thời gian tuyệt đối và thời gian tương đối (mặc dù lúc đầu khá khó chịu cho đến khi bạn bổ sung nó bằng một thư viện như PyTZ ).
BIÊN TẬP
Hãy để tôi giải thích sự khác biệt giữa tương đối và tuyệt đối hơn một chút.
Thời gian tuyệt đối được sử dụng để ghi lại một sự kiện. Ví dụ: "Người dùng 123 đã đăng nhập" hoặc "lễ tốt nghiệp bắt đầu lúc 2 giờ chiều theo giờ Thái Bình Dương, 2011-05-28." Bất kể múi giờ địa phương của bạn là gì, nếu bạn có thể dịch chuyển đến nơi xảy ra sự kiện, bạn có thể chứng kiến sự kiện đang diễn ra. Hầu hết dữ liệu thời gian trong cơ sở dữ liệu là tuyệt đối (và do đó TIMESTAMP WITH TIME ZONE
, lý tưởng là phải có độ lệch +0 và nhãn văn bản đại diện cho các quy tắc quản lý múi giờ cụ thể - không phải là độ lệch).
Một sự kiện tương đối sẽ là ghi lại hoặc lên lịch thời gian của một thứ gì đó từ góc độ múi giờ chưa được xác định. Ví dụ: "cửa hàng kinh doanh của chúng tôi mở lúc 8 giờ sáng và đóng lúc 9 giờ tối", "chúng ta hãy gặp nhau lúc 7 giờ sáng vào thứ Hai hàng tuần cho một cuộc họp ăn sáng hàng tuần" hoặc "mỗi Halloween lúc 8 giờ tối". Nói chung, thời gian tương đối được sử dụng trong một mẫu hoặc nhà máy cho các sự kiện và thời gian tuyệt đối được sử dụng cho hầu hết mọi thứ khác. Có một ngoại lệ hiếm hoi đáng chỉ ra sẽ minh họa giá trị của thời gian tương đối. Đối với các sự kiện tương lai đủ xa trong tương lai, nơi có thể không chắc chắn về thời gian tuyệt đối mà tại đó điều gì đó có thể xảy ra, hãy sử dụng dấu thời gian tương đối. Đây là một ví dụ thực tế:
Giả sử đó là năm 2004 và bạn cần lên lịch giao hàng vào lúc 1 giờ chiều ngày 31 tháng 10 năm 2008 ở Bờ Tây Hoa Kỳ (tức là America/Los_Angeles
/ PST8PDT
). Nếu bạn lưu trữ nó bằng cách sử dụng thời gian tuyệt đối ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
, giao hàng sẽ hiển thị lúc 2 giờ chiều vì Chính phủ Hoa Kỳ đã thông qua Đạo luật Chính sách Năng lượng năm 2005 đã thay đổi các quy tắc điều chỉnh thời gian tiết kiệm ánh sáng ban ngày. Vào năm 2004 khi giao hàng được lên lịch, ngày 10-31-2008
sẽ là Giờ chuẩn Thái Bình Dương ( +8000
), nhưng bắt đầu từ năm 2005+ cơ sở dữ liệu múi giờ được công nhận là giờ 10-31-2008
Tiết kiệm ánh sáng ban ngày Thái Bình Dương (+0700
). Lưu trữ một dấu thời gian tương đối với múi giờ sẽ dẫn đến lịch trình phân phối chính xác vì dấu thời gian tương đối miễn nhiễm với sự giả mạo thiếu thông tin của Quốc hội. Trường hợp giới hạn giữa việc sử dụng thời gian tương đối và thời gian tuyệt đối để lập lịch trình là một đường mờ, nhưng quy tắc chung của tôi là lập lịch cho bất kỳ thứ gì trong tương lai xa hơn 3-6 tháng nên sử dụng dấu thời gian tương đối (đã lên lịch = tuyệt đối so với kế hoạch = quan hệ ???).
Loại thời gian tương đối khác / cuối cùng là INTERVAL
. Ví dụ: "phiên sẽ hết 20 phút sau khi người dùng đăng nhập". An INTERVAL
có thể được sử dụng chính xác với dấu thời gian tuyệt đối ( TIMESTAMP WITH TIME ZONE
) hoặc dấu thời gian tương đối ( TIMESTAMP WITHOUT TIME ZONE
). Cũng đúng như nhau khi nói, "phiên người dùng hết hạn sau 20 phút sau khi đăng nhập thành công (login_utc + session_duration)" hoặc "cuộc họp bữa sáng buổi sáng của chúng tôi chỉ có thể kéo dài 60 phút (recring_start_time + meeting_length)".
Bit cuối cùng của sự nhầm lẫn: DATE
, TIME
, TIME WITHOUT TIME ZONE
và TIME WITH TIME ZONE
là tất cả các loại dữ liệu tương đối. Ví dụ: '2011-05-28'::DATE
đại diện cho một ngày tương đối vì bạn không có thông tin múi giờ có thể được sử dụng để xác định nửa đêm. Tương tự, '23:23:59'::TIME
là tương đối vì bạn không biết múi giờ hoặc DATE
thời gian được biểu thị. Ngay cả với '23:59:59-07'::TIME WITH TIME ZONE
, bạn không biết những gì DATE
sẽ là. Và cuối cùng, DATE
với múi giờ trên thực tế không phải là a DATE
, nó là 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)
Đưa ngày và múi giờ vào cơ sở dữ liệu là một điều tốt, nhưng rất dễ nhận được kết quả không chính xác một cách tinh vi. Cần nỗ lực tối thiểu để lưu trữ thông tin thời gian một cách chính xác và đầy đủ, tuy nhiên điều đó không có nghĩa là luôn cần nỗ lực thêm.