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 trữ 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 thời gian / sự kiện 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ằngTIMESTAMP WITH TIME ZONE
cột. - Trả lại các 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
đếnAmerica/Los_Angeles
). - Đặt
timezone
cho cơ sở dữ liệu của bạn sangUTC
.
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ư Tùy chọn 3, lý do cho WITH TIME ZONE
là bởi vì thời điểm mà điều gì đó đã xảy ra là tuyệt đối thời khắc. WITHOUT TIME ZONE
mang lại họ hàng Múi 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ó hữu í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 CHECK
trên cột để đảm bảo ghi vào cột tem thời gian có độ lệch múi giờ là 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à một thứ khác sắp xếp dữ liệu thời gian bằng cách 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 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ủa 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 rất kém năng lực), 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'
tách thông tin múi giờ và tạo một TIMESTAMP WITHOUT TIME ZONE
tương đối sử dụng hệ quy chiếu của mục tiêu (UTC
).
Khi chuyển đổi từ một TIMESTAMP WITHOUT TIME ZONE
tới TIMESTAMP WITH TIME ZONE
, múi giờ bị thiếu đượ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ữ khoảng chênh lệch 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
dưới dạngtimezone
trong cơ sở dữ liệu nếu có thể
Ghi chú ngôn ngữ lập trình ngẫu nhiên:datetime
của Python kiểu dữ liệu 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).
CHỈNH SỬA
Hãy để tôi giải thích sự khác biệt giữa tương đối và tuyệt đối một chút nữa.
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 đó phải là TIMESTAMP WITH TIME ZONE
, lý tưởng là có độ lệch +0 và nhãn văn bản đại diện cho các quy tắc điều chỉnh múi giờ cụ thể - không phải độ 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ừ quan điểm của một 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 để tổ chức buổi ă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 được 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 mà 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 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ữ dữ liệu đó bằng thời gian tuyệt đối bằng cách sử dụng ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
, việc giao hàng sẽ xuất hiện 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 quản lý thời gian tiết kiệm ánh sáng ban ngày. Vào năm 2004, khi việc 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ờ đã nhận ra rằng 10-31-2008
sẽ là giờ 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. Khoảng cách 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 =tương đối ???).
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". INTERVAL
có thể được sử dụng chính xác với một trong hai 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 sẽ 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 ta chỉ có thể kéo dài 60 phút (recring_start_time + meeting_length)".
Chút nhầm lẫn cuối cùng:DATE
, TIME
, TIME WITHOUT TIME ZONE
và TIME WITH TIME ZONE
là tất cả các kiểu 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
đại diện bởi thời gian. Ngay cả với '23:59:59-07'::TIME WITH TIME ZONE
, bạn không biết DATE
là gì sẽ được. Và cuối cùng, DATE
với múi giờ trên thực tế không phải là 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 tháng 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 bổ sung 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.