Mengapa perbedaan antara 30 Maret dan 1 Maret 2020 secara keliru memberi 28 hari, bukan 29?


124
TimeUnit.DAYS.convert(
   Math.abs(
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("30-03-2020 00:00:00").getTime() - 
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("1-03-2020 00:00:00").getTime()
   ),
   TimeUnit.MILLISECONDS)

Hasilnya adalah 28, sedangkan seharusnya 29.

Mungkinkah zona waktu / lokasi menjadi masalah?


17
Catatan: Tolong jangan gunakan SimpleDateFormatlagi, karena sudah usang. Gunakan paket dari java.timesebagai gantinya. Dalam SimpleDateFormathal ini, gunakan DateTimeFormatter. Dalam kasus Java 7, lihat komentar Andy Turner di bawah ini.
MC Emperor

28
Jangan menghitung matematika sesekali. Gunakan perpustakaan waktu yang tepat ( java.time[meskipun saya perhatikan Anda berada di Java 7], ThreeTenBp , Joda).
Andy Turner

14
Saya akan senang berada di pertemuan di mana seseorang pergi, "Oke, sekarang kita punya zona waktu yang agaknya, mari kita pergi mode ass 100% dan menerapkan hal ini yang disebut penghematan siang hari yang datang kepada saya dalam mimpi setelah perjalanan asam saya Tadi malam."
MonkeyZeus

5
@gmauch TimeUnittidak berpura-pura tahu tentang DST. Seperti yang dikatakan javadoc: Satu nanodetik didefinisikan sebagai seperseribu mikrodetik, mikrodetik sebagai seperseribu milidetik, satu milidetik sebagai seperseribu detik, satu menit enam puluh detik, satu jam enam puluh menit, dan satu hari sebagai dua puluh empat jam . --- Karena DST menyebabkan 2 hari dalam setahun menjadi tidak persis 24 jam, TimeUnitsalah jika DST terlibat.
Andreas

1
Masalah ini hanya terjadi pada komputer yang berada di zona waktu dengan penghematan siang hari. Ini memberikan jumlah hari yang benar (29) di zona waktu yang tidak memiliki penghematan siang hari!
Gopinath

Jawaban:


207

Masalahnya adalah bahwa karena pergeseran Daylight Saving Time (pada hari Minggu, 8 Maret 2020), ada 28 hari dan 23 jam antara tanggal-tanggal tersebut. TimeUnit.DAYS.convert(...) memotong hasilnya menjadi 28 hari.

Untuk melihat masalahnya (saya berada di zona waktu AS bagian Timur):

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);

Keluaran

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

Untuk memperbaikinya, gunakan zona waktu yang tidak memiliki DST, mis. UTC :

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

Keluaran

2505600000
Days: 29
Hours: 696
Days: 29.0

121
"Untuk memperbaikinya" jangan menghitung matematika dengan waktu. Gunakan perpustakaan tanggal / waktu yang tepat.
Andy Turner

62
@AndyTurner Untuk memperbaikinya, gunakan API Java 7 bawaan. Karena dapat diperbaiki, menunjukkan bagaimana jawaban yang valid. Memaksa seseorang untuk memasukkan pustaka lengkap (Joda-Time, ThreeTen, dll.) Hanya untuk melakukan ini perhitungan satu akan berlebihan. Tentu, menggunakan perpustakaan akan direkomendasikan, tetapi tidak diperlukan .
Andreas

38
Dengan hormat saya tidak setuju. Jika Anda ingin melakukan perhitungan, lakukan dengan benar; membayar biaya untuk melakukannya dengan benar.
Andy Turner

16
€ 0,02 saya: Cara "benar" untuk dilakukan di Java 7 tanpa perpustakaan eksternal adalah dengan menggunakan GregorianCalendarobjek dan menambahkan 1 hari pada suatu waktu hingga tanggal akhir tercapai. Saya akan membayar harga tinggi untuk menghindari itu. Dan menambahkan backport perpustakaan yang sudah menjadi bagian dari Java 8, 9, 10, 11, 12, 13, ... tidak ada harga mahal. Sebaliknya, lain kali Anda perlu melakukan apa pun dengan tanggal atau waktu, itu akan menjadi keuntungan.
Ole VV

27
Bahkan di UTC hari terakhir Juni kadang-kadang satu detik terlalu singkat, tanpa peringatan atau prediksi yang nyata. Selalu gunakan perpustakaan tanggal-waktu.
Affe

41

Penyebab masalah ini sudah disebutkan dalam jawaban Andreas .

Pertanyaannya adalah apa tepatnya yang ingin Anda hitung. Fakta bahwa Anda menyatakan bahwa selisih sebenarnya seharusnya 29 bukannya 28, dan bertanya apakah "waktu lokasi / zona bisa menjadi masalah" , mengungkapkan apa yang sebenarnya ingin Anda hitung. Tampaknya, Anda ingin menyingkirkan perbedaan zona waktu.

Saya berasumsi Anda hanya ingin menghitung hari, tanpa waktu dan zona waktu.

Java 8

Di bawah ini, dalam contoh bagaimana jumlah hari di antara dapat dihitung dengan benar, saya menggunakan kelas yang menunjukkan dengan tepat bahwa - tanggal tanpa waktu dan zona waktu - LocalDate.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy HH:mm:ss");
LocalDate start = LocalDate.parse("1-03-2020 00:00:00", formatter);
LocalDate end = LocalDate.parse("30-03-2020 00:00:00", formatter);

long daysBetween = ChronoUnit.DAYS.between(start, end);

Perhatikan itu ChronoUnit, DateTimeFormatterdan LocalDateminta setidaknya Java 8, yang tidak tersedia untuk Anda, menurut tag . Namun, mungkin untuk pembaca masa depan.

Seperti disebutkan oleh Ole VV, ada juga ThreeTen Backport , yang mendukung fungsi Java Date dan Time API ke Java 6 dan 7.


2
@ OleV.V. Saya tahu ada ThreeTen, beberapa pengguna mungkin telah menyebutkannya beberapa kali. (Saya baru saja akan menautkan ke kueri SEDE yang mengembalikan semua posting dan komentar pengguna 5772882 yang berisi teks ThreeTen;-), tetapi sayangnya itu sedang offline pada saat penulisan.) Saya akan memperbarui posting.
MC Emperor

2
@ OVVV Bukan hal yang buruk sama sekali Saya pikir kebanyakan orang sama sekali tidak tahu java.time, karena di sekolah mereka masih menggunakan kelas-kelas lama. Tetapi API Tanggal dan Waktu Java 8 dirancang dengan sangat baik - akan menjadi kerugian jika tidak menggunakannya.
MC Emperor
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.