How to add times in Java?

Asked

Viewed 6,985 times

28

I am working on a report that makes a sum of the data and part of the data is times, example:

 -----------------------
| Atividade |   Tempo   |
 -----------------------
|    1      |  11:00:00 |
 -----------------------
|    2      |  12:00:00 |
 -----------------------
|    3      |  13:00:00 |
 -----------------------
| Total     |  36:00:00 |
 -----------------------

I’m trying to add up the times this way:

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

The variable listaHoras is a ArrayList of String'all in the format respected by SimpleDateFormat. The problem with the above code is that I cannot generate the added value using the same SimpleDateFormat he returns me the time of a day there in front according to the date, but within 24 hours.

Does anyone know how to solve this problem?

  • In total should opinion 2:00:00, which in case is the interval between 11:00 and 13:00? From what I understand it is adding up 11 + 12 + 13 = 36, you need only display the interval between the first time and the last right?

  • @Eduardobentorochajunior Actually I don’t need the difference no, I need the same sum.

4 answers

22


The easiest and most convenient is to use the Duration from Jodatime, but maybe it doesn’t make much sense to add this dependency if you need to calculate dates just in this part.

Unfortunately, the easiest way to do this using only native libraries is similar to what you’ve already done, but instead of using one Calendar, manually calculate the total.

For example:

// estes numeros nem sempre sao verdade, mas deve ser suficiente para seu caso
long SECOND = 1000;
long MINUTE = 60 * SECOND;
long HOUR = 60 * MINUTE;
long DAY = 24 * HOUR;

// somatoria dos getTime, semelhante ao codigo que ja tens
long elapsedTimeInMilliseconds = ...

double elapsedDays = Math.floor(elapsedTimeInMilliseconds / DAY);
long remaining = elapsedTimeInMilliseconds % DAY; // resto da divisao acima
double elapsedHours = Math.floor(remaining / HOUR);
remaining = remaining % HOUR;
// e por ai vai, ate chegar nos segundos (se for o caso)    

EDIT: This answer was perfectly valid in 2014. Today, a API very similar to Jodatime is available in Java SE, therefore, it makes no sense to do this calculation manually if the platform where the code will run supports Java 8.

  • I really think jodatime better than the SE api, so much so that we will see the jodatime way of working with dates and times in the next version of java

  • I have the Joda Time here in a well outdated version of the current one which is the 2.3 and I have the 1.6.2. I never used it, but reading the link you sent me from the Duration class seems simple. She solving my problem or doing it in the nail I return you as accepted answer, anyway already has a +1 by the algorithm via SE.

  • Look at the answer I accepted in the OS: http://stackoverflow.com/questions/22230487/how-to-sum-times-in-java. That’s what you were wondering?

  • Yes, exactly that. Where I put elapsedTimeInMilliseconds is "long tm = sum.getTime(). getTime();" . The rest of the calculation is similar :-)

  • Then I will accept your reply, but I will put to that I arrived through it for possible comments and improvements.

  • I made an update in my reply. I could read and make your considerations?

Show 1 more comment

9

I came to that code after question in the OS and will post for possible criticism.

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);
}

UPDATING

Still working on the solution I developed the following code.

public static String somaTempos(final List<String> listaTempos) {
    long milissegundos = 0;
    final DateFormat dt = new SimpleDateFormat("HH:mm:ss");
    dt.setLenient(false);
    try {
        // Deslocamento de fuso-horário.
        final long timezoneOffset = dt.parse("00:00:00").getTime();
        for (final String tempo : listaTempos) {
            milissegundos += (dt.parse(tempo).getTime() - timezoneOffset);
        }
    } catch (final ParseException e) {
        throw new BusinessException(
                "Lista de tempos deve ser passada com os tempos respeitando o padrão HH:mm:ss.", e);
    }

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

In fact, the API gives me the minutes and seconds after calculating the sum of times without forgetting to consider the time zone shift. My real problem was calculating the number of hours on a given date, which was actually the easiest point of the problem and which was the first part I was able to solve.

Considerations for code improvements?

6

Since it’s something specific of hours, you can use LocalTime that provides the method plusHours to perform sum of hours. There are other methods to treat sum of minutes, seconds, etc (TODO: see class documentation).

LocalTime primeiro = LocalTime.of(12, 0); // 12:00
LocalTime segundo  = LocalTime.of(5, 45); //  5:45

LocalTime total = primeiro.plusHours(segundo.getHour())
                          .plusMinutes(segundo.getMinute());

System.out.println(total); // 17:45

Functioning in the IDEONE

If you are working with strings, there is the method parse to obtain an object LocalTime from a sequence of characters:

LocalTime primeiro = LocalTime.parse("12:00");
LocalTime segundo  = LocalTime.parse("05:45");

LocalTime total = primeiro.plusHours(segundo.getHour())
                          .plusMinutes(segundo.getMinute());

System.out.println(total); // 17:45

Functioning in the IDEONE

  • This only works if the total is less than or equal to 23:59:59.999999999: https://ideone.com/IsbHnA - The problem is that LocalTime was not made to work with durations (this use would be a hack limited), the ideal in this case would be to use java.time.Duration - since it will use the java.time, why not just use the right classes for it? : -) I understand the fact that I can do Parsing directly may seem an advantage, but the fact that it doesn’t work with totals greater than 24 hours already makes me not choose this solution... :-)

  • As he said he wants to "add up the times" and the total is 36 hours, so it’s durations :-)

