Defozo
Defozo

Reputation: 3092

How to get time difference between two ZonedDateTimes and pretty print it like "4 hours, 1 minute, 40 seconds ago"?

This is how I call getTimeBetween function:

getTimeBetween(ZonedDateTime.now().minusHours(4).minusMinutes(1).minusSeconds(40), ZonedDateTime.now());

And I expect this output:

4 hours, 1 minute, 40 seconds ago

This is my getTimeBetween function:

private String getTimeBetween(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
    Duration timeDifference = Duration.between(zonedDateTime1, zonedDateTime2);
    if (timeDifference.getSeconds() == 0) return "now";
    String timeDifferenceAsPrettyString = "";
    Boolean putComma = false;
    if (timeDifference.toDays() > 0) {
        if (timeDifference.toDays() == 1) timeDifferenceAsPrettyString += timeDifference.toDays() + " day";
        else timeDifferenceAsPrettyString += timeDifference.toDays() + " days";
        putComma = true;
    }
    if (timeDifference.toHours() > 0) {
        if (putComma) timeDifferenceAsPrettyString += ", ";
        if (timeDifference.toHours() == 1) timeDifferenceAsPrettyString += timeDifference.toHours() + " hour";
        else timeDifferenceAsPrettyString += timeDifference.toHours() % 24 + " hours";
        putComma = true;
    }
    if (timeDifference.toMinutes() > 0) {
        if (putComma) timeDifferenceAsPrettyString += ", ";
        if (timeDifference.toMinutes() == 1) timeDifferenceAsPrettyString += timeDifference.toMinutes() + " minute";
        else timeDifferenceAsPrettyString += timeDifference.toMinutes() % 60 + " minutes";
        putComma = true;
    }
    if (timeDifference.getSeconds() > 0) {
        if (putComma) timeDifferenceAsPrettyString += ", ";
        if (timeDifference.getSeconds() == 1) timeDifferenceAsPrettyString += timeDifference.getSeconds() + " second";
        else timeDifferenceAsPrettyString += timeDifference.getSeconds() % 60 + " seconds";
    }
    timeDifferenceAsPrettyString += " ago";
    return timeDifferenceAsPrettyString;
}

This function works as expected but is it really necessary to do it like this? Perhaps there is a better way to achieve this?

I'm using Java 8.

Upvotes: 0

Views: 242

Answers (2)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78935

You can use java.time.Duration which is modelled on ISO-8601 standards and was introduced as part of JSR-310 implementation. With Java-9 some more convenience methods were introduced.

Demo:

public class Main {
    public static void main(String[] args) {
        Duration duration = Duration.between(
                ZonedDateTime.now().minusHours(4).minusMinutes(1).minusSeconds(40),
                ZonedDateTime.now()
        );
        System.out.println(formatDuration(duration));

        // Example for '... later'
        duration = Duration.between(
                ZonedDateTime.now().plusHours(4).plusMinutes(1).plusSeconds(40),
                ZonedDateTime.now()
        );
        System.out.println(formatDuration(duration));
    }

    static String formatDuration(Duration duration) {
        //################# Java 9 onwards ###################
        String formatted = List.of(fmtHr(Math.abs(duration.toHoursPart())),
                fmtMin(Math.abs(duration.toMinutesPart())),
                fmtSec(Math.abs(duration.toSecondsPart()))
        ).stream().collect(Collectors.joining(", "));
        //#####################################################

        return formatted += duration.toHoursPart() >= 0 ? " ago." : " later.";
    }

    static String fmtHr(long hr) {
        return String.format("%d %s", hr, hr <= 1 ? "hour" : "hours");
    }

    static String fmtMin(long min) {
        return String.format("%d %s", min, min <= 1 ? "minute" : "minutes");
    }

    static String fmtSec(long sec) {
        return String.format("%d %s", sec, sec <= 1 ? "second" : "seconds");
    }
}

Output:

4 hours, 1 minute, 40 seconds ago.
4 hours, 1 minute, 40 seconds later.

Online Demo

Note: List#of and parts API for Duration e.g. Duration#toHoursPart are available since Java 9. If you are working with Java 8, you can use duration.toHours(), duration.toMinutes() % 60 and duration.toSeconds() % 60 with the Duration class and add elements to the ArrayList individually.

Learn about the modern date-time API from Trail: Date Time.

Upvotes: 0

Salem
Salem

Reputation: 14887

How about this?

static String getTimeBetween(ZonedDateTime from, ZonedDateTime to) {
    StringBuilder builder = new StringBuilder();
    long epochA = from.toEpochSecond(), epochB = to.toEpochSecond();
    long secs = Math.abs(epochB - epochA);
    if (secs == 0) return "now";
    Map<String, Integer> units = new LinkedHashMap<>();
    units.put("day", 86400);
    units.put("hour", 3600);
    units.put("minute", 60);
    units.put("second", 1);
    boolean separator = false;
    for (Map.Entry<String, Integer> unit : units.entrySet()) {
        if (secs >= unit.getValue()) {
            long count = secs / unit.getValue();
            if (separator) builder.append(", ");
            builder.append(count).append(' ').append(unit.getKey());
            if (count != 1) builder.append('s');
            secs %= unit.getValue();
            separator = true;
        }
    }
    return builder.append(epochA > epochB ? " ago" : " in the future").toString();
}

You could probably store the LinkedHashMap instead of instantiating it every method call, but this should work.

Upvotes: 1

Related Questions