Saya menggunakan zona waktu Amerika / New York. Pada musim gugur kami "mundur" satu jam - secara efektif "mendapatkan" satu jam pada pukul 2 pagi. Pada titik transisi terjadi hal berikut:
01:59:00 -04: 00
kemudian 1 menit kemudian menjadi:
01:00:00 -05: 00
Jadi, jika Anda hanya mengatakan "1:30 pagi", tidak jelas apakah Anda mengacu pada saat pertama kali 1:30 berputar atau yang kedua. Saya mencoba menyimpan data penjadwalan ke database MySQL dan tidak dapat menentukan cara menyimpan waktu dengan benar.
Inilah masalahnya:
"2009-11-01 00:30:00" disimpan secara internal sebagai 2009-11-01 00:30:00 -04: 00
"2009-11-01 01:30:00" disimpan secara internal sebagai 2009-11-01 01:30:00 -05: 00
Ini bagus dan cukup diharapkan. Tapi bagaimana cara menyimpan apa pun hingga 01:30:00 -04: 00 ? The dokumentasi tidak menunjukkan dukungan untuk menentukan offset dan, sesuai, ketika saya sudah mencoba menentukan offset itu sudah sepatutnya diabaikan.
Satu-satunya solusi yang saya pikirkan melibatkan pengaturan server ke zona waktu yang tidak menggunakan waktu musim panas dan melakukan transformasi yang diperlukan dalam skrip saya (saya menggunakan PHP untuk ini). Tapi sepertinya itu tidak perlu.
Terima kasih banyak atas sarannya.
Jawaban:
Jenis tanggal MySQL, sejujurnya, rusak dan tidak dapat disimpan sepanjang waktu dengan benar kecuali sistem Anda disetel ke zona waktu offset konstan, seperti UTC atau GMT-5. (Saya menggunakan MySQL 5.0.45)
Ini karena Anda tidak dapat menyimpan kapan pun selama satu jam sebelum Waktu Musim Panas berakhir . Tidak peduli bagaimana Anda memasukkan tanggal, setiap fungsi tanggal akan memperlakukan waktu-waktu ini seolah-olah selama satu jam setelah sakelar.
Zona waktu sistem saya adalah
America/New_York
. Mari kita coba menyimpan 1257051600 (Sun, 01 Nov 2009 06:00:00 +0100).Ini menggunakan sintaks INTERVAL berpemilik:
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599 SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200 SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599 SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200
Bahkan
FROM_UNIXTIME()
tidak akan mengembalikan waktu yang akurat.SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599 SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200
Anehnya, DATETIME masih akan menyimpan dan mengembalikan (dalam bentuk string saja!) Kali dalam jam "hilang" ketika DST dimulai (misalnya
2009-03-08 02:59:59
). Tetapi menggunakan tanggal-tanggal ini dalam fungsi MySQL apa pun berisiko:SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599 SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600 # ... SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600 SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600
Kesimpulannya: Jika Anda perlu menyimpan dan mengambil setiap kali dalam setahun, Anda memiliki beberapa opsi yang tidak diinginkan:
Simpan tanggal sebagai INT (seperti yang ditemukan Aaron, TIMESTAMP bahkan tidak dapat diandalkan)
Anggaplah tipe DATETIME memiliki zona waktu offset yang konstan. Misalnya Jika Anda masuk
America/New_York
, ubah tanggal Anda menjadi GMT-5 di luar MySQL , lalu simpan sebagai DATETIME (ini ternyata penting: lihat jawaban Aaron). Maka Anda harus sangat berhati-hati menggunakan fungsi tanggal / waktu MySQL, karena beberapa menganggap nilai Anda dari zona waktu sistem, yang lain (fungsi aritmatika khususnya waktu) adalah "agnostik zona waktu" (mereka mungkin berperilaku seolah-olah waktunya UTC).Aaron dan saya menduga bahwa kolom TIMESTAMP yang dibuat secara otomatis juga rusak. Keduanya
2009-11-01 01:30 -0400
dan2009-11-01 01:30 -0500
akan disimpan sebagai ambigu2009-11-01 01:30
.sumber
select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;
yang mana'2009-11-01 01:00:00'
. UNIX_TIMESTAMP kemudian hanya mencoba untuk menyembunyikan ini ke UTC dalam konteks zona waktu sesi - itu tidak mencoba untuk melakukan penambahan dalam konteks aturan DST zona waktu itu.America/New_York
, saya tidak melihat cara untuk menyimpan 1257051600. Apakah Anda?Saya sudah memikirkannya untuk tujuan saya. Saya akan meringkas apa yang saya pelajari (maaf, catatan ini bertele-tele; itu sama pentingnya dengan referensi saya di masa mendatang).
Bertentangan dengan apa yang saya katakan di salah satu komentar saya sebelumnya, bidang DATETIME dan TIMESTAMP jangan berperilaku berbeda. Kolom TIMESTAMP (seperti yang ditunjukkan oleh dokumen) mengambil apa pun yang Anda kirimkan dalam format "TTTT-BB-HH jj: mm: dd" dan mengubahnya dari zona waktu Anda saat ini ke waktu UTC. Kebalikannya terjadi secara transparan setiap kali Anda mengambil data. Bidang DATETIME tidak melakukan konversi ini. Mereka mengambil apa pun yang Anda kirimkan dan langsung menyimpannya.
Baik jenis bidang DATETIME maupun TIMESTAMP tidak dapat menyimpan data secara akurat dalam zona waktu yang mengamati DST . Jika Anda menyimpan "2009-11-01 01:30:00", kolom tidak memiliki cara untuk membedakan versi jam 1:30 mana yang Anda inginkan - versi -04: 00 atau -05: 00.
Ok, jadi kita harus menyimpan data kita di zona waktu non DST (seperti UTC). Kolom TIMESTAMP tidak dapat menangani data ini secara akurat karena alasan yang akan saya jelaskan: jika sistem Anda disetel ke zona waktu DST, apa yang Anda masukkan ke TIMESTAMP mungkin bukan yang Anda dapatkan kembali. Meskipun Anda mengirimkan data yang telah Anda ubah ke UTC, data tersebut akan tetap diasumsikan dalam zona waktu lokal Anda dan melakukan konversi lagi ke UTC. Perjalanan pulang pergi lokal-ke-UTC-kembali-ke-lokal yang diberlakukan TIMESTAMP ini merugikan saat zona waktu lokal Anda mengamati DST (karena "2009-11-01 01:30:00" memetakan ke 2 kemungkinan waktu yang berbeda).
Dengan DATETIME Anda dapat menyimpan data Anda di zona waktu mana pun yang Anda inginkan dan yakin bahwa Anda akan mendapatkan kembali apa pun yang Anda kirimkan (Anda tidak akan dipaksa melakukan konversi bolak-balik yang merugikan yang ditimbulkan oleh bidang TIMESTAMP pada Anda). Jadi solusinya adalah dengan menggunakan bidang DATETIME dan sebelum menyimpan ke bidang, ubah dari zona waktu sistem Anda menjadi zona non-DST apa pun yang Anda inginkan untuk menyimpannya (menurut saya UTC mungkin adalah opsi terbaik). Ini memungkinkan Anda untuk membuat logika konversi ke dalam bahasa skrip Anda sehingga Anda dapat secara eksplisit menyimpan UTC yang setara dengan "2009-11-01 01:30:00 -04: 00" atau "" 2009-11-01 01:30: 00 -05: 00 ".
Hal penting lainnya yang perlu diperhatikan adalah bahwa fungsi matematika tanggal / waktu MySQL tidak berfungsi dengan baik di sekitar batas DST jika Anda menyimpan tanggal Anda di DST TZ. Jadi semakin banyak alasan untuk menabung di UTC.
Singkatnya, sekarang saya melakukan ini:
Saat mengambil data dari database:
Tafsirkan secara eksplisit data dari database sebagai UTC di luar MySQL untuk mendapatkan stempel waktu Unix yang akurat. Saya menggunakan fungsi strtotime () PHP atau kelas DateTime untuk ini. Ini tidak dapat dilakukan dengan andal di dalam MySQL menggunakan fungsi MySQL CONVERT_TZ () atau UNIX_TIMESTAMP () karena CONVERT_TZ hanya akan mengeluarkan nilai 'YYYY-MM-DD hh: mm: ss' yang mengalami masalah ambiguitas, dan UNIX_TIMESTAMP () mengasumsikannya masukan dalam zona waktu sistem, bukan zona waktu tempat data SEBENARNYA disimpan dalam (UTC).
Saat menyimpan data ke database:
Ubah tanggal Anda ke waktu UTC tepat yang Anda inginkan di luar MySQL. Misalnya: dengan kelas DateTime PHP Anda dapat menentukan "2009-11-01 1:30:00 EST" dengan jelas dari "2009-11-01 1:30:00 EDT", lalu mengubahnya menjadi UTC dan menyimpan waktu UTC yang benar ke bidang DATETIME Anda.
Fiuh. Terima kasih banyak atas masukan dan bantuan semua orang. Mudah-mudahan ini bisa membuat orang lain sakit kepala di kemudian hari.
BTW, saya melihat ini di MySQL 5.0.22 dan 5.0.27
sumber
Saya pikir tautan micahwittman memiliki solusi praktis terbaik untuk batasan MySQL ini: Setel zona waktu sesi ke UTC saat Anda terhubung:
SET SESSION time_zone = '+0:00'
Kemudian Anda kirimkan saja cap waktu Unix dan semuanya akan baik-baik saja.
sumber
Utas ini membuat saya aneh karena kami menggunakan
TIMESTAMP
kolom denganOn UPDATE CURRENT_TIMESTAMP
(yaitu:)recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
untuk melacak catatan yang diubah dan ETL ke gudang data.Jika seseorang bertanya-tanya, dalam hal ini,
TIMESTAMP
berperilaku dengan benar dan Anda dapat membedakan antara dua tanggal serupa dengan mengonversiTIMESTAMP
stempel waktu menjadi unix:select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact; id recordTimestamp UNIX_TIMESTAMP(recordTimestamp) 1 2012-11-04 01:00:10.0 1352005210 2 2012-11-04 01:00:10.0 1352008810
sumber
Anda dapat mengonversi ke UTC seperti:
SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');
Lebih baik lagi, simpan tanggal sebagai bidang TIMESTAMP . Itu selalu disimpan dalam UTC, dan UTC tidak tahu tentang waktu musim panas / musim dingin.
Anda dapat mengonversi dari UTC ke waktu lokal menggunakan CONVERT_TZ :
SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');
Di mana '+00: 00' adalah UTC, zona waktu from, dan 'SYSTEM' adalah zona waktu lokal OS tempat MySQL dijalankan.
sumber
Mysql secara inheren memecahkan masalah ini menggunakan tabel time_zone_name dari mysql db. Gunakan CONVERT_TZ saat CRUD untuk memperbarui tanggal waktu tanpa mengkhawatirkan waktu musim panas.
SELECT CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1, CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;
sumber
Saya sedang mengerjakan pencatatan jumlah kunjungan halaman dan menampilkan jumlah tersebut dalam grafik (menggunakan plugin Flot jQuery). Saya mengisi tabel dengan data uji dan semuanya tampak baik-baik saja, tetapi saya perhatikan bahwa pada akhir grafik, titik-titik itu satu hari libur menurut label pada sumbu x. Setelah pemeriksaan, saya melihat bahwa jumlah tampilan untuk hari 2015-10-25 diambil dua kali dari database dan diteruskan ke Flot, jadi setiap hari setelah tanggal ini dipindahkan satu hari ke kanan.
Setelah mencari bug di kode saya untuk beberapa saat, saya menyadari bahwa tanggal ini adalah saat DST berlangsung. Kemudian saya datang ke halaman SO ini ...
... tetapi solusi yang disarankan berlebihan untuk apa yang saya butuhkan atau mereka memiliki kelemahan lain. Saya tidak terlalu khawatir karena tidak dapat membedakan antara cap waktu yang ambigu. Saya hanya perlu menghitung dan menampilkan catatan per hari.
Pertama, saya mengambil rentang tanggal:
SELECT DATE(MIN(created_timestamp)) AS min_date, DATE(MAX(created_timestamp)) AS max_date FROM page_display_log WHERE item_id = :item_id
Kemudian, dalam perulangan for, dimulai dengan
min_date
, diakhiri denganmax_date
, demi langkah satu hari (60*60*24
), saya mengambil hitungan:for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) { $query = " SELECT COUNT(*) AS count_per_day FROM page_display_log WHERE item_id = :item_id AND ( created_timestamp BETWEEN '" . date( "Y-m-d 00:00:00", $day ) . "' AND '" . date( "Y-m-d 23:59:59", $day ) . "' ) "; //execute query and do stuff with the result }
Saya solusi akhir dan cepat untuk saya masalah adalah ini:
$min_date_timestamp += 60 * 60 * 2; // To avoid DST problems for( $day = $min_date_timestamp; $day <= $max_da.....
Jadi saya tidak menatap lingkaran di awal hari, tetapi dua jam kemudian . Hari ini masih sama, dan saya masih mendapatkan hitungan yang benar, karena saya secara eksplisit meminta catatan database antara 00:00:00 dan 23:59:59 pada hari itu, terlepas dari waktu aktual stempel waktu. Dan ketika waktu melonjak satu jam, saya masih di hari yang benar.
Catatan: Saya tahu ini adalah utas berusia 5 tahun, dan saya tahu ini bukan jawaban untuk pertanyaan OP, tetapi mungkin membantu orang-orang seperti saya yang menemukan halaman ini mencari solusi untuk masalah yang saya jelaskan.
sumber
"SELECT CAST(created_timestamp AS date) day,COUNT(*) WHERE item_id=:item_id AND (created_timestamp BETWEEN '".date("Y-m-d 00:00:00", $min_date_timestamp)."' AND '".date("Y-m-d 23:59:59", $max_date_timestamp)."') GROUP BY day ORDER BY day";