Jenis cap waktu mana yang harus saya pilih di database PostgreSQL?

119

Saya ingin mendefinisikan praktik terbaik untuk menyimpan cap waktu di database Postgres saya dalam konteks proyek multi-zona waktu.

saya bisa

  1. pilih TIMESTAMP WITHOUT TIME ZONEdan ingat zona waktu mana yang digunakan pada waktu penyisipan untuk bidang ini
  2. pilih TIMESTAMP WITHOUT TIME ZONEdan tambahkan bidang lain yang akan berisi nama zona waktu yang digunakan pada waktu penyisipan
  3. pilih TIMESTAMP WITH TIME ZONEdan masukkan cap waktu yang sesuai

Saya memiliki sedikit preferensi untuk opsi 3 (stempel waktu dengan zona waktu) tetapi ingin memiliki pendapat yang berpendidikan tentang masalah tersebut.

Jerome WAGNER
sumber

Jawaban:

142

Pertama, penanganan waktu dan aritmatika PostgreSQL sangat fantastis dan Opsi 3 baik-baik saja dalam kasus umum. Namun demikian, ini merupakan pandangan yang tidak lengkap tentang waktu dan zona waktu dan dapat ditambahkan:

  1. Simpan nama zona waktu pengguna sebagai preferensi pengguna (misalnya America/Los_Angeles, bukan -0700).
  2. Minta data peristiwa / waktu pengguna dikirimkan secara lokal ke kerangka acuan mereka (kemungkinan besar merupakan penyeimbangan dari UTC, seperti -0700).
  3. Dalam aplikasi, ubah waktu menjadi UTCdan simpan menggunakan TIMESTAMP WITH TIME ZONEkolom.
  4. Permintaan waktu kembali lokal ke zona waktu pengguna (yaitu, ubah dari UTCke America/Los_Angeles).
  5. Setel database Anda timezoneke UTC.

Opsi ini tidak selalu berfungsi karena mungkin sulit untuk mendapatkan zona waktu pengguna dan karenanya merupakan saran lindung nilai yang digunakan TIMESTAMP WITH TIME ZONEuntuk aplikasi ringan. Karena itu, izinkan saya menjelaskan beberapa aspek latar belakang dari Opsi 4 ini secara lebih rinci.

Seperti Opsi 3, alasannya WITH TIME ZONEadalah karena waktu di mana sesuatu terjadi adalah momen mutlak dalam waktu. WITHOUT TIME ZONEmenghasilkan zona waktu relatif . Jangan pernah, pernah mencampur TIMESTAMP absolut dan relatif.

Dari perspektif programatik dan konsistensi, pastikan semua penghitungan dilakukan menggunakan UTC sebagai zona waktu. Ini bukan persyaratan PostgreSQL, tetapi membantu saat berintegrasi dengan bahasa atau lingkungan pemrograman lain. Menetapkan a CHECKpada kolom untuk memastikan penulisan ke kolom cap waktu memiliki offset zona waktu 0adalah posisi defensif yang mencegah beberapa kelas bug (misalnya skrip membuang data ke file dan sesuatu yang lain mengurutkan data waktu menggunakan semacam leksikal). Sekali lagi, PostgreSQL tidak memerlukan ini untuk melakukan penghitungan tanggal dengan benar atau untuk mengkonversi antar zona waktu (yaitu, PostgreSQL sangat mahir dalam mengubah waktu antara dua zona waktu sembarang). Untuk memastikan data yang masuk ke database disimpan dengan offset nol:

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

Ini tidak 100% sempurna, tetapi menyediakan ukuran anti-footshooting yang cukup kuat yang memastikan data sudah diubah ke UTC. Ada banyak pendapat tentang cara melakukan ini, tetapi ini tampaknya yang terbaik dalam praktik dari pengalaman saya.

Kritik terhadap penanganan zona waktu database sebagian besar dibenarkan (ada banyak database yang menangani ini dengan sangat tidak kompeten), namun penanganan cap waktu dan zona waktu PostgreSQL cukup mengagumkan (meskipun ada beberapa "fitur" di sana-sini). Misalnya, salah satu fitur tersebut:

