mlb
mlb

Reputation: 17

How would I convert the exact amount of time (years, months, days, hours, minutes, seconds) between two Calendar objects?

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

Answers (2)

MC Emperor
MC Emperor

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

Basil Bourque
Basil Bourque

Reputation: 338795

tl;dr

Period
.between(
    ( ( GregorianCalendar ) myCalStart ).toZonedDateTime().toLocalDate() ,
    ( ( GregorianCalendar ) myCalStop ).toZonedDateTime().toLocalDate() 
)

…or…

Duration
.between(
    ( ( GregorianCalendar ) myCalStart ).toInstant() ,
    ( ( GregorianCalendar ) myCalStop ).toInstant() 
)

java.time

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.


Table of date-time types in Java, both modern and legacy.

Upvotes: 2

Related Questions