As explained in your other question, don't mistake those 2 different concepts:
- a time of the day: it represents a specific point of a day, such as
10 AM
or 14:45:50
- a duration: it represents an amount of time, such as "1 hour and 10 minutes" or "2 years, 3 months and 4 days". The duration doesn't tell you when it starts or ends ("1 hour and 10 minutes" relative to what?), it's not attached to a chronology, it doesn't correspond to a specific point in the timeline. It's just the amount of time, by itself.
In your input, you have:
(start)14:26:12,(end)14:27:19,(duration)00:01:07
The start
and end
represents times of the day, and the duration
represents the amount of time. SimpleDateFormat
is designed to work with dates and times of the day, but not with durations. Treating the duration as a time of the day might work, but it's a hack as explained in this answer.
Another problem is that when SimpleDateFormat
parses only a time, it defaults the day to January 1st 1970 at the JVM default timezone, leading to all the strange results you see. Unfortunately there's no way to avoid that, as java.util.Date
works with full timestamps. A better alternative is to use the new date/time API.
As in your other question you're using Java 8, I'm assuming you can also use it here (but if you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. The only difference is the package names (in Java 8 is java.time
and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp
), but the classes and methods names are the same).
As you're working only with times, there's no need to consider date fields (day/month/year), we can use a LocalTime
instead. You can parse the strings directly, because they are in ISO861 compliant format:
LocalTime start = LocalTime.parse("14:26:12");
LocalTime end = LocalTime.parse("14:27:19");
Unfortunately there are no built-in parsers for a duration, so you'll have to parse it manually:
// parse the duration manually
String[] parts = "00:01:07".split(":");
Duration d = Duration
// get hours
.ofHours(Long.parseLong(parts[0]))
// plus minutes
.plusMinutes(Long.parseLong(parts[1]))
// plus seconds
.plusSeconds(Long.parseLong(parts[2]));
Another alternative is to remove the durations from your input (or ignore them) and calculate it using the start and end:
Duration d = Duration.between(start, end);
Both will give you a duration of 1 minute and 7 seconds.
My suggestion is to change the DependencyDownTime
to store start and end as LocalTime
objects, and the duration as a Duration
object. With this, your algorithm would be like this:
Duration total = Duration.ZERO;
for (...) {
LocalTime curStart = ...
LocalTime curEnd = ...
LocalTime nextStart = ...
LocalTime nextEnd = ...
if (total.toMillis() == 0) {
duration = dts.getDuration();
}
if (curStart.equals(nextStart) && curEnd.isBefore(nextEnd)) {
total = total.plus(Duration.between(curEnd, nextEnd));
} else if (nextStart.isAfter(curEnd)) {
total = total.plus(downTimes.get(i).getDuration());
} else if (curStart.isBefore(nextStart) && curEnd.isAfter(nextStart)) {
total = total.plus(Duration.between(curEnd, nextEnd));
} else if (curEnd.equals(nextStart)) {
total = total.plus(downTimes.get(i).getDuration());
}
i++;
if (i == downTimes.size()) {
// assuming you want the duration as a total of milliseconds
componentDTimeMap.put(application, total.toMillis());
return;
}
}
You can either store the Duration
object, or the respective value of milliseconds. Don't try to transform it to a Date
, because a date is not designed nor supposed to work with durations. You can adapt this code to format a duration if you want (unfortunately there are no native formatters for durations).
Limitations
The code above assumes that all start
and end
times are in the same day. But if you have start
at 23:50
and end
at 00:10
, should the duration be 20 minutes?
If that's the case, it's a little bit trickier, because LocalTime
is not aware of the date (so it considers 23:50 > 00:10
and the duration between them is "minus 23 hours and 40 minutes").
In this case, you could do a trick and assume the dates are all at the current date, but when start
is greater than end
, it means that end
time is in the next day:
LocalTime start = LocalTime.parse("23:50");
LocalTime end = LocalTime.parse("00:10");
// calculate duration
Duration d;
if (start.isAfter(end)) {
// start is after end, it means end is in the next day
// current date
LocalDate now = LocalDate.now();
// start is at the current day
LocalDateTime startDt = now.atTime(start);
// end is at the next day
LocalDateTime endDt = now.plusDays(1).atTime(end);
d = Duration.between(startDt, endDt);
} else {
// both start and end are in the same day
// just calculate the duration in the usual way
d = Duration.between(start, end);
}
In the code above, the result will be a Duration
of 20 minutes.
Don't format dates as durations
Here are some examples of why SimpleDateFormat
and Date
aren't good to handle durations of time.
Suppose I have a duration of 10 seconds. If I try to transform it to a java.util.Date
using the value 10 to a date (AKA treating a duration as a date):
// a 10 second duration (10000 milliseconds), treated as a date
Date date = new Date(10 * 1000);
System.out.println(date);
This will get a date that corresponds to "10000 milliseconds after unix epoch (1970-01-01T00:00Z
)", which is 1970-01-01T00:00:10Z
. But when I print the date object, the toString()
method is implicity called (as explained here). And this method converts this millis value to the JVM default timezone.
In the JVM I'm using, the default timezone is America/Sao_Paulo
, so the code above outputs:
Wed Dec 31 21:00:10 BRT 1969
Which is not what is expected: the UTC instant 1970-01-01T00:00:10Z
corresponds to December 31st 1969 at 9 PM in São Paulo timezone.
This happens because I'm erroneously treating the duration as a date (and the output will be different, depending on the default timezone configured in the JVM).
A java.util.Date
can't (must not) be used to work with durations. Actually, now that we have better API's, it should be avoided whenever possible. There are too many problems and design issues with this, just don't use it if you can.
SimpleDateFormat
also won't work properly if you handle the durations as dates. In this code:
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
Date d = dateFormat.parse("10:00:00");
The input has only time fields (hour, minute and second), so SimpleDateFormat
sets the date to January 1st 1970 at the JVM default timezone. If I System.out.println
this date, the result will be:
Thu Jan 01 10:00:00 BRT 1970
That's January 1st 1970 at 10 AM in São Paulo timezone, which in UTC is equivalent to 1970-01-01T13:00:00Z
- so d.getTime()
returns 46800000
.
If I change the JVM default timezone to Europe/London
, it will create a date that corresponds to January 1st 1970 at 10 AM in London (or UTC 1970-01-01T09:00:00Z
) - and d.getTime()
now returns 32400000
(because 10 AM in London and 10 AM in São Paulo happened at different instants).
SimpleDateFormat
isn't the right tool to work with durations - it isn't even the best tool to work with dates, actually.