ทำไมความแตกต่างระหว่าง 30 มีนาคมถึง 1 มีนาคม 2020 ให้ 28 วันแทนที่จะเป็น 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)

ผลลัพธ์คือ 28 ขณะที่ควรเป็น 29

เขตเวลา / ตำแหน่งที่ตั้งอาจเป็นปัญหาหรือไม่


17
หมายเหตุ: โปรดอย่าใช้SimpleDateFormatอีกต่อไปเพราะล้าสมัยแล้ว ใช้แพ็คเกจจากjava.timeแทน ในกรณีของการใช้งานSimpleDateFormat DateTimeFormatterในกรณีของ Java 7 ดูความคิดเห็นของ Andy Turner ด้านล่าง
พิธีกรจักรพรรดิ

28
อย่าทำคณิตศาสตร์ในบางครั้ง ใช้ไลบรารีเวลาที่เหมาะสม ( java.time[แม้ว่าฉันจะทราบว่าคุณอยู่บน Java 7], ThreeTenBp , Joda)
Andy Turner

14
ฉันชอบที่จะได้รับในการประชุมที่ใครบางคนไป "โอเคตอนนี้ที่เราได้มีเขตเวลาที่คิดออกไปลองโหมดตูด 100% และใช้สิ่งนี้เรียกว่าประหยัดเวลากลางวันซึ่งมาถึงฉันในฝันหลังจากการเดินทางกรดของฉัน คืนที่แล้ว."
MonkeyZeus

5
@ gmauch TimeUnitไม่ได้หลอกว่ารู้อะไรเกี่ยวกับ DST ตามที่ javadoc พูดว่า: นาโนวินาทีถูกกำหนดให้เป็นหนึ่งในพันของไมโครวินาที, ไมโครวินาทีเป็นหนึ่งในพันของมิลลิวินาที, มิลลิวินาทีเป็นหนึ่งพันวินาที, นาทีเป็นหกสิบวินาที, หนึ่งชั่วโมงเป็นหกสิบนาที, และหนึ่งวันต่อวัน ยี่สิบสี่ชั่วโมง --- เนื่องจาก DST ทำให้ 2 วันของปีไม่ตรงกับ 24 ชั่วโมงTimeUnitทำให้เกิดความผิดพลาดเมื่อเกี่ยวข้องกับ DST
Andreas

1
ปัญหานี้เกิดขึ้นเฉพาะกับคอมพิวเตอร์ที่อยู่ในโซนเวลาที่มีการประหยัดเวลากลางวัน มันให้จำนวนวันที่ถูกต้อง (29) ในโซนเวลาที่ไม่มีการออมในเวลากลางวัน!
Gopinath

คำตอบ:


207

ปัญหาคือเนื่องจากการเลื่อนเวลาตามฤดูกาล (วันอาทิตย์ 8 มีนาคม 2020) มี28 วันและ 23 ชั่วโมงระหว่างวันที่เหล่านั้น TimeUnit.DAYS.convert(...) ตัดผลลัพธ์ให้เหลือ 28 วัน

หากต้องการดูปัญหา (ฉันอยู่ในเขตเวลาของสหรัฐอเมริกาตะวันออก):

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

เอาท์พุต

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

ในการแก้ไขให้ใช้เขตเวลาที่ไม่มี DST เช่น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();

เอาท์พุต

2505600000
Days: 29
Hours: 696
Days: 29.0

121
"การแก้ไข" ไม่ต้องทำคณิตศาสตร์ด้วยเวลา ใช้ไลบรารีวันที่ / เวลาที่เหมาะสม
Andy Turner

62
@AndyTurner หากต้องการแก้ไขให้ใช้ Java 7 APIs ในตัว เนื่องจากสามารถแก้ไขได้แสดงว่าคำตอบนั้นถูกต้องอย่างไร บังคับให้ใครบางคนรวมถึงห้องสมุดเต็มรูปแบบ (Joda-Time, ThreeTen, ฯลฯ ) เพียงทำเช่นนี้การคำนวณอย่างใดอย่างหนึ่งจะเกินความจริง แน่นอนว่าการใช้ห้องสมุดจะได้รับการแนะนำ แต่ไม่จำเป็นต้องใช้
Andreas

