Philippe Gioseffi
Philippe Gioseffi

Reputation: 1658

How to sum times in Java?

I'm working on a report that calculates a sum of the data in it and some of the data are timestamps, for example:

 ----------------------
|  Activity |   Time   |
 ----------------------
|     1     | 11:00:00 |
 -----------------------
|     2     | 12:00:00 |
 -----------------------
|     3     | 13:00:00 |
 -----------------------
| Total     | 36:00:00 |
 ----------------------

I'm trying to sum timestamps as below:

final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
c.setTimeInMillis(0);
for (final String t : timestampsList) {
    c.add(Calendar.MILLISECOND, (int) dt.parse(t).getTime());
}

The variable timestampsList is an ArrayList of String's, all respecting the pattern used by the SimpleDateFormat object. The problem with the given code is that I can't generate the value of the sum of the timestamps, by using the same SimpleDateFormat what I get is an hour in the pattern informed in a future date.

I also have seen Joda Time Duration class but I'm not familiar with this lib and I 'don't know if I'm in a correct path that will lead me to the right answer.

Does anyone know how to handle it by using J2SE or Joda Time?

Upvotes: 3

Views: 14517

Answers (4)

Basil Bourque
Basil Bourque

Reputation: 339332

If those data input Strings represent durations in hours:minutes:seconds without any date or time-of-day, then the other answers are working much too hard.

Generally, the old java.util.Date and .Calendar classes are notoriously troublesome and should be avoided. Specifically here, those classes have no notion of a span of time. Instead you should be using either Joda-Time or maybe java.time.

Joda-Time

Joda-Time offers three classes to represent a span of time: Interval, Period, and Duration. The first is tied to points along the timeline of the Universe. The other two are not.

The Period and Duration classes are very close cousins. Period is a tuple with a number of years, months, days, hours, minutes, and seconds. Duration is a number of milliseconds with no concept of fields such as days or seconds.

Joda-Time uses the ISO 8601 standard for its defaults in parsing and generating strings. For period/duration time, this means the PnYnMnDTnHnMnS format. The P means "period" and the T is a separator between date and time portions.

Here is some example code in Joda-Time 2.3. Basically a couple of lines: parsePeriod & durationSum.plus seen below.

Simulate input strings.

List<String> durationStrings = new ArrayList<String>();
durationStrings.add( "11:00:00" ); // Number of hours/minutes/seconds. Not time-of-day.
durationStrings.add( "12:00:00" );
durationStrings.add( "13:00:00" ); // Expect sum of 36 hours = 11 + 12 + 13.

Define a formatter to parse those strings. Joda-Time might have such a formatter built-in, but I could not locate it. So I defined one.

PeriodFormatter formatter = new PeriodFormatterBuilder()
        .appendHours()
        .appendSeparator( ":" )
        .appendMinutes()
        .appendSeparator( ":" )
        .appendSeconds()
        .toFormatter();

Loop the input strings, parsing each one, then adding its duration to the sum.

Duration durationSum = Duration.ZERO;  // Initializing to empty amount. Add to this in loop below.
for ( String durationString : durationStrings ) {
    Period period = formatter.parsePeriod( durationString );
    Duration duration = period.toStandardDuration();
    durationSum = durationSum.plus( duration );
    System.out.println( "period: " + period );
    System.out.println( "duration: " + duration );
}
System.out.println( "durationSum: " + durationSum );
System.out.println( "durationSum as Period: " + durationSum.toPeriod() );

When run…

period: PT11H
duration: PT39600S
period: PT12H
duration: PT43200S
period: PT13H
duration: PT46800S
durationSum: PT129600S
durationSum as Period: PT36H

Upvotes: 1

Philippe Gioseffi
Philippe Gioseffi

Reputation: 1658

This is a original code from petrov with some edits made by me. Since it's quite dificult to discuss in comments providing big snippets of code I posted it as an answer so we can discuss petrov's other considerations.

public static void somaTempos(final String[] listaTempos) throws ParseException {
    long tm = 0;
    final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
    final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
    for (String tmp : listaTempos) {
        c.setTime(dt.parse(tmp));
        tm += c.get(Calendar.SECOND) + 60 * c.get(Calendar.MINUTE) + 3600 * c.get(Calendar.HOUR_OF_DAY);
    }

    final long l = tm % 3600;
    System.out.println(SIGRUtil.format(tm / 3600) + ':' + SIGRUtil.format(l / 60) + ':' + SIGRUtil.format(l % 60));
}

private static String format(long s) {
    if (s < 10) {
        return "0" + s;
    }
    return String.valueOf(s);
}

UPDATE: An alternative that also solves my problem:

public static String sumTimes(final String[] timestampList) {
    long milliseconds = 0;
    final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
    dt.setLenient(false);
    try {
        final long timezoneOffset = dt.parse("00:00:00").getTime();
        for (final String t: timestampList) {
            milliseconds += (dt.parse(t).getTime() - timezoneOffset);
        }
    } catch (final ParseException e) {
        throw new BusinessException(
                "One of the timestamps in the timestamp list cannot be applied to the HH:mm:ss pattern.", e);
    }

    ((SimpleDateFormat) dt).applyPattern(":mm:ss");
    return new StringBuilder(8).append(milliseconds / 3600000).append(
            dt.format(new Date(milliseconds))).toString();
}

