How can I find the difference between two dates in JDK7, such that the difference can be added back to make the two dates equal?
I tried to use the solution from a similar StackOverflow question , subtracting one date's milliseconds from the other, but this can result in an incorrect difference between dates.
In this example, I try to make a
equal to b
by setting a = (ba) + a
private void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b, DatatypeFactory datatypeFactory) {
long b_minus_a = b.getTimeInMillis() - a.getTimeInMillis();
Duration delta = datatypeFactory.newDuration(b_minus_a);
delta.addTo(a);
}
But the results are unexpected:
Before
a = "2015-08-29T00:00:00.000Z"
b = "2040-01-01T00:00:00.000Z"
delta = "P24Y4M5DT0H0M0.000S" // 2 days longer than it should be.
After
a = "2040-01-03T00:00:00.000Z"
b = "2040-01-01T00:00:00.000Z"
delta = "P24Y4M5DT0H0M0.000S"
a.equals(b) = false
Joda-Time has this same issue:
private void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b) {
DateTime date1 = new DateTime(a);
DateTime date2 = new DateTime(b);
Interval interval = new Interval(a.getTimeInMillis(), b.getTimeInMillis());
Period offsetPeriod = interval.toPeriod();
date1 = date1.plus(offsetPeriod);
date1.equals(date2); // false in my example
}
Before
date1 = "2015-08-29T00:00:00.000-05:00"
date2 = "2040-01-01T00:00:00.000-05:00"
offsetPeriod = "P24Y4M2DT23H"
date1.equals(date2) = false
After
date1 = "2039-12-31T23:00:00.000-05:00"
date2 = "2040-01-01T00:00:00.000-05:00"
offsetPeriod = "P24Y4M2DT23H"
date1.equals(date2) = false
Before it's asked, there should be no need to account for daylight savings or leap years because in each example I'm adding the interval back over the exact same time period from which it was derived.
There are a couple of possibilities using the modern Java date and time API known as JSR-310 or java.time
. Can you use this with Java 7? Certainly! Get ThreeTen Backport and start coding. :-)
EDIT: If you want to perceive your a
and b
as points in time, in other words, both dates and times of day, you may use either a suitably small time unit for the difference, or the Duration
class. Both will work the same. Since Instant
s have nanosecond precision, calculating a difference in nanos will make sure we don't lose any precision:
Instant a = Instant.parse("2015-08-29T00:00:00.000Z");
Instant b = Instant.parse("2040-01-01T00:00:00.000Z");
long delta = ChronoUnit.NANOS.between(a, b);
a = a.plusNanos(delta);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("delta = " + delta);
System.out.println("a.equals(b) = " + a.equals(b));
This prints
a = 2040-01-01T00:00:00Z
b = 2040-01-01T00:00:00Z
delta = 768182400000000000
a.equals(b) = true
The Duration
class is meant for durations in hours, minutes, seconds and fraction of second. You can use it for longer durations, but it doesn't know about weeks, months or years.
Duration delta = Duration.between(a, b);
a = a.plus(delta);
Otherwise as before. This time the output is
a = 2040-01-01T00:00:00Z
b = 2040-01-01T00:00:00Z
delta = PT213384H
a.equals(b) = true
So the difference is 213384 hours. Only because the minutes and seconds are 0 in this case, they are not printed, otherwise they would be; Duration
too has nanosecond precision.
Taking your title more literally and noticing that your example dates fall on midnight UTC. If you don't care about the time of day and only want to count days, months and years, you may use either the Period
class or simply count days. The Period
class is for periods of days, months and years and has 1 day precision (that means, cannot be used for hours and smaller units).
LocalDate a = LocalDate.of(2015, Month.AUGUST, 29);
LocalDate b = LocalDate.of(2040, Month.JANUARY, 1);
Period delta = Period.between(a, b);
a = a.plus(delta);
The printing goes as before, but now it prints
a = 2040-01-01
b = 2040-01-01
delta = P24Y4M3D
a.equals(b) = true
Counting days goes just like nanoseconds above, only with days instead:
long delta = ChronoUnit.DAYS.between(a, b);
a = a.plusDays(delta);
This time the output contains:
delta = 8891
a.equals(b) = true
Please note that because months haven't got the same length, even though 24 years 4 months 3 days seems to equal 8891 days, this will more often not be the exact case. This is explained in more detail in @Meno Hochschild's comment and Hugo's answer . So the choice between a Period
and a number of days will make a difference, and you should choose based on your more exact requirements.
It's a bit funny that Java doesn't offer a class for a period or duration of years, months, days, hour, minutes and seconds. If you wanted, you might use a Period
instance for the years, months and days, and then a Duration
for the hours, minutes and seconds. It's a little bit complicated, so I am not writing the code unless you say you need it, but it is viable. You may of course also work out the details yourself.
I am told that the ThreeTen-extra project includes a PeriodDuration
class that combines the two. I haven't got any experience with it myself.
As @Meno explained in the comments , date arithmetic is weird, because months and years can have different lenghts. In our ISO calendar, a month can have 28, 29, 30 or 31 days, and a year can have 365 or 366 days.
So, a Period
like P24Y4M5D
(24 years, 4 months and 5 days) can represent a totally different number of days, depending on when you start counting it.
If I start at 2017-01-01T00:00Z
, for example. If I add 24 years, I get 2041-01-01
, then adding 4 months, I get 2041-05-01
, then adding 5 days I get 2041-05-06
. The total difference is equivalent to 8891 days.
But if I start at 2016-01-01T00:00Z
and add the same period (24 years, 4 months and 5 days): adding 24 years I get 2040-01-01
, then adding for 4 months I get 2040-05-01
, then adding 5 days I get 2040-05-06
. But if I add 8891 days, I get 2040-05-05
.
That's because a month and year don't have fixed lenghts, so the equivalent total in days will always depend on the dates involved (not to mention Daylight Saving Time and leap seconds effects, which also changes the total of hours, minutes, seconds and milliseconds).
To not rely on that, you must calculate using the duration in milliseconds (and not in non-fixed-length variables such as years and months). In Joda-Time, you can use the org.joda.time.Duration
class:
public void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b) {
DateTime date1 = new DateTime(a);
DateTime date2 = new DateTime(b);
System.out.println("date1=" + date1);
System.out.println("date2=" + date2);
Duration duration = new Duration(date1, date2);
DateTime date3 = date1.plus(duration);
System.out.println("date3=" + date3);
System.out.println(date3.equals(date2));
}
Assuming that a
is equivalent to 2015-08-29T00:00:00Z
and b
is 2040-01-01T00:00:00Z
, this code will output:
date1=2015-08-29T00:00:00.000Z
date2=2040-01-01T00:00:00.000Z
date3=2040-01-01T00:00:00.000Z
true
Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310)." .
If you can't (or don't want to) migrate from Joda-Time to the new API, you can ignore this section.
If you're using Java 6 or 7 , you can use the ThreeTen Backport , a great backport for Java 8's new date/time classes. And for Android , you'll also need the ThreeTenABP (more on how to use it here ).
You can use the org.threeten.bp.DateTimeUtils
class to convert the GregorianCalendar
to org.threeten.bp.Instant
and then calculate the difference between them, resulting in a org.threeten.bp.Duration
:
public void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b) {
Instant instantA = DateTimeUtils.toInstant(a);
Instant instantB = DateTimeUtils.toInstant(b);
System.out.println(instantA);
System.out.println(instantB);
Duration duration = Duration.between(instantA, instantB);
Instant instantC = instantA.plus(duration);
System.out.println(instantC);
System.out.println(instantC.equals(instantB)); // true
}
Assuming that a
is equivalent to 2015-08-29T00:00:00Z
and b
is 2040-01-01T00:00:00Z
, this code will output:
2015-08-29T00:00:00Z
2040-01-01T00:00:00Z
2040-01-01T00:00:00Z
true
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.