Sự khác biệt giữa hai ngày trong Java

1. Khái quát chung

Trong phần viết nhanh này, chúng ta sẽ khám phá nhiều khả năng tính toán sự khác biệt giữa hai ngày trong Java.

2. Core Java

2.1. Sử dụng java.util.Date để tìm ra sự khác biệt trong ngày

Hãy bắt đầu bằng cách sử dụng các API Java cốt lõi để tính toán và xác định số ngày giữa hai ngày:

@Test public void givenTwoDatesBeforeJava8_whenDifferentiating_thenWeGetSix() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH); Date firstDate = sdf.parse("06/24/2017"); Date secondDate = sdf.parse("06/30/2017"); long diffInMillies = Math.abs(secondDate.getTime() - firstDate.getTime()); long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS); assertEquals(6, diff); }

2.2. Sử dụng java.time.temporal.ChronoUnit để tìm ra sự khác biệt

API Thời gian trong Java 8 đại diện cho một đơn vị ngày-giờ, ví dụ như giây hoặc ngày, sử dụng giao diện TemporalUnit . Mỗi đơn vị cung cấp một triển khai cho một phương pháp được đặt tên giữa để tính lượng thời gian giữa hai đối tượng thời gian theo đơn vị cụ thể đó .

Ví dụ: để tính số giây giữa hai LocalDateTime s:

@Test public void givenTwoDateTimesInJava8_whenDifferentiatingInSeconds_thenWeGetTen() { LocalDateTime now = LocalDateTime.now(); LocalDateTime tenSecondsLater = now.plusSeconds(10); long diff = ChronoUnit.SECONDS.between(now, tenSecondsLater); assertEquals(10, diff); }

ChronoUnit cung cấp một tập hợp các đơn vị thời gian cụ thể bằng cách triển khai giao diện TemporalUnit . Bạn nên nhập tĩnh các giá trị enum ChronoUnit để đạt được khả năng đọc tốt hơn:

import static java.time.temporal.ChronoUnit.SECONDS; // omitted long diff = SECONDS.between(now, tenSecondsLater);

Ngoài ra, chúng ta có thể chuyển bất kỳ hai đối tượng tạm thời tương thích nào vào phương thức giữa , thậm chí là ZonedDateTime .

Điều tuyệt vời về ZonedDateTime là tính toán sẽ hoạt động ngay cả khi chúng được đặt thành các múi giờ khác nhau:

@Test public void givenTwoZonedDateTimesInJava8_whenDifferentiating_thenWeGetSix() { LocalDateTime ldt = LocalDateTime.now(); ZonedDateTime now = ldt.atZone(ZoneId.of("America/Montreal")); ZonedDateTime sixMinutesBehind = now .withZoneSameInstant(ZoneId.of("Asia/Singapore")) .minusMinutes(6); long diff = ChronoUnit.MINUTES.between(sixMinutesBehind, now); assertEquals(6, diff); } 

2.3. Sử dụng java.time.temporal.Temporal # cho đến khi ()

Bất kỳ đối tượng Temporal nào , chẳng hạn như LocalDate hoặc ZonedDateTime, cung cấp phương thức cho đến khi tính lượng thời gian cho đến khi Temporal khác theo đơn vị được chỉ định:

@Test public void givenTwoDateTimesInJava8_whenDifferentiatingInSecondsUsingUntil_thenWeGetTen() { LocalDateTime now = LocalDateTime.now(); LocalDateTime tenSecondsLater = now.plusSeconds(10); long diff = now.until(tenSecondsLater, ChronoUnit.SECONDS); assertEquals(10, diff); }

Các Temporal # cho đến khiTemporalUnit # giữa hai API khác nhau cho các chức năng tương tự.

2.4. Sử dụng java.time.Durationjava.time.Period

Trong Java 8, API Thời gian giới thiệu hai lớp nhân vật mới: Thời gianThời gian .

Nếu chúng ta muốn tính toán chênh lệch giữa hai ngày-giờ trong khoảng thời gian dựa trên thời gian (giờ, phút hoặc giây), chúng ta có thể sử dụng lớp Thời lượng :

@Test public void givenTwoDateTimesInJava8_whenDifferentiating_thenWeGetSix() { LocalDateTime now = LocalDateTime.now(); LocalDateTime sixMinutesBehind = now.minusMinutes(6); Duration duration = Duration.between(now, sixMinutesBehind); long diff = Math.abs(duration.toMinutes()); assertEquals(6, diff); }

Tuy nhiên, chúng ta nên cảnh giác với một cạm bẫy nếu chúng ta cố gắng sử dụng lớp Giai đoạn để biểu thị sự khác biệt giữa hai ngày .