Actually, the API gives me for free the minutes and the seconds by only reaplying another pattern in the DateFormat after calculating the sum of the time stamps, without forgetting to consider the timezone offset in this calculation, my real problem was how to calculate the number of hours which really is the less dificult part.

Any suggestions of improvements?

Upvotes: 1

peter.petrov
peter.petrov

Reputation: 39477

I would just parse these Strings myself, convert them to
seconds or milliseconds and sum them up. See answer 2 below.

ANSWER 1

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;


public class Test051 {

    public static void main(String[] args) throws Exception {
        String pt = "1970-01-01-";
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:05");
        timestampsList.add("01:00:05");
        timestampsList.add("10:00:05");
        final DateFormat dt = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
        final Calendar sum = Calendar.getInstance();
        sum.setTimeInMillis(0);

        long tm0 = new SimpleDateFormat("yyyy-MM-dd").parse(pt).getTime();

        System.out.println("tm0 = " + tm0);

        for (final String t : timestampsList) {
            // System.out.println(dt.parse(pt + t).getTime());
            Date x = dt.parse(pt + t);
            // System.out.println(x.getTime());
            sum.add(Calendar.MILLISECOND, (int)x.getTime());
            sum.add(Calendar.MILLISECOND, (int)-tm0);
        }

        long tm = sum.getTime().getTime();
        System.out.println("tm = " + tm);

        tm = tm / 1000;


        long hh = tm / 3600;
        tm %= 3600;
        long mm = tm / 60;
        tm %= 60;
        long ss = tm;
        System.out.println(format(hh) + ":" + format(mm) + ":" + format(ss));
    }

    private static String format(long s){
        if (s < 10) return "0" + s;
        else return "" + s;
    }
}

ANSWER 2

import java.util.ArrayList;

public class Test051 {

    public static void main(String[] args) throws Exception {
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:05");
        timestampsList.add("01:00:05");
        timestampsList.add("10:00:05");

        long tm = 0;
        for (String tmp : timestampsList){
            String[] arr = tmp.split(":");
            tm += Integer.parseInt(arr[2]);
            tm += 60 * Integer.parseInt(arr[1]);
            tm += 3600 * Integer.parseInt(arr[0]);
        }

        long hh = tm / 3600;
        tm %= 3600;
        long mm = tm / 60;
        tm %= 60;
        long ss = tm;
        System.out.println(format(hh) + ":" + format(mm) + ":" + format(ss));
    }

    private static String format(long s){
        if (s < 10) return "0" + s;
        else return "" + s;
    }
}

ANSWER 3

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

public class Test051 {

    public static void main(String[] args) throws Exception {
        ArrayList<String> timestampsList = new ArrayList<String>();
        timestampsList.add("01:00:00");
        timestampsList.add("02:00:00");
        timestampsList.add("03:00:00");
        timestampsList.add("04:00:00");
        timestampsList.add("02:00:00");
        timestampsList.add("04:00:00");

        Date dt0 = new SimpleDateFormat("yyyy-MM-dd").parse("1970-01-01");

        // Check very carefully the output of this one.
        System.out.println(dt0.getTime());

        final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
        final Calendar c = Calendar.getInstance();
        c.setTimeInMillis(0);
        for (final String t : timestampsList) {
            c.add(Calendar.MILLISECOND, (int) dt.parse(t).getTime());
            c.add(Calendar.MILLISECOND, (int)-dt0.getTime());
        }

        // We need to add this back. This is basically the time zone offset.
        c.add(Calendar.MILLISECOND, (int)dt0.getTime());

        System.out.println(c.getTime().getTime());
        System.out.println(c.getTimeInMillis());

        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));
        System.out.println(new SimpleDateFormat("HH:mm:ss").format(c.getTime()));
    }

}

Upvotes: 5

Jonathan Drapeau
Jonathan Drapeau

Reputation: 2610

If you don't wanna use peter petrov solution to parse your String yourself, the way to do it with Calendar and SimpleDateFormat is as follow :

List<String> timestampsList = new ArrayList<String>();
timestampsList.add("11:00:00");
timestampsList.add("12:00:00");
timestampsList.add("13:00:00");
final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
final Calendar c = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
long milliseconds = 0;
c.clear();
long startingMS = c.getTimeInMillis();
for (final String t : timestampsList) {
  milliseconds = milliseconds + (dt.parse(t).getTime() - startingMS);
}

System.out.println(milliseconds + " milliseconds");
System.out.println(milliseconds / 1000 + " seconds");
System.out.println(milliseconds / 1000 / 60 + " minutes");
System.out.println(milliseconds / 1000 / 60 / 60 + " hours");

Or use

long startingMS = dt.parse("00:00:00").getTime();
for (final String t : timestampsList) {
  milliseconds = milliseconds + (dt.parse(t).getTime() - startingMS);
}

instead, removing the need for the Calendar. Both result in :

129600000 milliseconds 129600 seconds 2160 minutes 36 hours

Note that you might wanna make the results a double not to miss part of the time.

Upvotes: 1

Related Questions