1. Overview
1.概述
In this quick tutorial, we’ll explore multiple possibilities of calculating the difference between two dates in Java.
在这个快速教程中,我们将探索在Java中计算两个日期之差的多种可能性。
2. Core Java
2.Core Java
2.1. Using java.util.Date to Find the Difference in Days
2.1.使用java.util.Date查找日差
Let’s start by using the core Java APIs to do the calculation and determine the number of days between the two dates:
让我们先用核心的Java APIs来进行计算,确定两个日期之间的天数。
@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. Using java.time.temporal.ChronoUnit to Find the Difference
2.2.使用java.time.temporal.ChronoUnit来寻找差异
The Time API in Java 8 represents a unit of date-time, e.g. seconds or days, using TemporalUnit interface.
Java 8中的时间API使用TemporalUnit接口表示日期时间的单位,例如秒或天。
Each unit provides an implementation for a method named between to calculate the amount of time between two temporal objects in terms of that specific unit.
每个单元都提供了一个名为between的方法的实现,以计算两个时间对象在该特定单元方面的时间量。。
For example, in order to calculate the seconds between two LocalDateTimes:
例如,为了计算两个LocalDateTimes之间的秒钟。
@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 provides a set of concrete time units by implementing the TemporalUnit interface. It’s highly recommended to static import the ChronoUnitenum values to achieve better readability:
ChronoUnit通过实现TemporalUnit接口提供了一组具体的时间单位。强烈建议静态导入ChronoUnitenum值,以实现更好的可读性。
import static java.time.temporal.ChronoUnit.SECONDS;
// omitted
long diff = SECONDS.between(now, tenSecondsLater);
Also, we can pass any two compatible temporal objects to the between method, even the ZonedDateTime.
另外,我们可以向between方法传递任何两个兼容的时间对象,甚至是ZonedDateTime。
What’s great about ZonedDateTime is that the calculation will work even if they are set to different time zones:
ZonedDateTime的伟大之处在于,即使它们被设置为不同的时区,该计算也会有效。
@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. Using Temporal#until()
2.3.使用Temporal#until()
Any Temporal object, such as LocalDate or ZonedDateTime,provides an until method to calculate the amount of time until another Temporal in terms of the specified unit:
任何Temporal对象,例如LocalDate或ZonedDateTime,提供一个<a href=”https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/temporal/Temporal.html#until(java.time.temporal.Temporal,java.time.temporalUnit)>until方法来计算到另一个Temporal>的时间量,以指定单位为单位。
@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);
}
The Temporal#until and TemporalUnit#between are two different APIs for the same functionality.
Temporal#until和TemporalUnit#between是相同功能的两个不同的API。
2.4. Using java.time.Duration and java.time.Period
2.4.使用java.time.Duration和java.time.Period
In Java 8, the Time API introduced two new classes: Duration and Period.
在Java 8中,时间API引入了两个新的类: Duration 和Period。
If we want to calculate the difference between two date-times in a time-based (hour, minutes, or seconds) amount of time, we can use the Duration class:
如果我们想计算两个日期时间在基于时间(小时、分钟或秒)的数量上的差异,我们可以使用Duration class。
@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);
}
However, we should be wary of a pitfall if we try using the Period class to represent the difference between two dates.
然而,如果我们尝试使用Period类来表示两个日期之间的差异,我们应该警惕一个陷阱。
An example will explain this pitfall quickly.
一个例子可以快速解释这个陷阱。
Let’s calculate how many days between two dates using the Period class:
让我们使用Period类来计算两个日期之间有多少天。
@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);
}
If we run the test above, it’ll pass. We may think the Period class is convenient to solve our problem. So far, so good.
如果我们运行上面的测试,它就会通过。我们可能认为Period类很方便解决我们的问题。到目前为止,还不错。
If this way works with a difference of six days, we don’t doubt that it’ll work for 60 days as well.
如果这种方式在相差6天的情况下有效,我们不怀疑它在60天内也会有效。
So let’s change the 6 in the test above to 60 and see what happens:
因此,让我们把上面的测试中的6改为60,看看会发生什么。
@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);
}
Now if we run the test again, we’ll see:
现在,如果我们再次运行测试,我们会看到。
java.lang.AssertionError:
Expected :60
Actual :29
Oops! Why did the Period class report the difference as 29 days?
哎呀!为什么Period类报告的差异是29天?
This is because the Period class represents a date-based amount of time in the format of “x years, y months and z days”. When we call its getDays() method, it returns only the “z days” part.
这是因为Period类以 “x年、y月和z天 “的格式表示基于日期的时间量。当我们调用其getDays()方法时,它只返回 “z天 “部分。
Therefore, the period object in the test above holds the value “0 years, 1 month and 29 days”:
因此,上述测试中的period对象持有 “0年1个月29天 “的值。
@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 });
}
If we want to calculate the difference in days using Java 8’s Time API, the ChronoUnit.DAYS.between() method is the most straightforward way.
如果我们想用Java 8的时间API来计算天数的差异,ChronoUnit.DAYS.between() 方法是最直接的方法。
3. External Libraries
3.外部库
3.1. JodaTime
3.1.JodaTime
We can also do a relatively straightforward implementation with JodaTime:
我们也可以用JodaTime做一个相对简单的实现。
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
You can get the latest version of Joda-time from Maven Central.
你可以从Maven中心获得最新版本的Joda-time。
LocalDate case:
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);
}
Similarly, with LocalDateTime:
同样地,用LocalDateTime。
@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
3.2.Date4J
Date4j also provides a straightforward and simple implementation — noting that, in this case, we need to explicitly provide a TimeZone.
Date4j也提供了一个直接而简单的实现–注意,在这种情况下,我们需要明确提供一个TimeZone。
Let’s start with the Maven dependency:
让我们从Maven的依赖性开始。
<dependency>
<groupId>com.darwinsys</groupId>
<artifactId>hirondelle-date4j</artifactId>
<version>1.5.1</version>
</dependency>
Here’s a quick test working with the standard DateTime:
下面是一个使用标准DateTime的快速测试。
@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. Conclusion
4.结论
In this article, we illustrated a few ways of calculating the difference between dates (with and without time), both in plain Java as well as using external libraries.
在这篇文章中,我们说明了一些计算日期(有时间和无时间)之差的方法,既可以用普通的Java,也可以使用外部库。