DeanTwit
DeanTwit

Reputation: 335

Calculate duration between two hours in Java

I want to calculate the time difference (duration) between two hours (HH:mm:ss) in Java. Here, I've read several topics on this subject, but my problem is a little bit different.

I'm not able to use Joda-Time, as well.

Example:

input values:    12:03:00 
                 00:00:00

expected output: 11:57:00

Сode:

public static void main(String[] args) throws ParseException {

    Scanner sc = new Scanner(System.in);
    String startTime = sc.next();
    String endTime = sc.next();

    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    Date d1 = sdf.parse(startTime);
    Date d2 = sdf.parse(endTime);
    long elapsed = d2.getTime() - d1.getTime();

    String hms = String.format("%02d:%02d:%02d",
        TimeUnit.MILLISECONDS.toHours(elapsed),
        TimeUnit.MILLISECONDS.toMinutes(elapsed) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(elapsed)),
        TimeUnit.MILLISECONDS.toSeconds(elapsed) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(elapsed)));

    if (elapsed > 0) {
        System.out.println(hms);
    } else {

        elapsed = elapsed * (-1); //otherwise, print hours with '-'

        String hms1 = String.format("%02d:%02d:%02d",
            TimeUnit.MILLISECONDS.toHours(elapsed),
            TimeUnit.MILLISECONDS.toMinutes(elapsed) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(elapsed)),
            TimeUnit.MILLISECONDS.toSeconds(elapsed) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(elapsed)));

        System.out.println(hms1);
    }
}

Result:

expected output: 11:57:00

actual output:   12:03:00 //that's the problem

Upvotes: 0

Views: 5481

Answers (4)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79075

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

Demo:

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        LocalTime startTime = LocalTime.parse("12:03:00");
        LocalTime endTime = LocalTime.parse("00:00:00");

        LocalDateTime startDateTime = today.atTime(startTime);
        LocalDateTime endDateTime = today.atTime(endTime);

        if (startDateTime.isAfter(endDateTime)) {
            endDateTime = endDateTime.with(LocalTime.MIN).plusDays(1).with(endTime);
        }

        Duration duration = Duration.between(startDateTime, endDateTime);
        // Default format
        System.out.println(duration);

        // Custom format
        // ####################################Java-8####################################
        String formattedDuration = String.format("%02d:%02d:%02d", duration.toHours(), duration.toMinutes() % 60,
                duration.toSeconds() % 60);
        System.out.println(formattedDuration);
        // ##############################################################################

        // ####################################Java-9####################################
        formattedDuration = String.format("%02d:%02d:%02d", duration.toHoursPart(), duration.toMinutesPart(),
                duration.toSecondsPart());
        System.out.println(formattedDuration);
        // ##############################################################################
    }
}

Output:

PT11H57M
11:57:00
11:57:00

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

Upvotes: 0

Basil Bourque
Basil Bourque

Reputation: 338516

tl;dr

Duration.between( 
    LocalTime.parse( "12:03:00" ) , 
    LocalTime.parse( "23:59:59.999999999" ) 
).plusNanos( 1 ).withNanos( 0 ) 

PT11H57M

Use a nanosecond

The modern approach uses the java.time classes.

The catch is that for time-of-day only, there is no midnight. So your 00:00 which you apparently intended for end-of-day is actually interpreted as start-of-day.

There is only the last nanosecond before the day ends. The LocalTime has a constant defined for that last nanosecond: LocalTime.MAX = 23:59:59.999999999

Since you care only about whole seconds, we can take advantage of that fractional second. If your ending time happens to be 00:00:00, substitute LocalTime.MAX. Then calculate a Duration object. You can add a single nanosecond and then truncate the resulting fractional second by setting the fractional second (the nanoseconds) to zero.

For ending times other than 00:00:00, the math still works. Adding a nanosecond gets you ….000000001 fraction of second, and Duration::withNanos will truncate that unwanted fraction.

// This code assumes the inputs are *always* in whole seconds, without any fraction-of-second.

LocalTime start = LocalTime.parse( "12:03:00" );
LocalTime stop = LocalTime.parse( "00:00:00" );

if( stop.equals( LocalTime.MIN ) ) {
    stop = LocalTime.MAX ; // `23:59:59.999999999`
}

Duration d = Duration.between( start , stop );
d = d.plusNanos( 1 ).withNanos( 0 ) ;

System.out.println( "start/stop: " + start + "/" + stop );
System.out.println( "d: " + d );

See this code run live at IdeOne.com.

PT11H57M

Formatting

The output from toString is standard ISO 8601 format. The Duration class can parse such strings as well as generate them.

I strongly recommend not representing a span of time in time-of-day style, HH:MM:SS. This is ambiguous and often creates confusion when read by humans.

But if you insist on that format you must build the string yourself. The Duration class lacks a format method seen in other java.time class. Oddly, this class originally lacked methods to extract the parts, a number of days, of hours, of minutes, of seconds, and a fractional second. See this Question for discussion. Java 9 brings such methods, named to…Part.

While using Java 8, I suggest doing string manipulation of the ISO 8601 formatted output. Replace the PT with empty string. If there is an M, replace H with a colon. If there is an S, replace the M with a colon and replace the S with empty string. If no S, replace M with empty string. I believe you can find this code posted on Stack Overflow.

Upvotes: 3

clay
clay

Reputation: 20390

Is this what you want to do:

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class HourTest {
    public static void main(String[] args) throws Exception {
        LocalDateTime ldt1 = LocalDateTime.parse("2015-05-04T12:07:00");
        LocalDateTime ldt2 = LocalDateTime.parse("2015-05-04T00:00:00");

        long seconds = Math.abs(ChronoUnit.SECONDS.between(ldt1, ldt2));

        String hms = String.format("%02d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60);

        // Prints 12:07:00. I tried it.
        System.out.println(hms);
    }
}

Upvotes: 1

Steve
Steve

Reputation: 1041

The basic flaw here is that you want the NEAREST distance between two times. When you are constructing your date objects, even though you only format for Hour:Minute:Second it still stores the day/month/year etc... For the dates 12:03:00 and 00:00:00 it defaults them to the same day, so the difference from (Midnight to Noon) is what your getting not (Noon to Midnight) of the next day. The solution for you would be to check if the times are less than 12 (military time) and if so add 1 to the day.

Here's How you do it:

    String t1 = "12:03:00";
    String t2 = "00:00:00";
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    Date d1 = sdf.parse(t1);
    Date d2 = sdf.parse(t2);
    Calendar c1 = Calendar.getInstance();
    Calendar c2 = Calendar.getInstance();
    c1.setTime(d1);
    c2.setTime(d2);

    if(c2.get(Calendar.HOUR_OF_DAY) < 12) {
        c2.set(Calendar.DAY_OF_YEAR, c2.get(Calendar.DAY_OF_YEAR) + 1);
    }
    long elapsed = c2.getTimeInMillis() - c1.getTimeInMillis();

Upvotes: 2

Related Questions