Tại sao sự khác biệt giữa ngày 30 tháng 3 và ngày 1 tháng 3 năm 2020 lại đưa ra sai lầm trong 28 ngày thay vì 29 ngày?


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)

Kết quả là 28, trong khi nó phải là 29.

Múi giờ / địa điểm có thể là vấn đề?


17
Lưu ý: Vui lòng không sử dụng SimpleDateFormatnữa, vì nó đã lỗi thời. Sử dụng các gói từ java.timethay thế. Trong SimpleDateFormattrường hợp, sử dụng DateTimeFormatter. Trong trường hợp Java 7, hãy xem bình luận của Andy Turner bên dưới.
MC Hoàng đế

28
Đừng làm toán nhiều lần. Sử dụng một thư viện thời gian thích hợp ( java.time[mặc dù tôi lưu ý rằng bạn đang ở trên Java 7], ThreeTenBp , Joda).
Andy Turner

14
Tôi rất thích được tham dự cuộc họp nơi có người đến "Được rồi, bây giờ chúng ta đã có những múi giờ, hãy tìm ra chế độ ass 100% và thực hiện điều này gọi là tiết kiệm ánh sáng ban ngày đến với tôi trong giấc mơ sau chuyến đi axit của tôi tối hôm qua."
MonkeyZeus

5
@gmauch TimeUnitkhông giả vờ biết bất cứ điều gì về DST. Như javadoc nói: Một nano giây được định nghĩa là một phần nghìn của một phần triệu giây, một phần triệu giây là một phần nghìn của một phần nghìn giây, một phần nghìn giây là một phần nghìn giây, một phút là sáu mươi giây, một giờ là sáu mươi phút và một ngày là hai mươi bốn giờ . --- Vì DST khiến 2 ngày trong năm không chính xác là 24 giờ, TimeUnitnên đã sai khi DST tham gia.
Andreas

1
Vấn đề này chỉ xảy ra trên các máy tính nằm trong múi giờ với mức tiết kiệm ánh sáng ban ngày. Nó đưa ra số ngày chính xác (29) trong các múi giờ không có tiết kiệm ánh sáng ban ngày!
Gopinath

Câu trả lời:


207

Vấn đề là do sự thay đổi thời gian tiết kiệm ánh sáng ban ngày (vào Chủ nhật, ngày 8 tháng 3 năm 2020), có 28 ngày và 23 giờ giữa những ngày đó. TimeUnit.DAYS.convert(...) cắt ngắn kết quả đến 28 ngày.

Để xem vấn đề (Tôi ở múi giờ miền Đông Hoa Kỳ):

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);

Đầu ra

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

Để khắc phục, hãy sử dụng múi giờ không có DST, ví dụ: 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();

Đầu ra

2505600000
Days: 29
Hours: 696
Days: 29.0

121
"Để sửa chữa" không làm toán với thời gian. Sử dụng một thư viện ngày / thời gian thích hợp.
Andy Turner

62
@AndyTurner Để khắc phục, sử dụng API Java 7 tích hợp. Vì nó có thể được sửa chữa, cho thấy câu trả lời hợp lệ như thế nào. Buộc ai đó bao gồm một thư viện đầy đủ (Joda-Time, ThreeTen, v.v.) chỉ để thực hiện một phép tính này sẽ là quá mức cần thiết. Chắc chắn, sử dụng một thư viện sẽ được khuyến khích, nhưng không bắt buộc .
Andreas

38
Tôi tôn trọng không đồng ý. Nếu bạn muốn làm một phép tính, hãy làm đúng; trả chi phí để làm điều đó đúng
Andy Turner

16
0,02 € của tôi: Cách ngay lập tức của người dùng trong Java 7 mà không cần bất kỳ thư viện bên ngoài nào là sử dụng một GregorianCalendarđối tượng và thêm 1 ngày một lần cho đến khi đạt được ngày kết thúc. Tôi sẽ trả giá cao để tránh điều đó. Và việc thêm backport của một thư viện đã là một phần của Java 8, 9, 10, 11, 12, 13, không có giá cao. Ngược lại, lần tới bạn cần làm bất cứ điều gì với ngày hoặc thời gian, nó sẽ là một lợi ích.
Ole VV

27
Ngay cả trong UTC, ngày cuối cùng của tháng 6 đôi khi quá ngắn một giây, không có bất kỳ cảnh báo hay dự đoán thực sự nào. Luôn luôn sử dụng một thư viện thời gian.
Affe

41

Nguyên nhân của vấn đề này đã được đề cập trong câu trả lời của Andreas .

Câu hỏi là chính xác những gì bạn muốn đếm. Việc bạn nói rằng chênh lệch thực tế phải là 29 thay vì 28 và hỏi liệu "thời gian địa điểm / khu vực có thể là một vấn đề" hay không , cho thấy những gì bạn thực sự muốn tính. Rõ ràng, bạn muốn thoát khỏi bất kỳ sự khác biệt múi giờ.

Tôi giả sử bạn chỉ muốn tính ngày, không có thời gian và múi giờ.

Java 8

Dưới đây, trong ví dụ về cách tính số ngày giữa có thể được tính toán chính xác, tôi đang sử dụng một lớp thể hiện chính xác điều đó - một ngày không có thời gian và múi giờ - 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);

Lưu ý rằng ChronoUnit, DateTimeFormatterLocalDatecần ít nhất Java 8, mà không phải là có sẵn cho bạn, theo thẻ. Tuy nhiên, nó có lẽ là để độc giả tương lai.

Như Ole VV đã đề cập, cũng có ThreeTen Backport , hỗ trợ chức năng API Ngày và Giờ của Java 8 cho Java 6 và 7.


2
@ OleV.V. Tôi biết có ThreeTen, một số người dùng có thể đã đề cập đến nó một vài lần. (Tôi sắp liên kết đến truy vấn SEDE trả về tất cả các bài đăng và nhận xét của người dùng 5772882 có chứa văn bản ThreeTen;-), nhưng không may là nó ngoại tuyến tại thời điểm viết.) Tôi sẽ cập nhật bài viết.
MC Hoàng đế

2
@OleVV Đó không phải là một điều xấu. Tôi nghĩ rằng hầu hết mọi người chỉ đơn giản là không biết gì java.time, bởi vì ở trường họ vẫn sử dụng các lớp học cũ. Nhưng API ngày và thời gian Java 8 được thiết kế rất tốt - sẽ không mất công nếu không sử dụng nó.
MC Hoàng đế
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.