This is a quite complicated operation. The direct way would be to first create a Map<Integer, Map<Integer, List<TimeEntry>>>
by grouping over the days, then over the hours. Once we have that map, we can postprocess it to create the wanted List<Day>
.
Creating the temporary map is easy enough. We can use Collectors.groupingBy(classifier, downstream)
where the classifier returns the day (through the method reference TimeEntry::getDay
) and the downstream collector is another groupingBy
collector that classifies over the hours (through the method reference TimeEntry::getHour
). After this step, we have a map over each day where the value is a map over each hour mapping to the corresponding time entry.
Next, what we need to do is make a List<Day>
out of that map. Basically, each entry of the map (so each day number) must be mapped to the corresponding Day
object.
- The
dayNr
is simply the key of the entry.
- The
dayName
is the day name of one of the time entries for that day and an hour. One time entry must exist since we grouped by those fields earlier: to retrieve it, we get the hour map Map<Integer, List<TimeEntry>>
for that day number and just keep the first value.
- The
hours
can be retrieved by manipulating the hour map Map<Integer, List<TimeEntry>>
for that day number. For each entry of that map:
- The
hourNr
is simply the key of the entry
- The
entries
field is the value of the entry
- The
sumOfEntries
is the addition of all the duration of the time entries for that hour number.
- Finally, the
sumOfHours
is the addition of all the sumOfEntries
for each hour number for that day number.
A complete implementation is the following, where it is assumed that all of your domain objects have an appropriate constructor and the appropriate getters:
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
List<TimeEntry> timeEntries = Arrays.asList(
new TimeEntry(LocalDateTime.parse("2016-01-22 10:00", formatter), Duration.ofMinutes(5)),
new TimeEntry(LocalDateTime.parse("2016-01-23 08:00", formatter), Duration.ofMinutes(7)),
new TimeEntry(LocalDateTime.parse("2016-01-23 08:43", formatter), Duration.ofMinutes(3))
);
Map<Integer, Map<Integer, List<TimeEntry>>> map =
timeEntries.stream()
.collect(groupingBy(TimeEntry::getDay, groupingBy(TimeEntry::getHour)));
List<Day> output =
map.entrySet()
.stream()
.map(e -> {
String dayName = e.getValue().values().iterator().next().get(0).getDayName();
List<Hour> hours =
e.getValue().entrySet()
.stream()
.map(he -> new Hour(he.getKey(), sumDuration(he.getValue(), TimeEntry::getDuration), he.getValue()))
.collect(toList());
return new Day(e.getKey(), dayName, sumDuration(hours, Hour::getSumOfEntries), hours);
})
.collect(toList());
System.out.println(output);
}
private static <T> Duration sumDuration(List<T> list, Function<T, Duration> function) {
return list.stream().map(function::apply).reduce(Duration.ofMinutes(0), (d1, d2) -> d1.plus(d2));
}
Note that the addition of Duration
objects from a list was factored into a helper method sumDuration
.
Output of the sample above (assuming a toString()
printing all the fields enclosed in square brackets):
[Day [dayNr=22, dayName=vendredi, sumOfHours=PT5M, hours=[Hour [hourNr=10, sumOfEntries=PT5M, entries=[TimeEntry [start=2016-01-22T10:00, duration=PT5M]]]]], Day [dayNr=23, dayName=samedi, sumOfHours=PT10M, hours=[Hour [hourNr=8, sumOfEntries=PT10M, entries=[TimeEntry [start=2016-01-23T08:00, duration=PT7M], TimeEntry [start=2016-01-23T08:43, duration=PT3M]]]]]]