Sự khác biệt giữa dấu thời gian có / không có múi giờ trong PostgreSQL


Câu trả lời:


155

Sự khác biệt được đề cập trong tài liệu PostgreSQL cho các loại ngày / giờ . Có, việc điều trị TIMEhoặc TIMESTAMPkhác nhau giữa một WITH TIME ZONEhoặc WITHOUT TIME ZONE. Nó không ảnh hưởng đến cách các giá trị được lưu trữ; nó ảnh hưởng đến cách họ được giải thích.

Ảnh hưởng của múi giờ đối với các loại dữ liệu này được đề cập cụ thể trong các tài liệu. Sự khác biệt phát sinh từ những gì hệ thống có thể biết một cách hợp lý về giá trị:

  • Với múi giờ là một phần của giá trị, giá trị có thể được hiển thị dưới dạng thời gian cục bộ trong máy khách.

  • Không có múi giờ là một phần của giá trị, múi giờ mặc định rõ ràng là UTC, do đó, nó được hiển thị cho múi giờ đó.

Hành vi khác nhau tùy thuộc vào ít nhất ba yếu tố:

  • Cài đặt múi giờ trong máy khách.
  • Kiểu dữ liệu (tức là WITH TIME ZONEhoặc WITHOUT TIME ZONE) của giá trị.
  • Liệu giá trị được chỉ định với một múi giờ cụ thể.

Dưới đây là các ví dụ bao gồm sự kết hợp của các yếu tố đó:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)

88
Chỉ đúng nếu đề cập đến quá trình chèn / truy xuất giá trị. Nhưng độc giả nên hiểu rằng cả hai loại dữ liệu timestamp with time zonetimestamp without time zone, trong Postgres không * thực sự lưu trữ thông tin múi giờ. Bạn có thể xác nhận điều này bằng cách lướt qua trang tài liệu loại dữ liệu: Cả hai loại đều chiếm cùng số bát phân và có phạm vi lưu giá trị, do đó không có chỗ để lưu trữ thông tin múi giờ. Văn bản của trang xác nhận điều này. Một cái gì đó của một cách hiểu sai: "không có tz" có nghĩa là "bỏ qua phần bù khi chèn dữ liệu" và "với tz" có nghĩa là "sử dụng phần bù để điều chỉnh theo UTC".
Basil Bourque

41
Các loại dữ liệu là một cách gọi sai theo cách thứ hai: Họ nói "múi giờ" nhưng thực sự chúng ta đang nói về sự bù đắp từ UTC / GMT. Múi giờ thực sự là một phần bù cộng với các quy tắc / lịch sử về Giờ tiết kiệm ánh sáng ban ngày (DST) và các bất thường khác.
Basil Bourque

4
Tôi muốn nói rằng phần bù là múi giờ cộng với các quy tắc cho DST. Bạn không thể khám phá múi giờ được cung cấp một phần bù, nhưng bạn có thể khám phá phần bù cho các quy tắc DST và múi giờ.
igorsantos07

3
Trích dẫn tài liệu chính thức : Tất cả các ngày và giờ nhận biết múi giờ được lưu trữ nội bộ trong UTC. Chúng được chuyển đổi thành giờ địa phương trong vùng được chỉ định bởi tham số cấu hình TimeZone trước khi được hiển thị cho máy khách.
Guillaume Husta

2
@ igorsantos07 Múi giờ tập hợp các quy tắc / lịch sử về các thay đổi DST và các thay đổi khác. Từ ngữ của bạn có vẻ thừa. Và tuyên bố của bạn rằng "phần bù là múi giờ cộng với quy tắc cho DST" đơn giản là sai: phần bù chỉ là một số giờ, phút và giây - không hơn, không kém.
Basil Bourque

34

Tôi cố gắng giải thích nó dễ hiểu hơn tài liệu PostgreSQL được giới thiệu.

Cả hai TIMESTAMPbiến thể đều không lưu trữ múi giờ (hoặc phần bù), bất chấp những gì tên gợi ý. Sự khác biệt là trong việc giải thích dữ liệu được lưu trữ (và trong ứng dụng dự định), chứ không phải ở định dạng lưu trữ:

  • TIMESTAMP WITHOUT TIME ZONElưu trữ ngày giờ địa phương (còn gọi là ngày lịch treo tường và giờ đồng hồ treo tường). Múi giờ của nó là không xác định theo như PostgreSQL có thể nói (mặc dù ứng dụng của bạn có thể biết nó là gì). Do đó, PostgreSQL không chuyển đổi liên quan đến múi giờ trên đầu vào hoặc đầu ra. Nếu giá trị được nhập vào cơ sở dữ liệu dưới dạng '2011-07-01 06:30:30', thì không có vấn đề gì về múi giờ bạn hiển thị sau đó, nó vẫn sẽ nói năm 2011, tháng 07, ngày 01, 06 giờ, 30 phút và 30 giây (ở một số định dạng). Ngoài ra, bất kỳ phần bù hoặc múi giờ nào bạn chỉ định trong đầu vào đều bị PostgreSQL bỏ qua, do đó '2011-07-01 06:30:30+00''2011-07-01 06:30:30+05'giống như chỉ '2011-07-01 06:30:30'. Đối với các nhà phát triển Java: nó tương tự như java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONElưu trữ một điểm trên dòng thời gian UTC. Nó trông như thế nào (bao nhiêu giờ, phút, v.v.) phụ thuộc vào múi giờ của bạn, nhưng nó luôn đề cập đến cùng một "vật lý" tức thời (như khoảnh khắc của một sự kiện vật lý thực tế). Đầu vào được chuyển đổi nội bộ sang UTC và đó là cách nó được lưu trữ. Do đó, phần bù của đầu vào phải được biết, do đó, khi đầu vào không chứa phần bù hoặc múi giờ rõ ràng (như '2011-07-01 06:30:30'), nó được giả định là nằm trong múi giờ hiện tại của phiên PostgreQuery, nếu không thì sử dụng phần bù hoặc múi giờ được chỉ định rõ ràng (như trong '2011-07-01 06:30:30+05'). Đầu ra được hiển thị được chuyển đổi thành múi giờ hiện tại của phiên PostgreSQL. Đối với các nhà phát triển Java: Nó tương tự java.time.Instant(với độ phân giải thấp hơn), nhưng với JDBC và JPA 2.2, bạn phải ánh xạ nó tới java.time.OffsetDateTime(hoặc đến java.util.Datehoặcjava.sql.Timestamp tất nhiên).

Một số người nói rằng cả hai TIMESTAMPbiến thể lưu trữ ngày giờ UTC. Kiểu như vậy, nhưng theo tôi thì thật khó hiểu. TIMESTAMP WITHOUT TIME ZONEđược lưu trữ như một TIMESTAMP WITH TIME ZONE, được hiển thị với múi giờ UTC xảy ra để cung cấp cùng năm, tháng, ngày, giờ, phút, giây và micro giây khi chúng ở trong thời gian ngày địa phương. Nhưng nó không có nghĩa là đại diện cho điểm trên dòng thời gian mà cách giải thích của UTC nói, đó chỉ là cách các trường ngày giờ địa phương được mã hóa. (Đó là một số dấu chấm trên dòng thời gian, vì múi giờ thực không phải là UTC; chúng tôi không biết đó là gì.)


Không có gì sai khi lấy a TIMESTAMP WITH TIME ZONElà a Instant. Cả hai đều đại diện cho một điểm trên dòng thời gian trong UTC. InstantTheo ý kiến ​​của tôi, OffsetDateTimenó được ưu tiên hơn vì nó là tài liệu tự luận hơn: A TIMESTAMP WITH TIME ZONEluôn được lấy từ cơ sở dữ liệu dưới dạng UTC và Instantluôn luôn ở trong UTC sao cho khớp một cách tự nhiên, trong khi đó OffsetDateTimecó thể mang các giá trị khác.
Basil Bourque

@BasilBourque Thật không may, đặc tả JDBC hiện tại, đặc tả JPA 2.2 và tài liệu JDBC PostgreQuery chỉ đề cập đến OffsetDateTimenhư kiểu Java được ánh xạ. Tôi không chắc chắn nếu Instancevẫn được hỗ trợ không chính thức ở đâu đó.
ddekany

câu hỏi, bạn nói bất kỳ phần bù nào tôi chỉ định trong đầu vào như '2011-07-01 06:30:30+00''2011-07-01 06:30:30+05' bị bỏ qua nhưng tôi có thể làm được insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);và nó sẽ chuyển đổi nó thành utc chính xác. trong đó ngày là dấu thời gian không có múi giờ. Tôi đang cố gắng hiểu giá trị chính của dấu thời gian với múi giờ là gì và gặp sự cố.
pk1m

@ pk1m Bạn phức tạp hóa vấn đề với ::timestamptz. Cùng với đó, bạn chuyển đổi chuỗi thành TIMESTAMP WITH TIME ZONEvà khi đó sẽ được chuyển đổi thành chuỗi WITHOUT TIME ZONEđó, sẽ lưu trữ ngày "lịch treo tường" và thời gian đồng hồ treo tường của thời gian đó ngay từ múi giờ phiên của bạn (có thể là UTC). Nó vẫn chỉ là dấu thời gian cục bộ với phần bù không xác định (không có vùng).
ddekany

Tôi đang làm việc với python và đó là những gì được chèn khi chèn một đối tượng dữ liệu nhận biết dấu thời gian. Dường như với tôi có giá trị trong việc sử dụng dấu thời gian với múi giờ, nhưng không cần thiết phải xử lý múi giờ.
pk1m

12

Dưới đây là một ví dụ cần giúp đỡ. Nếu bạn có dấu thời gian với múi giờ, bạn có thể chuyển đổi dấu thời gian đó thành bất kỳ múi giờ nào khác. Nếu bạn không có múi giờ cơ sở, nó sẽ không được chuyển đổi chính xác.

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

Đầu ra:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03

5
Câu lệnh "sẽ không được chuyển đổi chính xác" đơn giản là không đúng. Bạn phải hiểu những gì timestamptimestamptzcó nghĩa. timestamptzcó nghĩa là một điểm tuyệt đối trong thời gian (UTC) trong khi timestampbiểu thị những gì đồng hồ hiển thị trong một múi giờ nhất định. Vì vậy, khi chuyển đổi timestamptzsang múi giờ bạn đang hỏi đồng hồ đã hiển thị gì ở New York vào thời điểm tuyệt đối này? trong khi đó khi "chuyển đổi" a timestamp, bạn đang hỏi thời điểm tuyệt đối khi đồng hồ ở New York hiển thị x là gì?
fphilipe

Cấu AT TIME ZONEtrúc này là một lời trêu ghẹo não của chính nó, ngay cả khi bạn đã hiểu WITHso với WITHOUT TIME ZONEcác loại. Vì vậy, đó là một lựa chọn tò mò để giải thích chúng. (: ( AT TIME ZONEchuyển đổi WITH TIME ZONEdấu thời gian thành WITHOUT TIME ZONEdấu thời gian và ngược lại ... không chính xác rõ ràng.)
ddekany

now()::timestamp AT TIME ZONE 'CST'không có ý nghĩa gì, trừ khi bạn ngay lập tức đồng hồ cho khu vực 'CST' sẽ hiển thị thời gian mà đồng hồ địa phương của bạn hiện đang hiển thị
Jasen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.