-- 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)

Perhatikan bahwa AT TIME ZONE 'UTC'menghapus info zona waktu dan membuat kerabat TIMESTAMP WITHOUT TIME ZONEmenggunakan kerangka acuan ( UTC) target Anda .

Saat mengonversi dari yang tidak lengkap TIMESTAMP WITHOUT TIME ZONEke a TIMESTAMP WITH TIME ZONE, zona waktu yang hilang diwarisi dari koneksi Anda:

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)

Garis bawah:

  • menyimpan zona waktu pengguna sebagai label bernama (mis. America/Los_Angeles) dan bukan offset dari UTC (mis. -0700)
  • gunakan UTC untuk semua hal kecuali jika ada alasan kuat untuk menyimpan offset bukan nol
  • perlakukan semua waktu UTC bukan nol sebagai kesalahan input
  • jangan pernah mencampur dan mencocokkan stempel waktu relatif dan absolut
  • juga digunakan UTCsebagai timezonedatabase jika memungkinkan

Catatan bahasa pemrograman acak: datetimeTipe data Python sangat bagus dalam menjaga perbedaan antara waktu absolut vs relatif (meskipun awalnya membuat frustrasi sampai Anda melengkapinya dengan pustaka seperti PyTZ ).


EDIT

Izinkan saya menjelaskan sedikit lebih banyak perbedaan antara relatif vs absolut.

Waktu absolut digunakan untuk merekam suatu peristiwa. Contoh: "Pengguna 123 login" atau "upacara wisuda dimulai pada 2011-05-28 2pm PST." Terlepas dari zona waktu lokal Anda, jika Anda dapat berteleportasi ke tempat terjadinya peristiwa, Anda dapat menyaksikan peristiwa tersebut terjadi. Sebagian besar data waktu dalam database bersifat absolut (dan karena itu TIMESTAMP WITH TIME ZONE, idealnya dengan offset +0 dan label tekstual yang mewakili aturan yang mengatur zona waktu tertentu - bukan offset).

Peristiwa relatif akan mencatat atau menjadwalkan waktu sesuatu dari perspektif zona waktu yang belum ditentukan. Contoh: "bisnis kita buka jam 8 pagi dan tutup jam 9 malam", "ayo kita bertemu setiap hari Senin jam 7 pagi untuk rapat sarapan mingguan," atau "setiap Halloween jam 8 malam". Secara umum, waktu relatif digunakan di templat atau pabrik untuk acara, dan waktu absolut digunakan untuk hampir semua hal lainnya. Ada satu pengecualian langka yang patut ditunjukkan yang seharusnya menggambarkan nilai waktu relatif. Untuk peristiwa di masa mendatang yang cukup jauh di masa mendatang yang mungkin memiliki ketidakpastian tentang waktu absolut saat sesuatu dapat terjadi, gunakan stempel waktu relatif. Inilah contoh dunia nyata:

Misalkan ini tahun 2004 dan Anda perlu menjadwalkan pengiriman pada 31 Oktober 2008 jam 1 siang di Pantai Barat AS (mis. America/Los_Angeles/ PST8PDT). Jika Anda menyimpannya menggunakan penggunaan waktu absolut ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE, pengiriman akan muncul pada pukul 2 siang karena Pemerintah AS mengeluarkan Undang-Undang Kebijakan Energi tahun 2005 yang mengubah aturan yang mengatur waktu musim panas. Pada tahun 2004 ketika pengiriman dijadwalkan, tanggalnya 10-31-2008adalah Waktu Standar Pasifik ( +8000), tetapi mulai tahun 2005+ basis data zona waktu dikenali bahwa itu 10-31-2008akan menjadi waktu Musim Panas Pasifik (+0700). Menyimpan stempel waktu relatif dengan zona waktu akan menghasilkan jadwal pengiriman yang benar karena stempel waktu relatif kebal terhadap gangguan informasi Kongres yang tidak tepat. Di mana batas antara menggunakan waktu relatif vs waktu absolut untuk hal-hal penjadwalan, adalah garis fuzzy, tetapi aturan praktis saya adalah bahwa penjadwalan untuk apa pun di masa depan lebih dari 3-6 bulan harus menggunakan cap waktu relatif (dijadwalkan = absolut vs direncanakan = relatif ???).

