I have list of Entry
objects. Entry
is a:
class Entry {
private final Date date;
private final String value;
// constructor
// getters
}
I need to group these entries by day. For example,
2011-03-21 09:00 VALUE1
2011-03-21 09:00 VALUE2
2011-03-22 14:00 VALUE3
2011-03-22 16:00 VALUE4
2011-03-21 16:00 VALUE5
Should be grouped:
2011-03-21
VALUE1
VALUE2
VALUE5
2011-03-22
VALUE3
VALUE4
I want to get a Map<Date, List<Entry>>
. How can I get this using the Stream API (groupingBy collector)?
My attempt below:
final Map<Date, List<Entry>> entries =
list.stream().collect(Collectors.groupingBy(request -> {
final Calendar ogirinal = Calendar.getInstance();
ogirinal.setTime(request.getDate());
final Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_MONTH, ogirinal.get(Calendar.DAY_OF_MONTH));
cal.set(Calendar.MONTH, ogirinal.get(Calendar.MONTH));
cal.set(Calendar.YEAR, ogirinal.get(Calendar.YEAR));
return cal.getTime();
}));
Output:
2011-03-21
VALUE1
2011-03-21
VALUE2
2011-03-22
VALUE3
VALUE4
2011-03-21
VALUE5
Again don't understand why you are using java.util.Date
when you can use LocalDateTime
for example. But here goes my attempt:
Map<Date, List<Entry>> entries = list.stream().collect(Collectors.groupingBy(e ->
// easier way to truncate the date
Date.from(e.getDate().toInstant().truncatedTo(ChronoUnit.DAYS)))
);
DEMO
You have to truncate the date values, as they may be different up to the millisecond:
import static java.time.temporal.ChronoUnit.DAYS;
final Map<Instant, List<Entry>> entries =
list.stream().collect(Collectors.groupingBy(request ->
request.getDate().toInstant().truncatedTo(DAYS)));
Your approach is pretty much the right way to go, but your entries end up in different groups because the Calendar
objects you return all have slightly different timestamps. This is because you do not set hour/minute/... to 0. Only when two of the calendars have the same time by coincidence (due to timer inaccuracies for example) two entries will end up in the same group.
Use something like this to group instead:
LocalDate.fromDateFields(request.getDate());
LocalDate
makes creating date-only timestamps much easier than Calendar
. This snippet uses joda time's LocalDate
, but Java's own is only slightly longer.
If you want to group by date (day, month and year), you can discard the time (hour, minutes, seconds). As you're using Java 8, just convert the java.util.Date
to a java.time.LocalDate
:
Map<LocalDate, List<Entry>> entries = list.stream()
.collect(Collectors.groupingBy(request ->
request.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));
Just some background: a Date
represents a specific point in time, the number of milliseconds since Unix epoch, while a LocalDate
represents only a day/month/year date, without any notion of timezone.
To properly convert a Date
to a LocalDate
, I set it to the JVM default timezone (using toInstant().atZone()
) and then get only the local part (toLocalDate()
).
You could also make your design simpler, changing the Entry
class to have a LocalDate
field, if possible. You're using Java 8, and unless you have a good reason to use the old API ("legacy code", "my boss doesn't want", etc), it's better to start using java.time
stuff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With