Menyimpan janji temu dalam database SQL seperti Postgres untuk digunakan dengan framework java.time

Oct 27 2020

Katakanlah kita ada janji di Milan Italia yang terjadi pada 23/1/2021 21:00 "Eropa / Roma". Janji temu ini disimpan ke database di UTC dalam kolom dengan tipe yang mirip dengan tipe standar SQL TIMESTAMP WITH TIME ZONE.

Sekarang pengguna yang tinggal di New York AS perlu memahami kapan janji temu ini akan dilakukan. Kami dapat menunjukkan kepada pengguna bahwa tanggal-waktu dikonversi ke zona waktu "Amerika / New_York", atau sebaliknya, di zona waktu "Eropa / Roma". Setelah pengguna akan terbang dari New York ke Milan, dia akan menemukan kedua info berguna.

Intinya adalah untuk menyimpan semua yang diubah menjadi referensi TZ yang sama (UTC), dan memanipulasi tanggal-waktu tergantung pada tujuan Anda menggunakan kerangka java.time yang dibundel dengan Java modern.

Masuk akal atau ada yang salah / hilang?

Jawaban

4 BasilBourque Oct 27 2020 at 04:57

Masuk akal atau ada yang salah / hilang?

Itu tergantung pada jenis janji temu.

Ada dua jenis janji temu:

  • Sesaat, titik tertentu di garis waktu, mengabaikan perubahan apa pun pada aturan zona waktu.
    Contoh: Peluncuran roket.
  • Tanggal dan waktu yang harus menyesuaikan untuk perubahan pada aturan zona waktu.
    Contoh: Kunjungan Medis / Gigi.

Saat

Jika kita memesan peluncuran roket, misalnya, kita tidak peduli dengan tanggal dan waktu. Kita hanya peduli pada saat (a) langit sejajar, dan (b) kita mengharapkan cuaca yang baik.

Jika dalam waktu sela politisi mengubah aturan zona waktu yang digunakan di situs peluncuran atau di kantor kami, itu tidak berpengaruh pada janji peluncuran kami. Jika politisi yang mengatur situs peluncuran kami mengadopsi Daylight Saving Time (DST) , momen peluncuran kami tetap sama. Jika politisi yang mengatur kantor kami memutuskan untuk mengubah jam setengah jam lebih awal karena hubungan diplomatik dengan negara tetangga, momen peluncuran kami tetap sama.

Untuk janji seperti itu, ya, pendekatan Anda akan benar. Anda akan mencatat janji temu dalam UTC menggunakan kolom tipe TIMESTAMP WITH TIME ZONE. Setelah pengambilan, sesuaikan dengan zona waktu mana pun yang disukai pengguna.

Basis data seperti Postgres menggunakan info zona waktu apa pun yang menyertai masukan untuk menyesuaikan ke dalam UTC, dan kemudian membuang info zona waktu tersebut. Saat Anda mengambil nilai dari Postgres, itu akan selalu mewakili tanggal dengan waktu seperti yang terlihat di UTC. Berhati-hatilah, beberapa perkakas atau middleware mungkin memiliki fitur anti-penerapan beberapa zona waktu default antara pengambilan dari database dan pengiriman programmer kepada Anda. Tapi jelas: Postgres selalu menyimpan dan mengambil nilai tipe TIMESTAMP WITH TIME ZONEdalam UTC, selalu UTC, dan offset-dari-UTC nol jam-menit-detik.

Berikut beberapa contoh kode Java.

LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;

launchMomentAsSeenInRome.toString (): 2021-01-23T21: 00 + 01: 00 [Eropa / Roma]

Untuk melihat momen yang sama di UTC, ubah menjadi Instant. Sebuah Instantobjek selalu merupakan saat seperti yang terlihat dalam UTC.

Instant launchInstant = launchMomentAsSeenInRome.toInstant() ;  // Adjust from Rome time zone to UTC.

launchInstant.toString (): 2021-01-23T20: 00: 00Z

Di Zakhir contoh string di atas adalah notasi standar untuk UTC, dan dilafalkan "Zulu".

Sayangnya tim JDBC 4.2 lalai membutuhkan dukungan untuk salah satu Instantatau ZonedDateTimetipe. Jadi driver JDBC Anda mungkin atau mungkin tidak dapat membaca / menulis objek seperti itu ke database Anda. Jika tidak, cukup ubah ke OffsetDateTime. Ketiga jenis mewakili momen, titik tertentu di garis waktu. Tetapi OffsetDateTimememiliki dukungan yang diperlukan oleh JDBC 4.2 untuk alasan yang luput dari saya.

OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;

Menulis ke database.

myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;

Pengambilan dari database.

OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;

Sesuaikan dengan zona waktu New York yang diinginkan oleh pengguna Anda.

ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;

launchAsSeenInNewYork.toString (): 2021-01-23T15: 00-05: 00 [Amerika / New_York]

Anda dapat melihat semua kode di atas dijalankan langsung di IdeOne.com .

Ngomong-ngomong, melacak peristiwa masa lalu juga diperlakukan sebagai momen. Kapan pasien benar-benar datang untuk membuat janji, kapan pelanggan membayar tagihan, kapan karyawan baru menandatangani dokumen mereka, kapan server crash ... semua ini dilacak pada saat dalam UTC. Seperti dibahas di atas, biasanya itu akan menjadi Instant, meskipun ZonedDateTime& OffsetDateTimejuga mewakili suatu momen. Untuk penggunaan database TIMESTAMP WITH TIME ZONE(bukan WITHOUT).

Waktu dalam sehari