2

First, we have to understand the difference between times and durations:

  • one timetable represents a specific moment of the day. Ex: the meeting will be at two o'clock in the afternoon (it says exactly at what time of the day the meeting will be)
  • one duration represents a amount of time. Ex: the meeting lasted two hours (I didn’t say what time she started or finished, just said how long she lasted)

What can be confusing is that both times and durations use the same words (hours, minutes and seconds), and are often written the same ("11:00" can be either 11:00 in the morning or an 11-hour duration). But each represents a different concept.


What happens is that java.util.Date, java.util.Calendar and java.text.SimpleDateFormat are used to work with dates and times, but not with durations.

In some cases, it may even "work", but it will be by coincidence. For example, when doing Parsing of 11:00:00 as if it were a timetable:

SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Date date = sdf.parse("11:00:00");
System.out.println(date);

This code prints out:

Thu Jan 01 11:00:00 BRT 1970

Remembering that the output may vary, since when printing a Date, is used the time zone corresponding to Timezone default jvm (in the case, the "BRT" indicates the Time of Brasilia, since in my JVM the Timezone default is America/Sao_Paulo).

Note that the resulting date is "1 January 1970, at 11 am". That’s a date and time, not a duration. You could even use the value of date.getTime() and correct according to the offset of Timezone (as you did in his reply), but the truth is that this solution is not ideal, since you are treating dates and times as if they were durations. You’re basically using a screwdriver to hammer a nail (it might even "work", but it’s not the ideal way).

Unfortunately the legacy API (Date, Calendar and SimpleDateFormat) has no mechanism for working with durations (including, was one of the many reasons that led to the creation of the Java 8 API). The solution, in this case, is to do as suggested to reply from @jpkrohling: manipulate the strings and do the accounts manually.

List<String> listaTempos = Arrays.asList("11:00:00", "12:00:00", "13:00:00");
long totalSegundos = 0;
for (String tempo : listaTempos) {
    String[] partes = tempo.split(":");
    long horas = Long.parseLong(partes[0]);
    long minutos = Long.parseLong(partes[1]);
    long segundos = Long.parseLong(partes[2]);
    totalSegundos += segundos + (minutos * 60) + (horas * 3600);
}
long totalHoras = totalSegundos / 3600;
totalSegundos -= (totalHoras * 3600);
long totalMinutos = totalSegundos / 60;
totalSegundos -= (totalMinutos * 60);
String totalFormatado = String.format("%02d:%02d:%02d", totalHoras, totalMinutos, totalSegundos);
System.out.println(totalFormatado); // 36:00:00

Java >= 8

From Java 8 you can use the API java.time. In addition to much higher than Date and Calendar, it also has specific classes to handle durations. In this case, we can use a java.time.Duration:

List<String> listaTempos = Arrays.asList("11:00:00", "12:00:00", "13:00:00");
Duration total = Duration.ZERO;
for (String tempo : listaTempos) {
    String[] partes = tempo.split(":");
    total = total
        // somar horas
        .plusHours(Long.parseLong(partes[0]))
        // somar minutos
        .plusMinutes(Long.parseLong(partes[1]))
        // somar segundos
        .plusSeconds(Long.parseLong(partes[2]));
}

With this, the variable total will represent a Duration containing the total duration. Unfortunately the API still doesn’t provide a mechanism to properly format a duration, so the way is to do the accounts manually, in a similar way to the previous solution:

long totalSegundos = total.getSeconds();
long totalHoras = totalSegundos / 3600;
totalSegundos -= (totalHoras * 3600);
long totalMinutos = totalSegundos / 60;
totalSegundos -= (totalMinutos * 60);
String totalFormatado = String.format("%02d:%02d:%02d", totalHoras, totalMinutos, totalSegundos);
System.out.println(totalFormatado); // 36:00:00

Just one detail: the class Duration has the method toMinutes(), that apparently could be used. But in this case it will return 2160, for it is the total of minutes corresponding to the total duration (corresponding to 36 hours). To get the duration "broken" in hours, minutes and seconds, the only solution - at least in Java 8 - is to do the accounts manually.

Java >= 9

Since Java 9, methods such as toMinutesPart(), that already bring these values properly "broken". That is, for Java >= 9, the final part (after the for) would be:

for (String tempo: listaTempos) {
    ... // igual ao exemplo anterior
}
String totalFormatado = String.format("%02d:%02d:%02d", (total.toDaysPart() * 24) + total.toHoursPart(),
                                      total.toMinutesPart(), total.toSecondsPart());
System.out.println(totalFormatado); // 36:00:00

In this case, I also needed to take the value of toDaysPart(), since a duration of 36 hours is returned as "1 day and 12 hours" by the respective methods toDaysPart() and toHoursPart(). A little boring detail, but still, much better than doing the math manually.

Java 6 and 7

For Java 6 and 7, the solution is to do the accounts manually, as already suggested. But there is also an alternative: use the Threeten Backport, one backport of java.time, which has basically the same classes and functionalities as Java 8 (not 100%, of course, but the main classes and methods are available).

The code is the same as the previous Java 8 example, the difference is that instead of the package java.time, the package is used org.threeten.bp.

Java 5

Finally, for Java 5 (in addition to the first solution of doing the accounts manually), an alternative is to use the Joda-Time. This library includes the class org.joda.time.format.PeriodFormatter to do the Parsing and formatting of a duration:

import org.joda.time.Duration;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

List<String> listaTempos = Arrays.asList("11:00:00", "12:00:00", "13:00:00");
PeriodFormatter fmt = new PeriodFormatterBuilder()
    .printZeroAlways().minimumPrintedDigits(2)
    .appendHours().appendSeparator(":")
    .appendMinutes().appendSeparator(":")
    .appendSeconds().toFormatter();
Duration total = Duration.ZERO;
for (String tempo : listaTempos) {
    total = total.plus(fmt.parsePeriod(tempo).toStandardDuration());
}
String totalFormatado = fmt.print(total.toPeriod());
System.out.println(totalFormatado); // 36:00:00

Remembering that Joda-Time is a project "terminated" and on its own site there’s a warning on this, recommending migration to the java.time. Anyway, for those who are still "stuck" to Java 5, it is a great alternative to Date and Calendar.

Note also that the class Duration is in the package org.joda.time (not to be confused with the java.time.Duration Java >= 8). Joda-Time is not 100% identical to java.time, but many of its concepts and ideas have been taken advantage of in Java 8 (including some classes and methods have the same names). The main similarities and differences between Apis are explained here and here.


Do not confuse dates/times with durations

One of the answers are using java.time.LocalTime to represent durations. But this class represents a time, not a duration. This means that it only works if the total is less than 23 hours and 59 minutes. Ex:

LocalTime primeiro = LocalTime.parse("12:00");
LocalTime segundo = LocalTime.parse("17:00");
LocalTime total = primeiro.plusHours(segundo.getHour()).plusMinutes(segundo.getMinute());
System.out.println(total); // 05:00

Summing up a duration of 12 hours with another of 17 hours, the result should be a total of 29 hours. But how LocalTime represents a time (not a duration), it only supports values up to 23:59:59.999999999, and after that she "comes back" to midnight. So the result is 05:00 (5 am - again, a time, not a duration).

It’s the same story as Date and SimpleDateFormat: Using classes that represent dates and times to work with durations will not always work. Often it will be just by coincidence, and yet for limited cases.

If you already have available the classes of java.time, use the right types for each case. If you need to deal with schedules, use LocalTime. If you need to deal with durations, use Duration (or Period).

  • If you can say the problem of the answer to deserve a negative, thank you. I will be happy to correct the problem..

Browser other questions tagged

You are not signed in. Login or sign up in order to post.