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:
- Simpan nama zona waktu pengguna sebagai preferensi pengguna (misalnya
America/Los_Angeles
, bukan -0700
).
- Minta data peristiwa / waktu pengguna dikirimkan secara lokal ke kerangka acuan mereka (kemungkinan besar merupakan penyeimbangan dari UTC, seperti
-0700
).
- Dalam aplikasi, ubah waktu menjadi
UTC
dan simpan menggunakan TIMESTAMP WITH TIME ZONE
kolom.
- Permintaan waktu kembali lokal ke zona waktu pengguna (yaitu, ubah dari
UTC
ke America/Los_Angeles
).
- Setel database Anda
timezone
ke 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 ZONE
untuk aplikasi ringan. Karena itu, izinkan saya menjelaskan beberapa aspek latar belakang dari Opsi 4 ini secara lebih rinci.
Seperti Opsi 3, alasannya WITH TIME ZONE
adalah karena waktu di mana sesuatu terjadi adalah momen mutlak dalam waktu. WITHOUT TIME ZONE
menghasilkan 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 CHECK
pada kolom untuk memastikan penulisan ke kolom cap waktu memiliki offset zona waktu 0
adalah 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 ZONE
menggunakan kerangka acuan ( UTC
) target Anda .
Saat mengonversi dari yang tidak lengkap TIMESTAMP WITHOUT TIME ZONE
ke 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
UTC
sebagai timezone
database jika memungkinkan
Catatan bahasa pemrograman acak: datetime
Tipe 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-2008
adalah Waktu Standar Pasifik ( +8000
), tetapi mulai tahun 2005+ basis data zona waktu dikenali bahwa itu 10-31-2008
akan 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 INTERVAL
dapat 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 ZONE
dan TIME WITH TIME ZONE
semua jenis data yang relatif. Misalnya: '2011-05-28'::DATE
mewakili tanggal relatif karena Anda tidak memiliki informasi zona waktu yang dapat digunakan untuk mengidentifikasi tengah malam. Demikian pula, '23:23:59'::TIME
bersifat relatif karena Anda tidak mengetahui zona waktu atau yang DATE
diwakili oleh waktu. Bahkan dengan '23:59:59-07'::TIME WITH TIME ZONE
, Anda tidak tahu apa yang DATE
akan terjadi. Dan terakhir, DATE
dengan 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.