Reputation: 17
I am trying to covert the exact amount of time between two Calendar objects in Java.
This is the code that I currently have...
public static Map<TimeUnit, Long> computeDifference(Calendar date1, Calendar date2) {
long diffInMillies = date2.getTimeInMillis() - date1.getTimeInMillis();
//create the list
List<TimeUnit> units = new ArrayList<TimeUnit>();
units.add(TimeUnit.SECONDS);
units.add(TimeUnit.MINUTES);
units.add(TimeUnit.HOURS);
units.add(TimeUnit.DAYS);
Collections.reverse(units);
//create the result map of TimeUnit and difference
Map<TimeUnit,Long> result = new LinkedHashMap<TimeUnit,Long>();
long milliesRest = diffInMillies;
for ( TimeUnit unit : units ) {
//calculate difference in millisecond
long diff = unit.convert(milliesRest,TimeUnit.MILLISECONDS);
long diffInMilliesForUnit = unit.toMillis(diff);
milliesRest = milliesRest - diffInMilliesForUnit;
//put the result in the map
result.put(unit,diff);
}
return result;
}
When printed, the output looks like this {DAYS=1, HOURS=10, MINUTES=30, SECONDS=45}
for input date1 = 19 August 2019 02:00:00
and date2 = 20 August 2019 12:30:45
The largest time unit available in this method is DAYS
, but I want to find something that includes both months and years. I realize that TimeUnit doesn't really have anything to do with specific calendar dates (rather a 24-hour interval), which is why I was wondering if there is any way to make this conversion using the Calendar class or something similar. I've also looked into ChronoUnit as a substitute for TimeUnit, but that won't work for the same reason TimeUnit doesn't.
Would love any suggestions for how to incorporate larger time units. Thank you!
Upvotes: 0
Views: 1128
Reputation: 22997
You shouldn't use the Calendar
class, since it's obsolete. You should use classes from the java.time
package instead.
In order to get the desired result, you could use the Period
class.
You first need to convert both Calendar
instances to LocalDate
instances.
Then you could use Period.between(startDate, endDate)
to get a Period
instance, which makes the getDays()
, getMonths()
and getYears()
methods available to you.
If you also want to include time components (hours, minutes and seconds), then you could use Duration
in combination with Period
. But then first read the post linked to by Sweeper.
Something like this would probably work:
LocalDateTime start = LocalDateTime.of(2019, 1, 1, 12, 0, 0);
LocalDateTime end = LocalDateTime.of(2021, 4, 26, 5, 56, 40);
Duration d = Duration.between(start.toLocalTime(), end.toLocalTime());
Period p = Period.between(start.toLocalDate(), end.toLocalDate());
// If the startdate's time component lies behind the enddate's time component,
// then we need to correct both the Period and Duration
if (d.isNegative()) {
p = p.minusDays(1);
d = d.plusDays(1);
}
System.out.printf("y %s m %s d %s h %s m %s s %s%n",
p.getYears(),
p.getMonths(),
p.getDays(),
d.toHours() % 24,
d.toMinutes() % 60,
d.getSeconds() % 60);
Note that Java 9 comes with to…Part
methods, so you don't have to use the modulo operator anymore.
Be advised: this code does not take into account clock adjustments due to daylight savings time.
Upvotes: 5
Reputation: 338795
Period
.between(
( ( GregorianCalendar ) myCalStart ).toZonedDateTime().toLocalDate() ,
( ( GregorianCalendar ) myCalStop ).toZonedDateTime().toLocalDate()
)
…or…
Duration
.between(
( ( GregorianCalendar ) myCalStart ).toInstant() ,
( ( GregorianCalendar ) myCalStop ).toInstant()
)
You are using terrible date-time classes that were supplanted years ago by the modern java.time classes defined in JSR 310.
Never use Calendar
, GregorianCalendar
, Date
, SimpleDateFormat
, and such. Use only the classes found in the java.time packages.
ZonedDateTime
Assuming both your Calendar
objects are actually GregorianCalendar
objects underneath, convert. To convert, call new to…
/from…
methods added to the old classes.
// start
GregorianCalendar gcStart = ( GregorianCalendar ) myCalStart ;
ZonedDateTime zdtStart = gcStart.toZonedDateTime() ;
// stop
GregorianCalendar gcStop = ( GregorianCalendar ) myCalStop ;
ZonedDateTime zdtStop = gcStop.toZonedDateTime() ;
Both GregorianCalendar
and ZonedDateTime
represent a date with a time-of-day placed in the context of a time zone, combined to determine a moment (a specific point on the timeline). ZonedDateTime
resolves to a finer level of nanoseconds rather than milliseconds.
Period
If you care about elapsed time in terms of years-months-days, use Period
with LocalDate
objects. LocalDate
represents a date without a time-of-day and without a time zone. We can extract the date portion from our ZonedDateTime
objects.
LocalDate ldStart = zdtStart.toLocalDate() ;
LocalDate ldStop = zdtStop.toLocalDate() ;
Period p = Period.between( ldStart , ldStop ) ;
Generate a string in standard ISO 8601 format.
String output = p.toString() ;
Interrogate for a count of years, months, days.
int years = p.getYears() ;
int months = p.getMonths() ;
int days = p.getDays() ;
Duration
If you cane about elapsed time in terms of hours-minutes-seconds, use Duration
. This class represent a pair of moments in UTC. So we extract Instant
objects from our pair of ZonedDateTime
objects, which internally keep a count of whole seconds since the epoch reference of first moment of 1970 in UTC, plus a fractional second as a count of nanoseconds.
Instant instantStart = zdtStart.toInstant() ;
Instant instantStop = zdtStop.toInstant() ;
Duration d = Duration.between( instantStart , instantStop ) ;
Generate a string in standard ISO 8601 format.
String output = d.toString() ;
Interrogate for a count of days (as 24-hour chunks of time unrelated to the calendar), hours, minutes, seconds.
long days = d.toDaysPart() ;
int hours = d.toHoursPart() ;
int minutes = d.toMinutesPart() ;
int seconds = d.toSecondsPart() ;
int nanos = d.toNanosPart() ;
PeriodDuration
If you think about it, you will see that it does not make sense to combine years/months/days with 24-hour-days/hours/minutes/seconds. See this Stack Overflow page as food for thought.
But if you insist on combining these two different concepts, see the PeriodDuration
class found in the ThreeTen-Extra library.
Upvotes: 2