ก่อนอื่นการจัดการเวลาและเลขคณิตของ PostgreSQL นั้นยอดเยี่ยมมากและตัวเลือกที่ 3 ก็ใช้ได้ดีในกรณีทั่วไป อย่างไรก็ตามเป็นมุมมองที่ไม่สมบูรณ์ของเวลาและเขตเวลาและสามารถเสริมได้:
- จัดเก็บชื่อเขตเวลาของผู้ใช้เป็นค่ากำหนดของผู้ใช้ (เช่น
America/Los_Angeles
ไม่ใช่-0700
)
- มีการส่งข้อมูลเหตุการณ์ / เวลาของผู้ใช้ในพื้นที่ไปยังกรอบอ้างอิง (ส่วนใหญ่จะเป็นการชดเชยจาก UTC เช่น
-0700
)
- ในแอปพลิเคชันแปลงเวลา
UTC
และจัดเก็บโดยใช้TIMESTAMP WITH TIME ZONE
คอลัมน์
- ส่งกลับคำขอเวลาท้องถิ่นไปยังเขตเวลาของผู้ใช้ (เช่นแปลงจาก
UTC
เป็นAmerica/Los_Angeles
)
- ตั้งฐานข้อมูลของคุณไป
timezone
UTC
ตัวเลือกนี้ใช้ไม่ได้เสมอไปเนื่องจากอาจเป็นเรื่องยากที่จะได้รับเขตเวลาของผู้ใช้ดังนั้นคำแนะนำในการป้องกันความเสี่ยงTIMESTAMP WITH TIME ZONE
สำหรับการใช้งานที่มีน้ำหนักเบา ที่กล่าวว่าให้ฉันอธิบายลักษณะพื้นฐานบางประการของตัวเลือก 4 นี้โดยละเอียด
เช่นเดียวกับทางเลือกที่ 3 เหตุผลที่WITH TIME ZONE
เป็นเพราะเวลาที่สิ่งที่เกิดขึ้นเป็นที่แน่นอนขณะที่ในเวลาที่ WITHOUT TIME ZONE
ให้เขตเวลาสัมพัทธ์ อย่าเคยผสม TIMESTAMPs สัมบูรณ์และสัมพัทธ์
จากมุมมองแบบเป็นโปรแกรมและความสอดคล้องตรวจสอบให้แน่ใจว่าการคำนวณทั้งหมดทำโดยใช้ UTC เป็นเขตเวลา นี่ไม่ใช่ข้อกำหนดของ PostgreSQL แต่จะช่วยเมื่อรวมเข้ากับภาษาโปรแกรมหรือสภาพแวดล้อมอื่น ๆ การตั้งค่าCHECK
บนคอลัมน์เพื่อให้แน่ใจว่าการเขียนไปยังคอลัมน์การประทับเวลามีการชดเชยโซนเวลา0
เป็นตำแหน่งการป้องกันที่ป้องกันไม่ให้มีข้อบกพร่องบางคลาส (เช่นสคริปต์ทิ้งข้อมูลไปยังไฟล์และอย่างอื่นจะจัดเรียงข้อมูลเวลาโดยใช้ a เรียงศัพท์). อีกครั้ง PostgreSQL ไม่จำเป็นต้องใช้สิ่งนี้ในการคำนวณวันที่อย่างถูกต้องหรือเพื่อแปลงระหว่างโซนเวลา (เช่น PostgreSQL มีความเชี่ยวชาญในการแปลงเวลาระหว่างสองเขตเวลาใด ๆ โดยพลการ) เพื่อให้แน่ใจว่าข้อมูลที่เข้าสู่ฐานข้อมูลถูกจัดเก็บโดยมีค่าชดเชยเป็นศูนย์:
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% แต่มีมาตรการต่อต้านการเดินเท้าที่แข็งแกร่งเพียงพอที่ทำให้แน่ใจว่าข้อมูลถูกแปลงเป็น 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'
แถบข้อมูลเขตเวลาและสร้างความสัมพันธ์TIMESTAMP WITHOUT TIME ZONE
โดยใช้กรอบอ้างอิงของเป้าหมาย ( UTC
)
เมื่อแปลงจากค่าที่ไม่สมบูรณ์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 ที่ไม่ใช่ศูนย์ทั้งหมดเป็นข้อผิดพลาดในการป้อนข้อมูล
- อย่าผสมและจับคู่การประทับเวลาแบบสัมพัทธ์และแบบสัมบูรณ์
- ใช้
UTC
เป็นtimezone
ฐานข้อมูลด้วยถ้าเป็นไปได้
หมายเหตุภาษาการเขียนโปรแกรมแบบสุ่ม: datetime
ประเภทข้อมูลของ Python นั้นดีมากในการรักษาความแตกต่างระหว่างเวลาสัมบูรณ์เทียบกับเวลาสัมพัทธ์ (แม้ว่าจะน่าหงุดหงิดในตอนแรกจนกว่าคุณจะเสริมด้วยไลบรารีเช่นPyTZ )
แก้ไข
ให้ฉันอธิบายความแตกต่างระหว่างสัมพัทธ์กับสัมบูรณ์อีกเล็กน้อย
เวลาสัมบูรณ์ใช้ในการบันทึกเหตุการณ์ ตัวอย่าง: "ผู้ใช้ 123 เข้าสู่ระบบ" หรือ "พิธีจบการศึกษาเริ่มเวลา 2011-05-28 14.00 น. PST" ไม่ว่าเขตเวลาท้องถิ่นของคุณจะเป็นอย่างไรหากคุณสามารถเทเลพอร์ตไปยังที่ที่เกิดเหตุการณ์ขึ้นได้ ข้อมูลเวลาส่วนใหญ่ในฐานข้อมูลเป็นค่าสัมบูรณ์ (ดังนั้นควรTIMESTAMP WITH TIME ZONE
เป็นค่าออฟเซ็ต +0 และป้ายข้อความที่แสดงถึงกฎที่ควบคุมเขตเวลาเฉพาะ - ไม่ใช่ค่าชดเชย)
เหตุการณ์สัมพัทธ์คือการบันทึกหรือกำหนดเวลาของบางสิ่งจากมุมมองของเขตเวลาที่ยังไม่ได้กำหนด ตัวอย่าง: "ประตูธุรกิจของเราเปิดเวลา 8.00 น. และปิดเวลา 21.00 น.", "นัดพบกันทุกวันจันทร์เวลา 7.00 น. สำหรับการประชุมอาหารเช้าประจำสัปดาห์" หรือ "ทุกวันฮาโลวีนเวลา 20.00 น." โดยทั่วไปเวลาสัมพัทธ์จะใช้ในเทมเพลตหรือโรงงานสำหรับเหตุการณ์และเวลาที่แน่นอนจะใช้สำหรับเกือบทุกอย่าง มีข้อยกเว้นที่หายากอย่างหนึ่งที่ควรค่าแก่การชี้ให้เห็นซึ่งควรแสดงให้เห็นถึงคุณค่าของเวลาสัมพัทธ์ สำหรับเหตุการณ์ในอนาคตที่ไกลพอในอนาคตซึ่งอาจมีความไม่แน่นอนเกี่ยวกับเวลาที่แน่นอนที่บางสิ่งอาจเกิดขึ้นให้ใช้การประทับเวลาแบบสัมพัทธ์ นี่คือตัวอย่างโลกแห่งความจริง:
สมมติว่าเป็นปี 2004 และคุณต้องกำหนดเวลาส่งมอบในวันที่ 31 ตุลาคม 2008 เวลา 13.00 น. ทางฝั่งตะวันตกของสหรัฐอเมริกา (เช่นAmerica/Los_Angeles
/ PST8PDT
) หากคุณเก็บข้อมูลนั้นโดยใช้เวลาสัมบูรณ์’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
การจัดส่งจะปรากฏขึ้นในเวลา 14.00 น. เนื่องจากรัฐบาลสหรัฐฯผ่านพระราชบัญญัตินโยบายพลังงานปี 2548ที่เปลี่ยนแปลงกฎที่ควบคุมเวลาออมแสง ในปี 2004 เมื่อกำหนดการส่งมอบวันที่10-31-2008
จะเป็นเวลามาตรฐานแปซิฟิก ( +8000
) แต่เริ่มต้นในปี 2548 ฐานข้อมูลเขตเวลา + ที่ได้รับการยอมรับว่า10-31-2008
จะเป็นเวลาออมแสงของแปซิฟิก (+0700
) การจัดเก็บการประทับเวลาแบบสัมพัทธ์กับเขตเวลาจะส่งผลให้กำหนดการส่งมอบที่ถูกต้องเนื่องจากการประทับเวลาแบบสัมพัทธ์นั้นไม่สามารถป้องกันการปลอมแปลงโดยแจ้งให้รัฐสภาทราบได้ ในกรณีที่จุดตัดระหว่างการใช้เวลาสัมพัทธ์เทียบกับเวลาสัมบูรณ์สำหรับการกำหนดเวลาสิ่งต่างๆคือเส้นที่คลุมเครือ แต่กฎง่ายๆของฉันคือการตั้งเวลาสำหรับสิ่งใด ๆ ในอนาคตที่ไกลกว่า 3-6mo ควรใช้การประทับเวลาสัมพัทธ์ (กำหนดเวลา = สัมบูรณ์เทียบกับที่วางแผนไว้ = ญาติ ???).
เวลาสัมพัทธ์อื่น ๆ / ประเภทสุดท้ายคือINTERVAL
. ตัวอย่าง: "เซสชันจะหมดเวลา 20 นาทีหลังจากผู้ใช้เข้าสู่ระบบ" INTERVAL
สามารถนำมาใช้อย่างถูกต้องด้วยการประทับเวลาสัมบูรณ์ ( TIMESTAMP WITH TIME ZONE
) หรือ timestamps ญาติ ( TIMESTAMP WITHOUT TIME ZONE
) เป็นเรื่องที่ถูกต้องพอ ๆ กันที่จะพูดว่า "เซสชันของผู้ใช้จะหมดอายุ 20 นาทีหลังจากการเข้าสู่ระบบสำเร็จ (login_utc + session_duration)" หรือ "การประชุมอาหารเช้าในตอนเช้าของเราสามารถใช้เวลาเพียง 60 นาทีเท่านั้น (ซ้ำซากจำเจ + เวลาการประชุม)"
บิตสุดท้ายของความสับสน: DATE
, TIME
, TIME WITHOUT TIME ZONE
และTIME 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)
การใส่วันที่และเขตเวลาในฐานข้อมูลเป็นสิ่งที่ดี แต่ก็เป็นเรื่องง่ายที่จะได้ผลลัพธ์ที่ไม่ถูกต้องเล็กน้อย ต้องใช้ความพยายามเพิ่มเติมเพียงเล็กน้อยในการจัดเก็บข้อมูลเวลาอย่างถูกต้องและสมบูรณ์ แต่นั่นไม่ได้หมายความว่าต้องใช้ความพยายามเพิ่มเติมเสมอไป