Menggunakan DateTime Di simple-jpa


simple-jpa 0.2.1 memiliki komponen DateTimePicker.  Apa kegunaannya?

Menyimpan dan men-query tipe data tanggal sekilas terlihat gampang, tapi kerap menimbulkan permasalahan pada prakteknya.   Hal ini karena Date di Java menyimpan tanggal  dan jam secara bersamaan.  Sebagai contoh, saya memiliki sebuah tabel dengan data bertipe tanggal seperti yang terlihat berikut ini:

mysql> select * from tagihan;
+------+---------------------+
| kode | jatuhTempo          |
+------+---------------------+
|    1 | 2013-04-11 10:00:00 |
|    2 | 2013-04-11 12:00:00 |
|    3 | 2013-04-12 09:00:00 |
|    4 | 2013-04-12 00:00:00 |
+------+---------------------+

Pada tabel di atas, nilai jatuhTempo mewakili tanggal jatuh tempo yang dipilih oleh pengguna melalui sebuah picker atau di-isi secara otomatis melalui kode program.  Bila tidak hati-hati, nilai jam secara otomatis juga akan ikut terisi (biasanya sesuai dengan jam saat pengguna memilih tanggal).

Apa akibatnya bila nilai jam ikut terisi?

Berikut ini adalah sebuah SQL yang ‘polos’ untuk mengambil seluruh tagihan yang jatuh tempo pada tanggal 2013-04-11:

mysql> select * from tagihan where jatuhTempo = '2013-04-11';
Empty set (0.00 sec)

Kenapa tidak ada hasil yang dikembalikan?  Karena query tersebut hanya mencari tagihan yang jatuh tempo pada waktu 2013-04-11 00:00:00.  Hal ini cukup menjebak dan bisa menjadi sumber kesalahan bila developer tidak berhati-hati.  Bila query dilakukan secara lengkap berdasarkan tanggal dan jam, maka hasil akan dikembalikan dengan baik, seperti berikut ini:

mysql> select * from tagihan where jatuhTempo = '2013-04-11 12:00:00';
+------+---------------------+
| kode | jatuhTempo          |
+------+---------------------+
|    2 | 2013-04-11 12:00:00 |
+------+---------------------+
1 row in set (0.00 sec)

Contoh ketidak-konsistenan lainnya adalah bila pengguna memilih untuk melihat tagihan yang jatuh tempo setelah tanggal 2013-04-11, perintah SQL akan menampilkan hasil seperti berikut ini:

mysql> select * from tagihan where jatuhTempo > '2013-04-11';
+------+---------------------+
| kode | jatuhTempo          |
+------+---------------------+
|    1 | 2013-04-11 10:00:00 |
|    2 | 2013-04-11 12:00:00 |
|    3 | 2013-04-12 09:00:00 |
|    4 | 2013-04-12 00:00:00 |
+------+---------------------+
4 rows in set (0.00 sec)

Bukankah harusnya hanya tanggal 12 saja yang disertakan?  Tapi yang dikerjakan adalah seluruh tagihan yang jatuh tempo setelah tanggal 2013-04-11 00:00:00 tengah malam (tagihan pada tanggal 2013-04-11 yang jam-nya bukan 00:00:00 akan ditampilkan)! Demikian juga bila pengguna menampilkan tagihan yang jatuh tempo setelah tanggal 2013-04-12, ia akan memperoleh hasil seperti berikut ini:

mysql> select * from tagihan where jatuhTempo > '2013-04-12';
+------+---------------------+
| kode | jatuhTempo          |
+------+---------------------+
|    3 | 2013-04-12 09:00:00 |
+------+---------------------+
1 row in set (0.00 sec)

Akan terasa aneh, bukan?

