Mengubah antara java.time.LocalDateTime dan java.util.Date

485

Java 8 memiliki API yang sepenuhnya baru untuk tanggal dan waktu. Salah satu kelas paling berguna dalam API ini adalah LocalDateTime, untuk memegang nilai tanggal-dengan-waktu-independen-zona waktu.

Mungkin ada jutaan baris kode menggunakan kelas legacy java.util.Dateuntuk tujuan ini. Dengan demikian, ketika menghubungkan kode lama dan baru akan ada kebutuhan untuk mengkonversi keduanya. Karena tampaknya tidak ada metode langsung untuk mencapai ini, bagaimana bisa dilakukan?

Knut Arne Vedaa
sumber

Jawaban:

706

Jawaban singkat:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Penjelasan: (berdasarkan pertanyaan ini tentang LocalDate)

Meskipun namanya, java.util.Datemewakili instan di garis waktu, bukan "tanggal". Data aktual yang disimpan dalam objek adalah longhitungan milidetik sejak 1970-01-01T00: 00Z (tengah malam pada awal 1970 GMT / UTC).

Kelas yang setara dengan java.util.Datedi JSR-310 adalah Instant, jadi ada metode yang mudah untuk menyediakan konversi ke sana kemari:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

Sebuah java.util.Dateinstance tidak memiliki konsep zona waktu. Ini mungkin tampak aneh jika Anda memanggil toString()pada java.util.Date, karena toStringrelatif ke-zona waktu. Namun metode itu benar-benar menggunakan zona waktu default Java on the fly untuk memberikan string. Zona waktu bukan bagian dari keadaan aktual java.util.Date.

An Instantjuga tidak mengandung informasi apa pun tentang zona waktu. Jadi, untuk mengkonversi dari Instanttanggal waktu setempat ke waktu perlu menentukan zona waktu. Ini mungkin zona default - ZoneId.systemDefault()- atau mungkin zona waktu yang dikontrol aplikasi Anda, seperti zona waktu dari preferensi pengguna. LocalDateTimememiliki metode pabrik yang nyaman yang menggunakan zona waktu instan dan waktu:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

Secara terbalik, LocalDateTimezona waktu ditentukan dengan memanggil atZone(ZoneId)metode. The ZonedDateTimekemudian dapat dikonversi langsung ke Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Perhatikan bahwa konversi dari LocalDateTimeke ZonedDateTimememiliki potensi untuk memperkenalkan perilaku yang tidak terduga. Ini karena tidak setiap tanggal lokal ada karena Waktu Musim Panas. Pada musim gugur / musim gugur, ada tumpang tindih dalam garis waktu lokal di mana waktu tanggal lokal yang sama terjadi dua kali. Di musim semi, ada celah, di mana satu jam menghilang. Lihat Javadoc atZone(ZoneId)untuk lebih banyak definisi tentang apa yang akan dilakukan konversi.

Ringkasan, jika Anda pulang pergi java.util.Dateke LocalDateTimedan kembali ke, java.util.DateAnda mungkin berakhir dengan instan berbeda karena Waktu Musim Panas.

Info tambahan: Ada perbedaan lain yang akan mempengaruhi tanggal yang sangat lama. java.util.Datemenggunakan kalender yang berubah pada 15 Oktober 1582, dengan tanggal sebelumnya menggunakan kalender Julian alih-alih kalender Gregorian. Sebaliknya, java.time.*gunakan sistem kalender ISO (setara dengan Gregorian) untuk sepanjang masa. Dalam kebanyakan kasus penggunaan, sistem kalender ISO adalah yang Anda inginkan, tetapi Anda mungkin melihat efek aneh ketika membandingkan tanggal sebelum tahun 1582.

JodaStephen
sumber
5
Terima kasih banyak untuk penjelasan yang jelas. Terutama untuk alasan mengapa java.util.Datetidak mengandung zona waktu, tetapi mencetaknya selama toString(). Dokumentasi resmi acara tidak memberitahukan hal ini dengan jelas saat Anda memposting.
Cherry
Peringatan: LocalDateTime.ofInstant(date.toInstant()... tidak berperilaku seperti yang diharapkan secara naif. Misalnya new Date(1111-1900,11-1,11,0,0,0);akan 1111-11-17 23:53:28menggunakan pendekatan ini. Lihatlah implementasi java.sql.Timestamp#toLocalDateTime()jika Anda membutuhkan hasilnya 1111-11-11 00:00:00pada contoh sebelumnya.
anjing
2
Saya telah menambahkan bagian tentang tanggal yang sangat lama (pra-1582). FWIW, saran perbaikan Anda mungkin salah, seperti 1111-11-11 di java.util.Date adalah hari sebenarnya yang sama dalam sejarah dengan 1111-11-18 di java.time karena sistem kalender yang berbeda (perbedaan 6,5 menit) terjadi pada banyak zona waktu sebelum 1900)
JodaStephen
2
Juga patut dicatat yang java.sql.Date#toInstantmelempar UnsupportedOperationException. Jadi jangan gunakan toInstantdi RowMapper pada java.sql.ResultSet#getDate.
LazerBass
132

Inilah yang saya kemukakan (dan seperti semua masalah Date Time, itu mungkin akan ditolak berdasarkan beberapa penyesuaian timezone-leapyear-daylight yang aneh: D)

Bolak-balik: Date<<->>LocalDateTime

Diberikan: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Contoh:

Diberikan:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Buat Instantdari Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Buat Datedari Instant(tidak perlu, tetapi untuk ilustrasi):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Buat LocalDateTimedariInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Buat Instantdari LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Buat Datedari Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Outputnya adalah:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
Saint Hill
sumber
2
@ scottb apakah Anda menyapa saya atau orang yang mengajukan pertanyaan? Adapun pemikiran saya, baik, akan menyenangkan untuk memiliki konversi yang dinyatakan secara eksplisit di API 8 Jdk - Mereka setidaknya bisa melakukan itu. Dalam banyak kasus, ada banyak perpustakaan yang akan difaktorkan ulang untuk memasukkan fitur Java-8 yang baru dan untuk mengetahui bagaimana melakukannya adalah penting.
Koordinator
4
@scottb Mengapa kasing pihak ketiga jarang terjadi? Sangat umum. Satu contoh: JDBC 4 dan kurang (mudah-mudahan tidak 5).
Raman
5
Sebagai aturan JSR-310 umum, tidak perlu mengkonversi antara tipe menggunakan epoch-millis. Alternatif yang lebih baik ada menggunakan objek, lihat jawaban lengkap saya di bawah ini. Jawaban di atas juga hanya sepenuhnya valid jika menggunakan zona-offset seperti UTC - beberapa bagian dari jawaban tidak akan berfungsi untuk zona waktu penuh seperti Amerika / New_York.
JodaStephen
2
Alih-alih Instant.ofEpochMilli(date.getTime())melakukannyadate.toInstant()
kambing
1
@goat toInstant()terlihat bagus, kecuali gagal java.sql.Date, arggggh! Jadi akhirnya lebih mudah digunakan Instant.ofEpochMilli(date.getTime()).
vadipp
22

Cara yang jauh lebih nyaman jika Anda yakin membutuhkan zona waktu default:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
Petrychenko
sumber
25
Tentu, ini lebih mudah, tapi saya tidak suka mencampur hal-hal yang terkait jdbc dengan penanganan Tanggal sederhana, setidaknya IMHO.
Enrico Giurin
9
Maaf, ini solusi mengerikan untuk masalah sederhana. Ini seperti mendefinisikan 5 agar sama dengan jumlah cincin di logo Olimpiade.
Madbreak
7

yang berikut ini tampaknya berfungsi ketika mengonversi dari LocalDateTime API baru ke java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

konversi sebaliknya dapat (semoga) tercapai dengan cara yang sama ...

semoga membantu ...

pusing
sumber
5

Semuanya ada di sini: http://blog.progs.be/542/date-to-java-time

Jawaban dengan "bolak-balik" tidak tepat: kapan Anda melakukannya

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

jika zona waktu sistem Anda bukan UTC / GMT, Anda mengubah waktu!

joe-mojo
sumber
Hanya jika Anda melakukannya selama 1 jam setahun, di musim gugur, ketika dua kali dari LocalDateTime tumpang tindih. Langkah maju pegas tidak menyebabkan masalah. Sebagian besar waktu akan mengkonversi kedua arah dengan benar.
David
4

Cara tercepat untuk LocalDateTime-> Dateadalah:

Date.from(ldt.toInstant(ZoneOffset.UTC))

Lukasz Czerwinski
sumber
3

Saya tidak yakin apakah ini cara termudah atau terbaik, atau jika ada jebakan, tetapi berhasil:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}
Knut Arne Vedaa
sumber
3
Pasti ada jebakan dari LocalDateTimeke Date. Pada transisi di siang hari, a LocalDateTimebisa tidak ada, atau terjadi dua kali. Anda perlu memikirkan apa yang Anda inginkan terjadi dalam setiap kasus.
Jon Skeet
1
BTW, GregorianCalendarmilik API canggung lama yang java.timebertujuan untuk menggantikan API baru
Vadzim
3

Jika Anda menggunakan Android dan menggunakan threetenbp, Anda dapat menggunakannyaDateTimeUtils .

ex:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

Anda tidak dapat menggunakan Date.fromkarena hanya didukung pada api 26+

humazed
sumber