Jenis waktu relatif lainnya / terakhir adalah INTERVAL. Contoh: "sesi akan berakhir 20 menit setelah pengguna login". Sebuah INTERVALdapat digunakan dengan benar baik dengan cap waktu absolut ( TIMESTAMP WITH TIME ZONE) atau cap waktu relatif ( TIMESTAMP WITHOUT TIME ZONE). Sama benarnya dengan mengatakan, "sesi pengguna kedaluwarsa 20 menit setelah login berhasil (login_utc + session_duration)" atau "pertemuan sarapan pagi kita hanya dapat berlangsung 60 menit (recurring_start_time + meeting_length)".

Bit terakhir kebingungan: DATE, TIME, TIME WITHOUT TIME ZONEdan TIME WITH TIME ZONEsemua jenis data yang relatif. Misalnya: '2011-05-28'::DATEmewakili tanggal relatif karena Anda tidak memiliki informasi zona waktu yang dapat digunakan untuk mengidentifikasi tengah malam. Demikian pula, '23:23:59'::TIMEbersifat relatif karena Anda tidak mengetahui zona waktu atau yang DATEdiwakili oleh waktu. Bahkan dengan '23:59:59-07'::TIME WITH TIME ZONE, Anda tidak tahu apa yang DATEakan terjadi. Dan terakhir, DATEdengan zona waktu bukan sebenarnya a DATE, itu adalah 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)

Menempatkan tanggal dan zona waktu dalam database adalah hal yang baik, tetapi mudah untuk mendapatkan hasil yang tidak benar. Upaya tambahan minimal diperlukan untuk menyimpan informasi waktu dengan benar dan lengkap, namun itu tidak berarti upaya ekstra selalu diperlukan.

Sean
sumber
2
Jika Anda secara akurat memberi tahu postgresql zona waktu yang benar dari stempel waktu pengguna, postgresql akan melakukan pekerjaan berat di belakang layar. Mengubahnya sendiri hanyalah meminjam masalah.
Seth Robertson
1
@ Sean - dengan batasan cek Anda, bagaimana Anda pernah memasukkan stempel waktu tanpa set timezone to 'UTC'? Anda tahu bahwa semua tanggal yang sadar zona waktu disimpan secara internal di UTC ?
2
Inti dari pemeriksaan ini adalah untuk memastikan bahwa data disimpan dengan offset nol dari UTC. Pengurutan dan pengambilan informasi dan perbandingan waktu dengan offset bukan-nol rawan kesalahan. Dengan menerapkan pengimbangan UTC nol, Anda dapat secara konsisten berinteraksi dengan data dari satu perspektif dengan cara yang hampir tidak berisiko yang berperilaku seperti yang dapat diprediksi di semua skenario. Jika stempel waktu dapat digunakan untuk mendukung representasi tekstual zona waktu, pemikiran saya tentang subjek tersebut akan berbeda. : ~]
Sean
6
@Sean: Tapi, seperti yang ditunjukkan Jack, semua stempel waktu yang sadar zona waktu pada dasarnya disimpan secara internal di UTC dan diubah ke zona waktu lokal Anda saat digunakan; efektif, ekstrak (zona waktu dari ...) kemudian akan selalu mengembalikan apa pun zona waktu lokal koneksi: tidak ada kaitannya dengan bagaimana stempel waktu "disimpan". Dengan kata lain, zona waktu sama sekali bukan bagian dari jenis, dan tidak dapat disimpan: "dengan zona waktu" hanyalah properti tentang bagaimana data akan dikonversi saat berinteraksi dengan jenis lain. Dengan demikian, data tidak memiliki representasi zona waktu sama sekali, tekstual atau lainnya.
Jay Freeman -saurik-
@ JayFreeman-saurik-: Anda benar sekali. The '' CHECK () '' ada sebagai tindakan anti-footshooting untuk melindungi dari kode yang mungkin cerdik. Memastikan bahwa data dalam UTC saat menulis memberikan jaminan sederhana bahwa kode telah dipikirkan atau lingkungan eksekusi disiapkan dengan benar.
Sean
59

