Well, while I was writing my nice answer, @orhtej2 was faster :)
Anyway, I hope that for the sake of readability, you will have some benefit from my solution, too.
I prefer implementing a few methods to the classes, so the Collector is then very simple and straightforward:
final Map<LocalDate, DaySum> result = list.stream()
.collect(Collectors.groupingBy(
TimeInterval::getDatePart, // how to group the elements
Collector.of(
DaySum::new, // how to create a new accumulator
DaySum::addTimeInterval, // how to add a single element
DaySum::combine // how to combine two accumulators - used only when the stream is computed concurrently
))
);
Here is the full code, then:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import lombok.With;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AggregationTest {
@Test
void aggregate() {
final List<TimeInterval> list = List.of(
new TimeInterval(ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), 1.0, 2.0),
new TimeInterval(ZonedDateTime.of(2018, 1, 1, 8, 0, 0, 0, ZoneId.systemDefault()), null, null),
new TimeInterval(ZonedDateTime.of(2018, 1, 1, 16, 0, 0, 0, ZoneId.systemDefault()), 5.0, 6.0),
new TimeInterval(ZonedDateTime.of(2018, 1, 2, 0, 0, 0, 0, ZoneId.systemDefault()), 1.0, 2.0),
new TimeInterval(ZonedDateTime.of(2018, 1, 2, 8, 0, 0, 0, ZoneId.systemDefault()), 3.0, 4.0),
new TimeInterval(ZonedDateTime.of(2018, 1, 2, 16, 0, 0, 0, ZoneId.systemDefault()), 5.0, 6.0),
new TimeInterval(ZonedDateTime.of(2018, 1, 3, 0, 0, 0, 0, ZoneId.systemDefault()), null, null),
new TimeInterval(ZonedDateTime.of(2018, 1, 3, 8, 0, 0, 0, ZoneId.systemDefault()), null, null),
new TimeInterval(ZonedDateTime.of(2018, 1, 3, 16, 0, 0, 0, ZoneId.systemDefault()), null, null)
);
final Map<LocalDate, DaySum> expected = Map.of(
LocalDate.of(2018, 1, 1), new DaySum(32.0),
LocalDate.of(2018, 1, 2), new DaySum(44.0),
LocalDate.of(2018, 1, 3), new DaySum(null)
);
final Map<LocalDate, DaySum> result = list.stream()
.collect(Collectors.groupingBy(
TimeInterval::getDatePart,
Collector.of(
DaySum::new,
DaySum::addTimeInterval,
DaySum::combine
))
);
assertEquals(expected, result);
// Visualisation:
System.out.println("===== INPUT =====");
list.forEach(System.out::println);
System.out.println("===== OUTPUT =====");
System.out.println("result = " + result);
}
}
@Value
class TimeInterval {
ZonedDateTime time;
Double value1;
Double value2;
public LocalDate getDatePart() {
return time.toLocalDate();
}
}
/**
* DaySum is made as a read-and-write object
* so it may be easily used in the Collector calculations.
* If you need an immutable object, create one more class, and
* add an extra 4th parameter to the Collector.of() to convert it.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class DaySum {
@With
Double sum;
/**
* The crucial method, responsible for the logic you requested.
*/
public void addTimeInterval(TimeInterval timeInterval) {
if (timeInterval.getValue1() == null || timeInterval.getValue2() == null) {
return;
}
final double product = timeInterval.getValue1() * timeInterval.getValue2();
if (this.sum == null) {
this.sum = product;
}
else {
this.sum += product;
}
}
/**
* A Collector requires such a method for combining interim results
* in case of parallel processing.
* The idea is that for the same group, the DaySum may be
* calculated concurrently into multiple objects.
* The Collector uses this method to combine them into one
* at the end of the computation.
*/
public DaySum combine(DaySum another) {
if (another == null || another.sum == null) {
return this;
}
if (this.sum == null) {
return another;
}
return this.withSum(this.sum + another.sum);
}
}
N.B.
If you are sure that you will always execute your stream sequentially only, you might start questioning if you really need to implement the combiner. There is a great question Can a Collector's combiner function ever be used on sequential streams? with great answers dedicated to this topic, it's worth reading before you decide to implement your combiner as a no-op function :)