Pertama, penanganan waktu dan aritmatika PostgreSQL fantastis dan Opsi 3 baik-baik saja dalam kasus umum. Namun, ini adalah tampilan waktu dan zona waktu yang tidak lengkap dan dapat ditambahkan:
- Menyimpan nama zona waktu pengguna sebagai preferensi pengguna (mis.
America/Los_Angeles
, bukan-0700
). - Minta data peristiwa/waktu pengguna dikirimkan secara lokal ke kerangka acuan mereka (kemungkinan besar merupakan offset dari UTC, seperti
-0700
). - Dalam aplikasi, ubah waktu menjadi
UTC
dan disimpan menggunakanTIMESTAMP WITH TIME ZONE
kolom. - Mengembalikan permintaan waktu secara lokal ke zona waktu pengguna (yaitu mengonversi dari
UTC
keAmerica/Los_Angeles
). - Setel
timezone
database Anda keUTC
.
Opsi ini tidak selalu berfungsi karena sulit untuk mendapatkan zona waktu pengguna dan karenanya saran lindung nilai untuk menggunakan TIMESTAMP WITH TIME ZONE
untuk aplikasi ringan. Karena itu, izinkan saya menjelaskan beberapa aspek latar belakang Opsi 4 ini secara lebih rinci.
Seperti Opsi 3, alasan WITH TIME ZONE
adalah karena waktu terjadinya sesuatu adalah mutlak saat dalam waktu. WITHOUT TIME ZONE
menghasilkan kerabat zona waktu. Jangan pernah mencampur TIMESTAMP absolut dan relatif.
Dari perspektif program dan konsistensi, pastikan semua perhitungan dibuat menggunakan UTC sebagai zona waktu. Ini bukan persyaratan PostgreSQL, tetapi ini membantu saat berintegrasi dengan bahasa atau lingkungan pemrograman lain. Menyetel 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 pengurutan leksikal). Sekali lagi, PostgreSQL tidak memerlukan ini untuk melakukan perhitungan tanggal dengan benar atau untuk mengonversi antar zona waktu (yaitu PostgreSQL sangat mahir dalam mengonversi waktu antara dua zona waktu arbitrer). 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 memberikan ukuran anti-footshooting yang cukup kuat yang memastikan data sudah dikonversi ke UTC. Ada banyak pendapat tentang cara melakukan ini, tetapi tampaknya ini praktik terbaik dari pengalaman saya.
Kritik terhadap penanganan zona waktu basis data sebagian besar dibenarkan (ada banyak basis data 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 TIMESTAMP WITHOUT TIME ZONE
menggunakan kerangka acuan target Anda (UTC
).
Saat mengonversi dari TIMESTAMP WITHOUT TIME ZONE
ke 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)
Intinya:
- menyimpan zona waktu pengguna sebagai label bernama (mis.
America/Los_Angeles
) dan bukan offset dari UTC (mis.-0700
) - gunakan UTC untuk semuanya kecuali ada alasan kuat untuk menyimpan offset bukan nol
- perlakukan semua waktu UTC bukan nol sebagai kesalahan masukan
- jangan pernah mencampur dan mencocokkan stempel waktu relatif dan absolut
- gunakan juga
UTC
sebagaitimezone
dalam database jika memungkinkan
Catatan bahasa pemrograman acak:datetime
Python Python tipe data sangat baik dalam menjaga perbedaan antara waktu absolut vs waktu relatif (walaupun awalnya membuat frustrasi sampai Anda melengkapinya dengan pustaka seperti PyTZ).
EDIT
Mari saya jelaskan sedikit lebih banyak perbedaan antara relatif vs absolut.
Waktu absolut digunakan untuk merekam suatu peristiwa. Contoh:"Pengguna 123 masuk" atau "upacara kelulusan dimulai pada 28-05-2011 14:00 PST." Terlepas dari zona waktu lokal Anda, jika Anda dapat berteleportasi ke tempat peristiwa itu terjadi, Anda dapat menyaksikan peristiwa itu terjadi. Sebagian besar data waktu dalam database adalah mutlak (dan karenanya harus TIMESTAMP WITH TIME ZONE
, idealnya dengan offset +0 dan label tekstual yang mewakili aturan yang mengatur zona waktu tertentu - bukan offset).
Peristiwa relatif adalah merekam atau menjadwalkan waktu sesuatu dari perspektif zona waktu yang belum ditentukan. Contoh:"pintu bisnis kita buka jam 8 pagi dan tutup jam 9 malam", "mari kita bertemu setiap hari Senin jam 7 pagi untuk rapat sarapan mingguan", atau "setiap Halloween jam 8 malam". Secara umum, waktu relatif digunakan dalam 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 masa depan yang cukup jauh di masa depan di mana mungkin ada ketidakpastian tentang waktu absolut di mana sesuatu dapat terjadi, gunakan stempel waktu relatif. Berikut ini contoh dunia nyata:
Misalkan ini tahun 2004 dan Anda perlu menjadwalkan pengiriman pada tanggal 31 Oktober 2008 pukul 1 siang di Pantai Barat AS (yaitu America/Los_Angeles
/PST8PDT
). Jika Anda menyimpannya menggunakan waktu absolut menggunakan ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
, pengiriman akan muncul pada jam 2 siang karena Pemerintah AS mengesahkan Undang-Undang Kebijakan Energi tahun 2005 yang mengubah aturan yang mengatur waktu musim panas. Pada tahun 2004 ketika pengiriman dijadwalkan, tanggal 10-31-2008
akan menjadi Waktu Standar Pasifik (+8000
), tetapi mulai tahun 2005+ basis data zona waktu mengenali bahwa 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 absolut untuk menjadwalkan sesuatu adalah, adalah garis kabur, tetapi aturan praktis saya adalah bahwa penjadwalan untuk apa pun di masa depan lebih dari 3-6 bulan harus menggunakan stempel waktu relatif (dijadwalkan =absolut vs direncanakan =relatif ???).
Jenis waktu relatif lainnya/terakhir adalah INTERVAL
. Contoh:"waktu sesi akan habis 20 menit setelah pengguna login". Sebuah INTERVAL
dapat digunakan dengan benar dengan stempel waktu absolut (TIMESTAMP WITH TIME ZONE
) atau stempel waktu relatif (TIMESTAMP WITHOUT TIME ZONE
). Sama benarnya dengan mengatakan, "sesi pengguna berakhir 20 menit setelah login berhasil (login_utc + session_duration)" atau "rapat sarapan pagi kami hanya dapat berlangsung 60 menit (recurring_start_time + meeting_length)".
Kebingungan terakhir:DATE
, TIME
, TIME WITHOUT TIME ZONE
dan TIME WITH TIME ZONE
semua tipe data 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
relatif karena Anda tidak tahu zona waktu atau DATE
diwakili oleh waktu. Bahkan dengan '23:59:59-07'::TIME WITH TIME ZONE
, Anda tidak tahu tanggal DATE
akan menjadi. Dan terakhir, DATE
dengan zona waktu sebenarnya bukan DATE
, ini 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 mendapatkan hasil yang salah. Upaya tambahan minimal diperlukan untuk menyimpan informasi waktu dengan benar dan lengkap, namun itu tidak berarti upaya ekstra selalu diperlukan.