Untuk menghindari permasalahan seperti di atas, simple-jpa memakai Joda Time.  Komponen DateTimePicker mendukung object Joda Time berikut ini:

  • LocalDate – hanya berisi tanggal tanpa ada informasi jam.  Pada contoh di atas, jatuhTempo merupakan kandidat yang tepat untuk menggunakan LocalDate.
  • LocalTime – hanya berisi jam tanpa informasi tanggal.  Bisa digunakan pada atribut seperti batasJamTerlambat, jamMulaiLembur, dsb.
  • LocalDateTime – berisi informasi tanggal dan jam.  Bisa digunakan pada atribut seperti jamMasukKerja (berisi tanggal dan jam saat karyawan absensi masuk setiap harinya),  tanggalDaftar (tanggal plus jam pendaftaran), dsb.
  • DateTime – mirip seperti LocalDateTime tetapi menyertakan informasi time zone sehingga waktu dapat ditampilkan sesuai dengan time zone masing-masing pengguna.

Untuk melihat perbedaan masing-masing object di atas, saya akan membuat sebuah proyek sederhana yang memakai simple-jpa 0.2.1. Saya akan membuat domain class dengan nama Latihan yang terlihat seperti berikut ini:

package domain

import ...

@DomainModel @Entity
@TupleConstructor @ToString
class Latihan {

    @Size(min=2, max=50)
    String nama

    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    LocalDate tanggal

}

Pada domain class di atas, saya wajib menyertakan @Type karena LocalDate bukanlah tipe data yang di-dukung secara native oleh Hibernate JPA.  simple-jpa memakai Jadira User Type yang menyediakan class PersistentLocalDate, PersistentLocalTime, PersistentLocalDateTime, dan PersistentDateTime untuk men-konversi setiap object Joda Time ke dalam field database (dan sebaliknya).  Bila saya memakai perintah generate-all, pada view akan terdapat baris seperti berikut ini:

dateTimePicker(id: 'tanggal', localDate: bind('tanggal', target: model, mutual: true), 
   errorPath: 'tanggal', dateVisible: true, timeVisible: false)

Binding dilakukan terhadap nilai property localDate.  Selain itu, timeVisible diberi nilai false sehingga JSpinner untuk memilih jam tidak akan ditampilkan.  Tampilannya akan terlihat seperti berikut ini:

Tampilan DateTimePicker Untuk LocalDate

Tampilan DateTimePicker Untuk LocalDate

Bila saya melihat isi tabel, data akan tersimpan seperti berikut ini:

mysql> select tanggal from latihan;
+------------+
| tanggal    |
+------------+
| 2013-04-11 |
| 2013-04-14 |
| 2013-04-23 |
+------------+

Selanjutnya, saya akan mengubah definisi tanggal di file Latihan.groovy menjadi seperti berikut ini:

@Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalTime")
LocalTime tanggal

Setiap perubahan pada domain class umumnya juga akan mempengaruhi view model (atau disebut hanya model).  Agar tidak bingung, anggap saja domain class berhubungan dengan keseluruhan business logic aplikasi yang dapat dipakai dimana saja, sementara view model dipakai untuk mewakili data pada view yang sedang aktif.  Karena setiap form umumnya menampilkan atribut sebuah domain class, maka biasanya atribut di domain class juga ada di view model.

Perubahan bisa disesuaikan secara otomatis oleh perintah generate-all, akan tetapi kali ini saya akan mencoba mengubah secara manual tanpa melalui perintah tersebut.  Saya perlu mengubah file LatihanModel.groovy yang mendefinisikan tanggal menjadi seperti berikut ini:

@Bindable LocalTime tanggal

Lalu, saya juga mengubah definisi tanggal di view sehingga terlihat menjadi seperti berikut ini:

dateTimePicker(id: 'tanggal', localTime: bind('tanggal', target: model, mutual: true), 
   errorPath: 'tanggal', dateVisible: true, timeVisible: false)

Bila saya menjalankan aplikasi, sekarang akan terlihat seperti pada gambar berikut ini:

Tampilan DateTimePicker Untuk LocalTime

Tampilan DateTimePicker Untuk LocalTime

Selanjutnya, akan kembali mengubah Latihan.groovy sehingga definisi tanggal menjadi seperti berikut ini:

@Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
LocalDateTime tanggal

Lalu, saya mengubah LatihanModel.groovy pada definisi tanggal menjadi seperti berikut ini:

@Bindable LocalDateTime tanggal

Terakhir, saya mengubah definisi di view menjadi seperti berikut ini:

