简体   繁体   中英

Calculate elapsed time in Java / Groovy

I have...


Date start = new Date()

...
...
...

Date stop = new Date()

I'd like to get the years, months, days, hours, minutes and seconds ellapsed between these two dates.

--

I'll refine the question.

I just want to get the elapsed time, as an absolute measure, that is without taking into account leap years, the days of each month, etc.

Thus I think it's impossible to get the years and months elapsed, all I can get is days, hours, minutes and seconds.

More specifically I want to tell that a certain task lasted for eg

20 sec
13 min, 4 sec
2 h, 10 min, 2 sec
4 d, 4 h, 2 min, 2 sec

So please forgive my lack of precision.

I've just discovered this quick Groovy-sourced solution:

import groovy.time.TimeCategory 
import groovy.time.TimeDuration

Date start = new Date()

// do something here

Date stop = new Date()

TimeDuration td = TimeCategory.minus( stop, start )
println td

You can do all of this with division and mod.

long l1 = start.getTime();
long l2 = stop.getTime();
long diff = l2 - l1;

long secondInMillis = 1000;
long minuteInMillis = secondInMillis * 60;
long hourInMillis = minuteInMillis * 60;
long dayInMillis = hourInMillis * 24;

long elapsedDays = diff / dayInMillis;
diff = diff % dayInMillis;
long elapsedHours = diff / hourInMillis;
diff = diff % hourInMillis;
long elapsedMinutes = diff / minuteInMillis;
diff = diff % minuteInMillis;
long elapsedSeconds = diff / secondInMillis;

That should give you all of the information you requested.

EDIT: Since people seem to be confused, no, this does not take things like leap years or daylight savings time switches into account. It is pure elapsed time , which is what opensas asked for.

Not so easy with the standard Date API.

You might want to look at Joda Time , or JSR-310 instead.

I'm not an expert in Joda, but I think the code would be:

Interval interval = new Interval(d1.getTime(), d2.getTime());
Period period = interval.toPeriod();
System.out.printf(
    "%d years, %d months, %d days, %d hours, %d minutes, %d seconds%n", 
    period.getYears(), period.getMonths(), period.getDays(),
    period.getHours(), period.getMinutes(), period.getSeconds());

Regarding JodaTime I just got it going; thanks to the responder who suggested it. Here's a more condensed version of the Joda code suggested:

Period period = new Period(d1.getTime(), d2.getTime());
System.out.printf(
    "%d years, %d months, %d days, %d hours, %d minutes, %d seconds%n", 
    period.getYears(), period.getMonths(), period.getDays(),
    period.getHours(), period.getMinutes(), period.getSeconds());

(not sure if this is helping the original question but certainly searchers).

tl;dr

Duration.between( then , Instant.now() )

Using java.time

The modern way uses the java.time classes that supplant the troublesome old date-time classes.

Instead of Date , use Instant . TheInstant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant then = Instant.now();
…
Instant now = Instant.now();

Use the Duration class for a span of time unattached to the timeline, with resolution of day-hours-minutes-seconds-nanos.

Duration d = Duration.between( then , now );

For a span of time with resolution of years-months-days, use the Period class.

Generate a string is standard ISO 8601 format for durations : PnYnMnDTnHnMnS . The P marks the beginning. The T separates any years-months-days from hours-minutes-seconds. So two and a half hours is PT2H30M .

String output = d.toString();

In Java 9 and later, you can access the individual parts with methods toDaysPart , toHoursPart , and so on.

Also in Java 9 and later is an improvement to Instant.now where it captures the current moment in microseconds rather than the milliseconds seen in Java 8. Of course in all versions of Java the Instant class can hold a value in nanoseconds .


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat .

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes.

To learn more, see the Oracle Tutorial . And search Stack Overflow for many examples and explanations. Specification is JSR 310 .

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more .

Well since Java 1.5 you should use TimeUnit.

Here is a simple & plain example for this. I think in groovy it might get shorter(as always).

/**
 * Formats a given {@link Date} to display a since-then timespan.
 * @param created
 * @return String with a format like "3 minutes ago"
 */
public static String getElapsedTime(Date created) {
    long duration = System.currentTimeMillis() - created.getTime();
    long seconds = TimeUnit.MILLISECONDS.toSeconds(duration);
    long days = TimeUnit.MILLISECONDS.toDays(duration);
    long hours = TimeUnit.MILLISECONDS.toHours(duration);
    long minutes = TimeUnit.MILLISECONDS.toMinutes(duration);
    if (days > 0) {
        return days + " days";
    }
    if (hours > 0) {
        return hours + " hrs";
    }
    if (minutes > 0) {
        return minutes + " minutes";
    }

    return seconds + " seconds";
}

Oh and avoid multiple returns please ;)

Actually, regarding the above answers about Joda-Time .

There's an easier way to do this with Joda-Time'sPeriod class:

Period period = new Period(startDate, endDate);
System.out.println(PeriodFormat.getDefault().print(period));

To customize the output, look into the PeriodFormat , PeriodFormatter , and PeriodFormatterBuilder classes.