Jawaban Sean terlalu rumit dan menyesatkan.

Faktanya adalah bahwa "DENGAN ZONA WAKTU" dan "TANPA ZONA WAKTU" menyimpan nilai sebagai stempel waktu UTC absolut seperti unix. Perbedaannya terletak pada bagaimana stempel waktu ditampilkan. Jika "DENGAN zona waktu", nilai yang ditampilkan adalah nilai UTC yang disimpan yang diterjemahkan ke zona pengguna. Ketika "TANPA zona waktu", nilai UTC yang disimpan diputar untuk menampilkan tampilan jam yang sama, apa pun zona yang telah ditetapkan pengguna ".

Satu-satunya situasi di mana "TANPA zona waktu" dapat digunakan adalah ketika nilai tampilan jam dapat diterapkan terlepas dari zona sebenarnya. Misalnya, ketika stempel waktu menunjukkan kapan bilik suara akan ditutup (misalnya, tutup pada pukul 20:00 terlepas dari zona waktu seseorang).

Gunakan pilihan 3. Selalu gunakan "DENGAN zona waktu" kecuali ada alasan yang sangat spesifik untuk tidak melakukannya.

Jay
sumber
10
David E. Wheeler, seorang ahli Postgres utama, akan setuju dengan penilaian Anda menurut postingannya, Selalu Gunakan TIMESTAMP DENGAN ZONA WAKTU .
Basil Bourque
2
Bagaimana jika Anda ingin browser mengonversi cap waktu UTC ke zona waktu lokal? Jadi, db tidak akan pernah melakukan konversi dan hanya berisi UTC. Apakah "TANPA zona waktu" dapat diterima?
dman
5

Preferensi saya adalah ke opsi 3, karena Postgres kemudian dapat melakukan banyak pekerjaan menghitung ulang cap waktu relatif terhadap zona waktu untuk Anda, sedangkan dengan dua lainnya Anda harus melakukannya sendiri. Overhead penyimpanan ekstra untuk menyimpan stempel waktu dengan zona waktu benar-benar dapat diabaikan kecuali jika Anda membicarakan jutaan catatan, dalam hal ini Anda mungkin sudah memiliki persyaratan penyimpanan yang cukup besar.

GordonM
sumber
19
Salah. Tidak ada biaya tambahan ... Postgres tidak menyimpan zona waktu ('offset' adalah istilah yang benar, bukan zona waktu). The TIMESTAMP WITH TIME ZONENama menyesatkan. Ini benar-benar berarti "perhatikan offset tertentu saat memasukkan / memperbarui dan gunakan offset tersebut untuk menyesuaikan tanggal-waktu ke UTC". The TIMESTAMP WITHOUT TIME ZONEnamanya berarti "mengabaikan offset yang mungkin hadir saat insert / update, mempertimbangkan tanggal dan waktu bagian sebagai dalam UTC dengan tidak perlu penyesuaian". Baca dokumennya dengan cermat.
Basil Bourque
1
@BasilBourque terima kasih atas informasi ini. Sangat berguna. Bagi orang lain yang membaca ini, baris dari dokumen tersebut mengatakan, "Dalam literal yang telah ditentukan sebagai stempel waktu tanpa zona waktu, PostgreSQL akan diam-diam mengabaikan indikasi zona waktu. Artinya, nilai yang dihasilkan berasal dari bidang tanggal / waktu di nilai masukan, dan tidak disesuaikan dengan zona waktu. "
Aidan Rosswood