I have a map of string values which represent down times for different components.
dependencyMap.put ("sut", "14:26:12,14:27:19,00:01:07;15:01:54,15:02:54,00:01:00;15:44:30,15:46:30,00:02:00;16:10:30,16:11:30,00:01:00");
dependencyMap.put ("jms", "14:26:12,14:28:12,00:02:00;15:10:50,15:12:55,00:02:05;15:42:30,15:43:30,00:01:00;16:25:30,16:27:30,00:02:00");
The strings represent the start, end and duration of down times.
(start)14:26:12,(end)14:27:19,(duration)00:01:07
I read the values in, then add them to a list of DependencyDownTime
objects which hold the Long
values startTime, endTime and duration.
jArray.forEach (dependency ->{
String downTimeValues = knownDowntimesMap.get(dependency);
final String[] downtime = downTimeValues.split (";");
for (final String str : downtime) {
final DependencyDownTime depDownTime = new DependencyDownTime ();
final String[] strings = str.split (",");
if (strings.length == 3) {
final DateFormat dateFormat = new SimpleDateFormat ("HH:mm:ss");
try {
depDownTime.setStartTime(dateFormat.parse (strings[0]).getTime ());
depDownTime.setEndTime (dateFormat.parse (strings[1]).getTime ());
depDownTime.setDuration (dateFormat.parse (strings[2]).getTime ());
downTimes.add (depDownTime);
} catch (final ParseException e) {
//logger.warn (e.getMessage (), e);
}
} else {
//logger.warn ("");
}
}
I then perform simple arithmetic on the values, which calculates the total down time for each component.
// sort the list by start time
Collections.sort(downTimes, Comparator.comparing (DependencyDownTime::getStartTime));
int i = 1;
Long duration = 0L;
for(DependencyDownTime dts: downTimes){
Long curStart = dts.getStartTime ();
Long curEnd = dts.getEndTime();
Long nextStart = downTimes.get(i).getStartTime ();
Long nextEnd = downTimes.get(i).getEndTime ();
if(duration == 0){
duration = dts.getDuration();
}
if(curStart.equals(nextStart) && curEnd < nextEnd){
duration += (nextEnd - curEnd);
}
else if(nextStart > curEnd){
duration += downTimes.get(i).getDuration();
}
else if( curStart < nextStart && curEnd > nextStart){
duration += (nextEnd - curEnd);
}
else if(curEnd == nextStart){
duration += downTimes.get(i).getDuration();
}
i++;
if(i == downTimes.size ()){
componentDTimeMap.put (application, duration);
return;
}
The expected values should be something like 1970-01-01T 00:14:35 .000+0100
, a matter of minutes. The actual result is usually extremely high off by a matter of hours in the difference 1969-12-31T 15:13:35 .000+0100
I have 2 questions.
Am I parsing the values correctly?
If my calculations are a little off when adding and subtracting the long values. When I convert the values back to Date format will there be a drastic difference in the expected value?
As explained in your other question, don't mistake those 2 different concepts:
10 AM
or14:45:50
In your input, you have:
The
start
andend
represents times of the day, and theduration
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, asjava.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) isorg.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:Unfortunately there are no built-in parsers for a duration, so you'll have to parse it manually:
Another alternative is to remove the durations from your input (or ignore them) and calculate it using the start and 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 asLocalTime
objects, and the duration as aDuration
object. With this, your algorithm would be like this:You can either store the
Duration
object, or the respective value of milliseconds. Don't try to transform it to aDate
, 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
andend
times are in the same day. But if you havestart
at23:50
andend
at00: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 considers23: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 thanend
, it means thatend
time is in the next day: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
andDate
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):This will get a date that corresponds to "10000 milliseconds after unix epoch (
1970-01-01T00:00Z
)", which is1970-01-01T00:00:10Z
. But when I print the date object, thetoString()
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: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: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 ISystem.out.println
this date, the result will be:That's January 1st 1970 at 10 AM in São Paulo timezone, which in UTC is equivalent to
1970-01-01T13:00:00Z
- sod.getTime()
returns46800000
.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 UTC1970-01-01T09:00:00Z
) - andd.getTime()
now returns32400000
(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.