Apart from the aforementioned great JodaTime API which I do recommend, the best standard Java alternative you can have is the java.util.Calendar . It is cumbersome to work with it (this is an understatement .. look at the single-line JodaTime examples here), but you can calculate the elapsed time with it as well. Important key is that you should use the Calendar#add() in a loop to get the elapsed value for years, months and days to take leap days, years and centuries into account. You should not calculate them back from the (milli)seconds.

Here's a basic example:

import java.util.Calendar;

public class Test {

    public static void main(String[] args) throws Exception {
        Calendar start = Calendar.getInstance();
        start.set(1978, 2, 26, 12, 35, 0); // Just an example.
        Calendar end = Calendar.getInstance();

        Integer[] elapsed = new Integer[6];
        Calendar clone = (Calendar) start.clone(); // Otherwise changes are been reflected.
        elapsed[0] = elapsed(clone, end, Calendar.YEAR);
        clone.add(Calendar.YEAR, elapsed[0]);
        elapsed[1] = elapsed(clone, end, Calendar.MONTH);
        clone.add(Calendar.MONTH, elapsed[1]);
        elapsed[2] = elapsed(clone, end, Calendar.DATE);
        clone.add(Calendar.DATE, elapsed[2]);
        elapsed[3] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 3600000;
        clone.add(Calendar.HOUR, elapsed[3]);
        elapsed[4] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 60000;
        clone.add(Calendar.MINUTE, elapsed[4]);
        elapsed[5] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 1000;

        System.out.format("%d years, %d months, %d days, %d hours, %d minutes, %d seconds", elapsed);
    }

    private static int elapsed(Calendar before, Calendar after, int field) {
        Calendar clone = (Calendar) before.clone(); // Otherwise changes are been reflected.
        int elapsed = -1;
        while (!clone.after(after)) {
            clone.add(field, 1);
            elapsed++;
        }
        return elapsed;
    }

}

It should print my age as of now =)

Oh, I should add, you can "convert" Date to Calendar using Calendar#setTime() .

Hmm, if I get what you're asking, you want to know that if:

start = Jan 1, 2001, 10:00:00.000 am and

stop = Jan 6, 2003, 12:01:00.000 pm

you want an answer of 2 years, 0 months, 5 days, 2 hours, 1 minute

Unfortunately, this is a specious answer. What if the dates were Jan 2, and Jan 31? Would that be 29 days? Ok, but Feb 2 to Mar 2 is 28 (29) days, but would be listed as 1 month?

The length of time in anything other than seconds or possibly days is variable without knowing the context since months and years are of different lengths. The difference between 2 dates should be in static units, which are easily computable from stop.getTime() - start.getTime() (which is the difference in millisecs)

It's easy; You should set the right timezone

import java.util.TimeZone;
import java.util.logging.Logger;

import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class ImportData {

    private final static Logger log = Logger.getLogger(ImportData.class.getName());
    private final static DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
    private final static DateTimeFormatter dtfh = DateTimeFormat.forPattern("HH:mm:ss.SSS");

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
       TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));
       DateTimeZone.setDefault(DateTimeZone.forID("Europe/Berlin"));
       // Quotes connection=Quotes.getInstance();
       final long start  = System.currentTimeMillis();
       // do something here ...
       // connection.importTickdata();
       Thread.currentThread().sleep(2000);
       final long end  = System.currentTimeMillis();
       log.info("[start]    " + dtf.print(start));
       log.info("[end]      " + dtf.print(end));
       TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
       DateTimeZone.setDefault(DateTimeZone.forID("UTC"));
       log.info("[duration] " + dtfh.print(end - start));
       // connection.logOff();
       // System.exit(0);
    }

returns:

10.11.2010 00:08:12 ImportData main
INFO: [start]    2010-11-10 00:08:10.306
10.11.2010 00:08:12 ImportData main
INFO: [end]      2010-11-10 00:08:12.318
10.11.2010 00:08:12 ImportData main
INFO: [duration] 00:00:02.012

I do this in general:

def start_time = System.currentTimeMillis()
...
def end_time = System.currentTimeMillis()

println  (end_time - start_time) +' ms'

Then you can break that into any time unit that you want using the Duration Groovy class http://docs.groovy-lang.org/latest/html/api/groovy/time/Duration.html .

import groovy.time.TimeCategory
import groovy.time.TimeDuration

time = { closure ->
    use(TimeCategory) {
        def started = new Date()
        def res = closure()
        TimeDuration duration = new Date() - started
        logger.info("Execution duration: " + duration.toMilliseconds() + "ms")
        res
    }
}

time {
    println 'A realy heavy operation here that you want to measure haha'
}

There is no point creating a Date object as all this does is wrap System.currentTimeMillis(). The getTime() function just unwraps the Date object. I suggest you just use this function to obtain a long.

If you only need second accuracy, this is fine. However if you want sub-millisecond accuracy use System.nanoTime() to get the elapse time.

This is a complete function I implemented based on Sebastian Celis answer. And again, from his post - this does not take things like leap years or daylight savings time switches into account. It is pure elapsed time .

The output is tailored towards my need. This only outputs three significant sections. Instead of returning