Saya berharap sebagian besar aplikasi berorientasi bisnis difokuskan pada janji temu jenis lain, di mana kami bertujuan pada tanggal dengan waktu hari daripada momen tertentu.

Jika pengguna membuat janji dengan penyedia layanan kesehatan mereka untuk meninjau hasil tes, mereka melakukannya untuk waktu tertentu pada tanggal itu. Jika sementara itu politisi mengubah aturan zona waktu mereka, menggerakkan jam ke depan atau di belakang satu jam atau setengah jam atau jumlah waktu lainnya, tanggal dan waktu penunjukan medis tersebut tetap sama. Sebenarnya, poin timeline penunjukan awal akan berubah setelah politisi mengubah zona waktu, bergeser ke poin sebelumnya / nanti pada timeline.

Untuk janji seperti itu, kami tidak menyimpan tanggal dan waktu seperti yang terlihat di UTC. Kami tidak menggunakan tipe kolom database TIMESTAMP WITH TIME ZONE.

Untuk janji seperti itu, kami menyimpan tanggal dengan waktu hari tanpa memperhatikan zona waktu. Kami menggunakan jenis kolom database TIMESTAMP WITHOUT TIME ZONE(pemberitahuan WITHOUTdaripada WITH). Jenis pencocokan di Java adalah LocalDateTime.

LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;

Tulis itu ke database.

myPreparedStatement.setObject( … , medicalApptDateTime ) ;

Perjelas ini: LocalDateTimeobjek tidak mewakili momen, bukan titik spesifik pada garis waktu. Sebuah LocalDateTimeobjek mewakili berbagai kemungkinan saat bersama sekitar 26-27 jam dari timeline (kisaran zona waktu di seluruh dunia). Untuk memberikan arti yang sebenarnya pada a LocalDateTime, kita harus mengaitkan zona waktu yang dimaksudkan.

Untuk zona waktu yang dimaksud, gunakan kolom kedua untuk menyimpan pengidentifikasi zona. Misalnya, string Europe/Romeatau America/New_York. Lihat daftar nama zona .

ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;

Tulis itu ke database sebagai teks.

myPreparedStatement.setString( … , zoneEuropeRome ) ;

Pengambilan. Ambil nama zona sebagai teks, dan buat ZoneIdinstance objek.

LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;

Gabungkan kedua potongan itu untuk menentukan momen yang direpresentasikan sebagai sebuah ZonedDateTimeobjek. Lakukan ini secara dinamis saat Anda perlu menjadwalkan kalender. Tapi jangan tidak menyimpan saat ini. Jika politisi mendefinisikan ulang zona waktu di masa depan, maka momen yang berbeda harus dihitung.

ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;

Pengguna bepergian ke New York AS. Mereka perlu tahu kapan harus menghubungi penyedia layanan kesehatan di Milan Italia sesuai dengan jam di dinding di lokasi sementara mereka di New York. Jadi sesuaikan dari satu zona waktu ke zona waktu lainnya. Momen yang sama, waktu jam dinding yang berbeda.

ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;

tzdata

Ketahuilah bahwa jika aturan zona waktu yang Anda inginkan mungkin berubah, Anda harus memperbarui salinan definisi zona waktu di komputer Anda.

Java berisi salinan tzdata -nya sendiri , seperti halnya mesin database Postgres. Dan sistem operasi host Anda juga. Kode khusus yang ditampilkan di sini hanya memerlukan Java yang mutakhir. Jika Anda menggunakan Postgres untuk melakukan penyesuaian zona waktu, tzdata -nya juga harus mutakhir. Dan untuk logging dan semacamnya, OS host Anda harus selalu up-to-date. Untuk menonton jam yang tepat oleh pengguna, OS mesin klien mereka juga harus mutakhir.

Waspadalah: Politisi di seluruh dunia telah menunjukkan kecenderungan untuk mengubah zona waktu mereka dengan frekuensi yang mengejutkan, dan seringkali dengan sedikit peringatan sebelumnya.


Tentang java.time

The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date, Calendar, & SimpleDateFormat.

Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasinya adalah JSR 310 .

Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke kelas java.time .

Anda dapat bertukar objek java.time langsung dengan database Anda. Gunakan driver JDBC yang sesuai dengan JDBC 4.2 atau yang lebih baru. Tidak perlu string, tidak perlu java.sql.*kelas. Hibernate 5 & JPA 2.2 mendukung java.time .

Di mana mendapatkan kelas java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 , Java SE 11 , dan yang lebih baru - Bagian dari Java API standar dengan implementasi yang dibundel.
    • Java 9 membawa beberapa fitur dan perbaikan kecil.
  • Java SE 6 dan Java SE 7
    • Sebagian besar fungsionalitas java.time di -back-port ke Java 6 & 7 di ThreeTen-Backport .
  • Android
    • Versi implementasi paket Android (26+) yang lebih baru dari kelas java.time .
    • Untuk Android versi sebelumnya (<26), proses yang disebut API desugaring menghadirkan subset fungsionalitas java.time yang awalnya tidak disertakan dalam Android.
      • Jika desugaring tidak menawarkan apa yang Anda butuhkan, ThreeTenABP proyek menyesuaikan ThreeTen-backport (yang disebutkan di atas) untuk Android. Lihat Cara menggunakan ThreeTenABP… .
LaurenzAlbe Oct 27 2020 at 14:09

Solusi yang sangat sederhana adalah meninggalkan konversi ke PostgreSQL. Jika Anda menyetel timezoneparameter dengan benar untuk setiap sesi dan menggunakan timestamp with time zonePostgreSQL akan secara otomatis menampilkan stempel waktu ke pengguna New York dalam waktu New York.