dateTimePicker(id: 'tanggal', localDateTime: bind('tanggal', target: model, mutual: true), 
   errorPath: 'tanggal', dateVisible: true, timeVisible: true)

Bila saya menjalankan aplikasi, tampilan akan terlihat seperti berikut ini:

Tampilan DateTimePicker Untuk LocalDateTime

Tampilan DateTimePicker Untuk LocalDateTime

Bila saya melihat data yang tersimpan di tabel,  saya akan menemukan seperti berikut ini:

mysql> select tanggal from latihan;
+---------------------+
| tanggal             |
+---------------------+
| 2013-04-02 20:14:00 |
| 2013-04-30 16:22:00 |
| 2013-04-16 22:11:00 |
+---------------------+

Berikutnya, saya akan melihat perbedaan antara LocalDateTime dan DateTime.  Saya akan mulai dengan mengubah definisi tanggal di Latihan.groovy menjadi seperti berikut ini:

@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
DateTime tanggal

Setelah itu, saya mengubah file LatihanModel.groovy yang mendefinisikan tanggal menjadi seperti berikut ini:

@Bindable DateTime tanggal

Lalu, saya mengubah file LatihanView.groovy yang mendefinisikan tanggal menjadi seperti berikut:

dateTimePicker(id: 'tanggal', dateTime: bind('tanggal', target: model, mutual: true), errorPath: 'tanggal')

Saya boleh menghilangkan bagian yang mengubah property dateVisible dan timeVisible menjadi true karena secara default kedua nilai tersebut adalah true.

Pada saat dijalankan, aplikasi akan terlihat seperti berikut ini:

Tampilan DateTimePicker Untuk Data DateTime

Tampilan DateTimePicker Untuk Data DateTime

Secara sekilas, tidak ada yang terlihat berbeda dengan LocalDateTime (kecuali ada tambahan +7 yang menunjukkan informasi time zone).  Akan tetapi, bila saya melihat data yang tersimpan di database, saya akan menemukan seperti berikut ini:

mysql> select tanggal from latihan;
+---------------------+
| tanggal             |
+---------------------+
| 2013-04-01 05:33:00 |
| 2013-04-08 07:33:00 |
| 2013-04-30 13:44:00 |
+---------------------+

Kenapa data yang tersimpan berbeda jauh dengan data yang ditampilkan?  Karena Jadira User Type akan men-konversi DateTime dan menyimpannya di database dalam timezone UTC (+0).  Saat sebuah aplikasi menbaca data DateTime, ia akan menerjemahkan data dari database yang dalam UTC  ke time zone yang dipakai oleh aplikasi.  Bila Jadira User Type tidak menyimpan data dalam bentuk UTC, maka proses menerjemahkan kembali tanggal sesuai dengan time zone pengguna akan menjadi kompleks.

Sekarang, saya akan mensimulasikan seorang pengguna dari time zone berbeda, misalnya seorang pengguna dari time zone berbeda dengan cara mengubah Time Zone setting di komputer saya.  Tetap berdasarkan data yang sama (saya tidak menghapus data yang sebelumnya ada di tabel!), saat saya menjalankan aplikasi, saya akan memperoleh tampilan seperti berikut ini:

Tanggal yang sama di TimeZone yang berbeda

Tanggal yang sama di TimeZone yang berbeda

Untuk memeriksa apakah nilai yang ditampilkan adalah benar, saya akan menggunakan rumus konversi time zone pada baris pertama seperti berikut ini:

"time in zone A" − "UTC offset for zone A" = "time in zone B" − "UTC offset for zone B"
2013-04-01T12:33:00 - (+07:00) = 2013-04-01T07:33:00 - (+02:00)
2013-04-01T05:33:00 = 2013-04-01T05:33:00

Dengan menggunakan DateTime, tanggal dan jam dapat ditampilkan dengan benar sesuai dengan time zone yang dipakai oleh komputer yang menjalankan aplikasi (atau time zone yang diatur untuk Java). Hal ini yang membedakan antara DateTime dan LocalDateTime.

Perihal Solid Snake
I'm nothing...

One Response to Menggunakan DateTime Di simple-jpa

  1. makasih, sangat membantu sekali..

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: