ความแตกต่างระหว่างการประทับเวลาที่มี / ไม่มีโซนเวลาใน PostgreSQL


186

ค่าการประทับเวลาถูกจัดเก็บแตกต่างกันใน PostgreSQL เมื่อชนิดข้อมูลWITH TIME ZONEเทียบกับWITHOUT TIME ZONEหรือไม่ สามารถแสดงความแตกต่างกับกรณีทดสอบอย่างง่ายได้หรือไม่?


คำตอบ:


157

ความแตกต่างได้รับความคุ้มครองในเอกสาร PostgreSQL สำหรับวันที่ / เวลาประเภท ใช่การรักษาTIMEหรือTIMESTAMPแตกต่างระหว่างหนึ่งหรือWITH TIME ZONE WITHOUT TIME ZONEไม่ส่งผลกระทบต่อวิธีการจัดเก็บค่า มันมีผลต่อวิธีการตีความ

ผลกระทบของเขตเวลาในประเภทข้อมูลเหล่านี้ครอบคลุมเฉพาะในเอกสาร ความแตกต่างเกิดขึ้นจากสิ่งที่ระบบสามารถรู้ถึงคุณค่าอย่างสมเหตุสมผล:

  • ด้วยเขตเวลาเป็นส่วนหนึ่งของค่าค่าสามารถแสดงผลเป็นเวลาท้องถิ่นในลูกค้า

  • หากไม่มีเขตเวลาเป็นส่วนหนึ่งของค่าเขตเวลาเริ่มต้นที่ชัดเจนคือ UTC ดังนั้นจึงมีการแสดงผลสำหรับเขตเวลานั้น

พฤติกรรมนั้นแตกต่างกันไปตามปัจจัยอย่างน้อยสามประการ:

  • การตั้งค่าเขตเวลาในไคลเอนต์
  • ประเภทข้อมูล (เช่นWITH TIME ZONEหรือWITHOUT TIME ZONE) ของค่า
  • ระบุว่าค่าจะถูกระบุด้วยเขตเวลาเฉพาะหรือไม่

นี่คือตัวอย่างที่ครอบคลุมการรวมกันของปัจจัยเหล่านี้:

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
แก้ไขเฉพาะเมื่ออ้างถึงกระบวนการแทรก / ดึงค่า แต่ผู้อ่านควรเข้าใจว่าทั้งสองประเภทข้อมูลtimestamp with time zoneและtimestamp without time zoneใน Postgres ไม่ได้เก็บข้อมูลโซนเวลาจริงๆ คุณสามารถยืนยันสิ่งนี้ได้อย่างรวดเร็วที่หน้าเอกสารประเภทข้อมูล: ทั้งสองประเภทใช้จำนวนออคเต็ตเท่ากันและมีช่วงของการบันทึกค่าดังนั้นจึงไม่มีที่ว่างสำหรับการจัดเก็บข้อมูลเขตเวลา ข้อความของหน้ายืนยันสิ่งนี้ ข้อผิดพลาดบางอย่าง: "ไม่มี tz" หมายถึง "ละเว้น offset เมื่อแทรกข้อมูล" และ "with tz" หมายถึง "ใช้ offset เพื่อปรับค่าเป็น UTC"
Basil Bourque

42
ชนิดข้อมูลเป็นตัวเรียกชื่อผิดในวิธีที่สอง: พวกเขาพูดว่า "เขตเวลา" แต่จริงๆแล้วเรากำลังพูดถึงออฟเซ็ตจาก UTC / GMT จริง ๆ แล้วเขตเวลาเป็นออฟเซ็ตบวกกฎ / ประวัติเกี่ยวกับ Daylight Saving Time (DST) และความผิดปกติอื่น ๆ
โหระพา Bourque

4
ฉันอยากจะบอกว่าการชดเชยเป็นเขตเวลาบวกกับกฎสำหรับ DST คุณไม่สามารถค้นหาเขตเวลาที่ได้รับการชดเชย แต่คุณสามารถค้นพบการชดเชยที่กำหนดให้กับเขตเวลาและกฎ DST
igorsantos07

3
อ้างถึงเอกสารอย่างเป็นทางการ : วันที่และเวลาที่ทราบเขตเวลาทั้งหมดจะถูกเก็บไว้ภายใน UTC พวกเขาจะถูกแปลงเป็นเวลาท้องถิ่นในโซนที่ระบุโดยพารามิเตอร์การกำหนดค่า TimeZone ก่อนที่จะแสดงให้ลูกค้า
Guillaume Husta

2
@ igorsantos07 เขตเวลาคือชุดของกฎ / ประวัติเกี่ยวกับการเปลี่ยนแปลงเวลาและการเปลี่ยนแปลงอื่น ๆ ข้อความของคุณดูเหมือนไม่จำเป็น และคำแถลงของคุณว่า "การชดเชยคือเขตเวลาบวกกฎสำหรับ DST" นั้นผิด: การชดเชยเป็นเพียงจำนวนชั่วโมงนาทีและวินาที - ไม่มีอะไรมากไม่มีอะไรน้อย
Basil Bourque

34

ฉันพยายามอธิบายให้เข้าใจมากกว่าเอกสาร PostgreSQL ที่อ้างถึง

ทั้ง TIMESTAMPรุ่นเก็บเขตเวลา (หรือออฟเซ็ต) แม้ชื่อจะแนะนำก็ตาม ความแตกต่างคือในการตีความข้อมูลที่จัดเก็บ (และในแอปพลิเคชันที่ต้องการ) ไม่ใช่ในรูปแบบที่จัดเก็บเอง:

  • TIMESTAMP WITHOUT TIME ZONEร้านค้าวันที่เวลาท้องถิ่น (aka. วันที่ปฏิทินผนังและเวลานาฬิกาแขวน) เขตเวลาของมันไม่ได้ระบุเท่าที่ PostgreSQL สามารถบอกได้ (แม้ว่าแอปพลิเคชันของคุณอาจรู้ว่ามันคืออะไร) ดังนั้น PostgreSQL จะไม่มีการแปลงที่เกี่ยวข้องกับเขตเวลาในอินพุตหรือเอาต์พุต หากค่าถูกป้อนลงในฐานข้อมูลเป็น'2011-07-01 06:30:30'แล้วไม่มี mater ในเขตเวลาใดที่คุณแสดงในภายหลังมันจะยังคงบอกว่าปี 2011, เดือน 07, วันที่ 07, วันที่ 01, 06 ชั่วโมง, 30 นาทีและ 30 วินาที (ในบางรูปแบบ) PostgreSQL '2011-07-01 06:30:30+00'และเขตเวลาใด ๆ ที่คุณระบุในอินพุตจะถูกละเว้นดังนั้นจึง'2011-07-01 06:30:30+05'เหมือนกันกับ Just'2011-07-01 06:30:30'เป็นเช่นเดียวกับเพียงสำหรับนักพัฒนา Java: java.time.LocalDateTimeมันคล้ายกับ

  • TIMESTAMP WITH TIME ZONEเก็บจุดบนเส้นเวลา UTC ลักษณะที่ปรากฏ (จำนวนชั่วโมงนาที ฯลฯ ) ขึ้นอยู่กับเขตเวลาของคุณ แต่จะอ้างอิงทันที "กายภาพ" ทันที (เช่นช่วงเวลาของเหตุการณ์จริงทางกายภาพ) อินพุตถูกแปลงภายในเป็น UTC และเป็นวิธีการจัดเก็บ สำหรับสิ่งนั้นการชดเชยของอินพุตจะต้องเป็นที่รู้จักดังนั้นเมื่ออินพุตไม่มีออฟเซ็ตหรือโซนเวลาที่ชัดเจน (เช่นแน่นอน)'2011-07-01 06:30:30' ) จะถือว่าอยู่ในเขตเวลาปัจจุบันของเซสชัน PostgreSQL มิฉะนั้นจะใช้ออฟเซ็ตหรือโซนเวลาที่ระบุอย่างชัดเจน (เหมือนใน'2011-07-01 06:30:30+05') ผลลัพธ์จะถูกแปลงเป็นเขตเวลาปัจจุบันของเซสชัน PostgreSQL สำหรับนักพัฒนา Java: มันคล้ายกับjava.time.Instant(โดยมีความละเอียดต่ำกว่า) แต่ด้วย JDBC และ JPA 2.2 คุณควรแมปกับjava.time.OffsetDateTime( java.util.Dateหรือjava.sql.Timestamp

บางคนบอกว่าทั้งสองTIMESTAMPรูปแบบเก็บวันที่เวลา UTC ชนิดของมัน แต่มันทำให้สับสนในความคิดของฉัน TIMESTAMP WITHOUT TIME ZONEถูกจัดเก็บไว้เช่น a TIMESTAMP WITH TIME ZONEซึ่งแสดงผลด้วยเขตเวลา UTC เกิดขึ้นเพื่อให้ปีเดือนวันชั่วโมงนาทีวินาทีและ microseconds เดียวกันกับที่พวกเขาอยู่ในวันเวลาท้องถิ่น แต่มันไม่ได้หมายถึงการเป็นตัวแทนของจุดบนเส้นเวลาที่การตีความ UTC บอกว่ามันเป็นเพียงวิธีเข้ารหัสฟิลด์วันที่และเวลาท้องถิ่น (เป็นกลุ่มของจุดบนเส้นเวลาเนื่องจากเขตเวลาจริงไม่ใช่ UTC เราไม่ทราบว่ามันคืออะไร)


มีอะไรผิดปกติกับการเรียกเป็นเป็นTIMESTAMP WITH TIME ZONE Instantทั้งสองเป็นตัวแทนของจุดบนเส้นเวลาใน UTC Instantเป็นที่ต้องการในความคิดของฉันมากกว่าOffsetDateTimeที่มันเป็นตัวเองมากขึ้นเอกสาร: การTIMESTAMP WITH TIME ZONEถูกดึงเสมอจากฐานข้อมูลเป็น UTC และInstantอยู่เสมอในเวลา UTC เพื่อให้การแข่งขันเป็นธรรมชาติในขณะที่OffsetDateTimeสามารถดำเนินการชดเชยอื่น ๆ
Basil Bourque

@BasilBourque แต่น่าเสียดายที่ข้อกำหนด JDBC ปัจจุบันข้อกำหนด JPA 2.2 และเอกสารคู่มือ PostgreSQL JDBC ระบุว่าOffsetDateTimeเป็นประเภท Java ที่แมป ฉันไม่แน่ใจว่าInstanceยังคงสนับสนุนอย่างไม่เป็นทางการอยู่หรือไม่
ddekany

คำถามคุณบอกว่าอ็อฟเซ็ตใด ๆ ที่ฉันระบุในอินพุตเช่น'2011-07-01 06:30:30+00'และ'2011-07-01 06:30:30+05' ถูกละเว้น แต่ฉันสามารถทำได้insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);และมันจะแปลงมันเป็น utc อย่างถูกต้อง โดยที่ date เป็น timestamp โดยไม่มี timezone ฉันพยายามเข้าใจว่าค่าหลักของการประทับเวลาด้วยเขตเวลาคืออะไรและมีปัญหา
pk1m