4 months, 2 weeks, 3 days, 7 hours, 28 minutes, 43 seconds

It just returns first 3 block (see more sample run at the end of post) :

4 months, 2 weeks, 3 days

Here is the complete method source code:

/**
 * Format milliseconds to elapsed time format
 * 
 * @param time difference in milliseconds
 * @return Human readable string representation - eg. 2 days, 14 hours, 5 minutes
 */
public static String formatTimeElapsedSinceMillisecond(long milisDiff) {
    if(milisDiff<1000){ return "0 second";}

    String formattedTime = "";
    long secondInMillis = 1000;
    long minuteInMillis = secondInMillis * 60;
    long hourInMillis = minuteInMillis * 60;
    long dayInMillis = hourInMillis * 24;
    long weekInMillis = dayInMillis * 7;
    long monthInMillis = dayInMillis * 30;

    int timeElapsed[] = new int[6];
    // Define time units - plural cases are handled inside loop
    String timeElapsedText[] = {"second", "minute", "hour", "day", "week", "month"};
    timeElapsed[5] = (int) (milisDiff / monthInMillis); // months
    milisDiff = milisDiff % monthInMillis;
    timeElapsed[4] = (int) (milisDiff / weekInMillis); // weeks
    milisDiff = milisDiff % weekInMillis;
    timeElapsed[3] = (int) (milisDiff / dayInMillis); // days
    milisDiff = milisDiff % dayInMillis;
    timeElapsed[2] = (int) (milisDiff / hourInMillis); // hours
    milisDiff = milisDiff % hourInMillis;
    timeElapsed[1] = (int) (milisDiff / minuteInMillis); // minutes
    milisDiff = milisDiff % minuteInMillis;
    timeElapsed[0] = (int) (milisDiff / secondInMillis); // seconds

    // Only adds 3 significant high valued units
    for(int i=(timeElapsed.length-1), j=0; i>=0 && j<3; i--){
        // loop from high to low time unit
        if(timeElapsed[i] > 0){
            formattedTime += ((j>0)? ", " :"") 
                + timeElapsed[i] 
                + " " + timeElapsedText[i] 
                + ( (timeElapsed[i]>1)? "s" : "" );
            ++j;
        }
    } // end for - build string

    return formattedTime;
} // end of formatTimeElapsedSinceMillisecond utility method

Here are some sample test statement:

System.out.println(formatTimeElapsedSinceMillisecond(21432424234L));
// Output: 8 months, 1 week, 1 day

System.out.println(formatTimeElapsedSinceMillisecond(87724294L));
// Output: 1 day, 22 minutes, 4 seconds

System.out.println(formatTimeElapsedSinceMillisecond(123434L));
// Output: 2 minutes, 3 seconds

This is another similar function, It won't show up days, hours, minutes, etc. if its not needed change its literals if needed.

public class ElapsedTime {

    public static void main(String args[]) {

        long start = System.currentTimeMillis();
        start -= (24 * 60 * 60 * 1000 * 2);
        start -= (60 * 60 * 1000 * 2);
        start -= (60 * 1000 * 3);
        start -= (1000 * 55);
        start -= 666;
        long end = System.currentTimeMillis();
        System.out.println(elapsedTime(start, end));

    }

    public static String elapsedTime(long start, long end) {

        String auxRet = "";

        long aux = end - start;
        long days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0;
        // days
        if (aux > 24 * 60 * 60 * 1000) {
            days = aux / (24 * 60 * 60 * 1000);
        }
        aux = aux % (24 * 60 * 60 * 1000);
        // hours
        if (aux > 60 * 60 * 1000) {
            hours = aux / (60 * 60 * 1000);
        }
        aux = aux % (60 * 60 * 1000);
        // minutes
        if (aux > 60 * 1000) {
            minutes = aux / (60 * 1000);
        }
        aux = aux % (60 * 1000);
        // seconds
        if (aux > 1000) {
            seconds = aux / (1000);
        }
        milliseconds = aux % 1000;

        if (days > 0) {
            auxRet = days + " days ";
        }
        if (days != 0 || hours > 0) {
            auxRet += hours + " hours ";
        }
        if (days != 0 || hours != 0 || minutes > 0) {
            auxRet += minutes + " minutes ";
        }
        if (days != 0 || hours != 0 || minutes != 0 || seconds > 0) {
            auxRet += seconds + " seconds ";
        }
        auxRet += milliseconds + " milliseconds ";

        return auxRet;
    }

}

This is a problem and an algorithm needs to be made to account for leap years and exact amount of months and days beside years. Interesting how it is simple if only one unit of measure is to be used. For example, total number of days between two days is correct as apposed to reminder number of days after number of months and years is calculate within let's say two decades.

I am currently working on this to provide it from my PML implementation, for example, in the form of:

unemployed <- date.difference[
    From = 2009-07-01, 
    Till = now, 
    YEARS, MONTHS, DAYS
]: yyyy-MM-dd

$$-> *unemployed -> date.translate[ YEARS, MONTHS, DAYS ] -> print["Unemployed for:", .]

Of course, this would also be useful and required for exact interest rate calculations.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM