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?

Joe
sumber
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
Andreas
sumber
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.

MC Emperor
sumber
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