@ pk1m ::timestamptzคุณยุ่งเรื่องกับ เมื่อคุณแปลงสตริงเป็นTIMESTAMP WITH TIME ZONEและเมื่อนั้นจะถูกแปลงเป็นเพิ่มเติมWITHOUT TIME ZONEจะเก็บวัน "ปฏิทินผนัง" และเวลานาฬิกาแขวนของทันทีที่เห็นจากโซนเวลาเซสชันของคุณ (ซึ่งอาจเป็น UTC) มันจะยังคงเป็นเพียงการประทับเวลาในท้องถิ่นที่มีการชดเชยที่ไม่ได้ระบุ (ไม่มีโซน)
ddekany

ฉันกำลังทำงานกับหลามและนั่นคือสิ่งที่ได้รับการแทรกเมื่อแทรกอ๊อบเจคไทม์ไทม์ที่ทราบเวลา ดูเหมือนว่าฉันมีค่าในการใช้การประทับเวลากับเขตเวลา แต่ไม่จำเป็นต้องจัดการกับเขตเวลา
pk1m

12

นี่คือตัวอย่างที่ควรช่วย หากคุณมีการประทับเวลาด้วยเขตเวลาคุณสามารถแปลงการประทับเวลานั้นเป็นเขตเวลาอื่น หากคุณไม่มีเขตเวลาพื้นฐานมันจะไม่ถูกแปลงอย่างถูกต้อง

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

เอาท์พุท:

-[ 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
คำสั่ง "จะไม่ถูกแปลงอย่างถูกต้อง"นั้นไม่เป็นความจริง คุณต้องเข้าใจสิ่งที่timestampและtimestamptzหมายถึง timestamptzหมายถึงจุดสัมบูรณ์ในเวลา (UTC) โดยที่timestampแสดงถึงสิ่งที่นาฬิกาแสดงในเขตเวลาที่แน่นอน ดังนั้นเมื่อแปลงtimestamptzเป็นเขตเวลาคุณจะถามว่านาฬิกาแสดงอะไรในนิวยอร์ก ณ เวลาที่แน่นอนนี้ ในขณะที่เมื่อ "แปลง" timestampคุณกำลังถามว่าอะไรคือจุดที่แน่นอนในเวลาที่นาฬิกาในนิวยอร์กแสดง x
fphilipe

AT TIME ZONEปลูกสร้างนี้เป็นเกมพัฒนาสมองของตัวเองแม้ว่าคุณจะเข้าใจประเภทWITHvs. WITHOUT TIME ZONEดังนั้นจึงเป็นทางเลือกที่อยากรู้อยากเห็นสำหรับการอธิบายพวกเขา (: ( AT TIME ZONEแปลงการWITH TIME ZONEประทับเวลาเป็นการWITHOUT TIME ZONEประทับเวลาและในทางกลับกัน ... ไม่ชัดเจน)
ddekany

now()::timestamp AT TIME ZONE 'CST'ไม่เข้าท่าเว้นแต่คุณจะรู้ว่านาฬิกา CST สำหรับโซน 'CST' จะแสดงเวลาที่นาฬิกาท้องถิ่นของคุณกำลังแสดงอยู่หรือไม่
Jasen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.