38
ฉันไม่เห็นด้วยอย่างเคารพ หากคุณต้องการคำนวณให้ทำถูกต้อง จ่ายค่าใช้จ่ายให้ถูกต้อง
Andy Turner

16
€ 0.02 ของฉัน: วิธี "ถูกต้อง" ใน Java 7 ที่ไม่มีไลบรารี่ภายนอกใด ๆ คือการใช้GregorianCalendarวัตถุและเพิ่มวันละ 1 วันจนกว่าจะถึงวันที่สิ้นสุด ฉันจะจ่ายราคาสูงเพื่อหลีกเลี่ยงสิ่งนั้น และการเพิ่มแบ็คพอร์ทของไลบรารีที่เป็นส่วนหนึ่งของ Java 8, 9, 10, 11, 12, 13, …นั้นไม่มีราคาสูง ในทางตรงกันข้ามครั้งต่อไปที่คุณต้องทำอะไรกับวันที่หรือเวลาก็จะได้รับ
Ole VV

27
แม้ใน UTC วันสุดท้ายของเดือนมิถุนายนบางครั้งก็สั้นเกินไปหนึ่งวินาทีโดยไม่มีการเตือนหรือการคาดการณ์ที่แท้จริง ใช้ไลบรารีวันที่เสมอ
Affe

41

สาเหตุของปัญหานี้เป็นที่กล่าวถึงแล้วในคำตอบของอันเดรีย

คำถามคือสิ่งที่คุณต้องการนับ ความจริงที่ว่าคุณระบุว่าความแตกต่างที่แท้จริงควรเป็น 29 แทนที่จะเป็น 28 และถามว่า"เวลา / สถานที่ตั้งอาจเป็นปัญหา"หรือไม่เผยให้เห็นสิ่งที่คุณต้องการนับ เห็นได้ชัดว่าคุณต้องการกำจัดความแตกต่างของเขตเวลาใด ๆ

ฉันถือว่าคุณต้องการคำนวณวันโดยไม่มีเวลาและเขตเวลา

Java 8

ด้านล่างในตัวอย่างวิธีคำนวณจำนวนวันระหว่างวันได้อย่างถูกต้องฉันกำลังใช้คลาสที่แสดงถึงวันที่ที่ไม่มีเวลาและเขตเวลา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);

โปรดทราบว่าChronoUnit, DateTimeFormatterและLocalDateต้องมีอย่างน้อย 8 Java ซึ่งไม่ได้มีให้คุณตามที่แท็ก อย่างไรก็ตามอาจเป็นเพื่อผู้อ่านในอนาคต

ตามที่กล่าวถึงโดย Ole VV นอกจากนี้ยังมีThreeTen Backportซึ่งรองรับฟังก์ชั่น Java 8 Date และ Time API ไปยัง Java 6 และ 7


2
@ OleV.V ฉันรู้ว่ามี ThreeTen ผู้ใช้บางคนอาจพูดถึงมันสองสามครั้ง (ฉันกำลังจะเชื่อมโยงไปยังแบบสอบถาม SEDE ที่ส่งคืนโพสต์และความคิดเห็นทั้งหมดของผู้ใช้ 5772882 ที่มีข้อความThreeTen;-) แต่น่าเสียดายที่ออฟไลน์ในขณะที่เขียน) ฉันจะอัปเดตโพสต์
พิธีกรจักรพรรดิ

2
@OleVV มันไม่ได้เลวร้ายอะไรเลย ฉันคิดว่าคนส่วนใหญ่ไม่รู้เพียงjava.timeเพราะที่โรงเรียนพวกเขายังคงใช้ชั้นเรียนเก่า แต่ Java 8 Date and Time API นั้นได้รับการออกแบบมาเป็นอย่างดี - มันจะเป็นการสูญเสียที่จะไม่ใช้มัน
พิธีกรจักรพรรดิ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.