It depends whether you need to process the objects of all days or one specific day.
Building on DiabolicWords's answer, this is an example to process all days:
TreeSet<MyObject> currentDaysObjects = new TreeSet<>(Comparator.comparing(MyObject::getTimestamp));
LocalDate[] currentDay = new LocalDate[1];
incoming.peek(o -> {
LocalDate date = o.getTimestamp().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
if (!date.equals(currentDay[0]))
{
if (currentDay != null)
{
processOneDaysObjects(currentDaysObjects);
currentDaysObjects.clear();
}
currentDay[0] = date;
}
}).forEach(currentDaysObjects::add);
This will collect the objects for one day, process them, reset the collection and continue with the next day.
If you only want one specific day:
TreeSet<MyObject> currentDaysObjects = new TreeSet<>(Comparator.comparing(MyObject::getTimestamp));
LocalDate specificDay = LocalDate.now();
incoming.filter(o -> !o.getTimestamp()
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate()
.isBefore(specificDay))
.peek(o -> currentDaysObjects.add(o))
.anyMatch(o -> {
if (o.getTimestamp().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().isAfter(specificDay))
{
currentDaysObjects.remove(o);
return true;
}
return false;
});
The filter will skip objects from before the specificDay
, and the anyMatch will terminate the stream after the specificDay
.
I have read that there will be methods like skipWhile or takeWhile on streams with Java 9. These would make this a lot easier.
Edit after Op specified goal more in detail
Wow, this is a nice exercise, and quite a tough nut to crack. The problem is that an obvious solution (collecting the stream) always goes through the whole stream. You cannot take the next x elements, order them, stream them, then repeat without doing it for the whole stream (i.e. all days) at once. For the same reason, calling sorted()
on a stream will go through it completely (especially as the stream does not know the fact that the elements are sorted by days already). For reference, read this comment here: https://stackoverflow.com/a/27595803/7653073.
As they recommend, here is an Iterator implementation wrapped in a stream that kind of looks ahead in the original stream, takes the elements of one day, sorts them, and gives you the whole thing in a nice new stream (without keeping all days in memory!). The implementation is more complicated as we do not have a fixed chunk size, but always have to find the first element of the next next day to know when to stop.
public class DayByDayIterator implements Iterator<MyObject>
{
private Iterator<MyObject> incoming;
private MyObject next;
private Iterator<MyObject> currentDay;
private MyObject firstOfNextDay;
private Set<MyObject> nextDaysObjects = new TreeSet<>(Comparator.comparing(MyObject::getTimestamp));
public static Stream<MyObject> streamOf(Stream<MyObject> incoming)
{
Iterable<MyObject> iterable = () -> new DayByDayIterator(incoming);
return StreamSupport.stream(iterable.spliterator(), false);
}
private DayByDayIterator(Stream<MyObject> stream)
{
this.incoming = stream.iterator();
firstOfNextDay = incoming.next();
nextDaysObjects.add(firstOfNextDay);
next();
}
@Override
public boolean hasNext()
{
return next != null;
}
@Override
public MyObject next()
{
if (currentDay == null || !currentDay.hasNext() && incoming.hasNext())
{
nextDay();
}
MyObject result = next;
if (currentDay != null && currentDay.hasNext())
{
this.next = currentDay.next();
}
else
{
this.next = null;
}
return result;
}
private void nextDay()
{
while (incoming.hasNext()
&& firstOfNextDay.getTimestamp().toLocalDate()
.isEqual((firstOfNextDay = incoming.next()).getTimestamp().toLocalDate()))
{
nextDaysObjects.add(firstOfNextDay);
}
currentDay = nextDaysObjects.iterator();
if (incoming.hasNext())
{
nextDaysObjects = new TreeSet<>(Comparator.comparing(MyObject::getTimestamp));
nextDaysObjects.add(firstOfNextDay);
}
}
}
Use it like this:
public static void main(String[] args)
{
Stream<MyObject> stream = Stream.of(
new MyObject(LocalDateTime.now().plusHours(1)),
new MyObject(LocalDateTime.now()),
new MyObject(LocalDateTime.now().plusDays(1).plusHours(2)),
new MyObject(LocalDateTime.now().plusDays(1)),
new MyObject(LocalDateTime.now().plusDays(1).plusHours(1)),
new MyObject(LocalDateTime.now().plusDays(2)),
new MyObject(LocalDateTime.now().plusDays(2).plusHours(1)));
DayByDayIterator.streamOf(stream).forEach(System.out::println);
}
------------------- Output -----------------
2017-04-30T17:39:46.353
2017-04-30T18:39:46.333
2017-05-01T17:39:46.353
2017-05-01T18:39:46.353
2017-05-01T19:39:46.353
2017-05-02T17:39:46.353
2017-05-02T18:39:46.353
Explanation:
currentDay
and next
are the basis for the iterator, while firstOfNextDay
and nextDaysObjects
already look at the first element of the next day. When currentDay
is exhausted, nextDay()
is called and continues adding incoming
's element to nextDaysObjects
until the next next day is reached, then turns nextDaysObjects
into currentDay
.
One thing: If the incoming stream is null or empty, it will fail. You can test for null, but the empty case requires to catch an Exception in the factory method. I did not want to add this for readability.
I hope this is what you need, let me know how it goes.