Một ví dụ sẽ giải thích nó một cách nhanh chóng. Hãy tính xem có bao nhiêu ngày giữa hai ngày bằng cách sử dụng lớp Giai đoạn :

@Test public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenWorks() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixDaysBehind = aDate.minusDays(6); Period period = Period.between(aDate, sixDaysBehind); int diff = Math.abs(period.getDays()); assertEquals(6, diff); }

Nếu chúng tôi chạy thử nghiệm ở trên, nó sẽ vượt qua. Chúng tôi có thể nghĩ rằng lớp Giai đoạn là thuận tiện để giải quyết vấn đề của chúng tôi. Càng xa càng tốt.

Nếu cách này hiệu quả với chênh lệch sáu ngày, chúng tôi sẽ không nghi ngờ rằng nó cũng sẽ hiệu quả trong 60 ngày.

Tiếp theo, hãy thay đổi 6 trong thử nghiệm ở trên thành 60 và xem điều gì sẽ xảy ra:

@Test public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenDoesNotWork() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixtyDaysBehind = aDate.minusDays(60); Period period = Period.between(aDate, sixtyDaysBehind); int diff = Math.abs(period.getDays()); assertEquals(60, diff); }

Bây giờ, nếu chúng tôi chạy lại kiểm tra, chúng tôi sẽ thấy:

java.lang.AssertionError: Expected :60 Actual :29

Giáo sư! Tại sao Thời gian lớp báo cáo sự khác biệt như 29 ngày?

Điều này là do lớp Giai đoạn biểu thị lượng thời gian dựa trên ngày ở định dạng " x năm, y tháng và z ngày ". Khi chúng ta gọi phương thức getDays () của nó, nó chỉ trả về phần “ z days ”.

Do đó, đối tượng chu kỳ trong phép thử ở trên giữ giá trị: “ 0 năm, 1 tháng và 29 ngày ”:

@Test public void givenTwoDatesInJava8_whenUsingPeriod_thenWeGet0Year1Month29Days() { LocalDate aDate = LocalDate.of(2020, 9, 11); LocalDate sixtyDaysBehind = aDate.minusDays(60); Period period = Period.between(aDate, sixtyDaysBehind); int years = Math.abs(period.getYears()); int months = Math.abs(period.getMonths()); int days = Math.abs(period.getDays()); assertArrayEquals(new int[] { 0, 1, 29 }, new int[] { years, months, days }); }

Nếu chúng ta muốn tính toán sự khác biệt trong số ngày bằng cách sử dụng API thời gian của Java 8, thì phương thức ChronoUnit.DAYS.between () là cách đơn giản nhất.

3. Thư viện bên ngoài

3.1. JodaTime

Chúng tôi cũng có thể thực hiện triển khai tương đối đơn giản với JodaTime :

 joda-time joda-time 2.9.9 

Bạn có thể tải phiên bản mới nhất của Joda-time từ Maven Central.

Trường hợp LocalDate :

@Test public void givenTwoDatesInJodaTime_whenDifferentiating_thenWeGetSix() { org.joda.time.LocalDate now = org.joda.time.LocalDate.now(); org.joda.time.LocalDate sixDaysBehind = now.minusDays(6); long diff = Math.abs(Days.daysBetween(now, sixDaysBehind).getDays()); assertEquals(6, diff); } 

Tương tự, với LocalDateTime, nó là:

@Test public void givenTwoDateTimesInJodaTime_whenDifferentiating_thenWeGetSix() { org.joda.time.LocalDateTime now = org.joda.time.LocalDateTime.now(); org.joda.time.LocalDateTime sixMinutesBehind = now.minusMinutes(6); long diff = Math.abs(Minutes.minutesBetween(now, sixMinutesBehind).getMinutes()); assertEquals(6, diff); } 

3.2. Date4J

Date4j cũng cung cấp một triển khai đơn giản và dễ hiểu, không cần lưu ý rằng, trong trường hợp này, chúng tôi cần cung cấp TimeZone một cách rõ ràng .

Hãy bắt đầu với sự phụ thuộc của Maven:

 com.darwinsys hirondelle-date4j 1.5.1 

Đây là một thử nghiệm nhanh làm việc với DateTime tiêu chuẩn :

@Test public void givenTwoDatesInDate4j_whenDifferentiating_thenWeGetSix() { DateTime now = DateTime.now(TimeZone.getDefault()); DateTime sixDaysBehind = now.minusDays(6); long diff = Math.abs(now.numDaysFrom(sixDaysBehind)); assertEquals(6, diff); }

4. Kết luận

Trong hướng dẫn này, chúng tôi đã minh họa một số cách tính toán sự khác biệt giữa các ngày (có và không có thời gian), cả trong Java thuần túy cũng như sử dụng các thư viện bên ngoài.

Mã nguồn đầy đủ của bài viết hiện có trên GitHub.