There are several ways you can tackle this problem. One way is to create yourself a sorted index of the birth month and year so you can quickly find the following birthdays for a given day.
The first thing you need to realise is that "next" birthdays shouldn't take into account the year. If you just sort or compare on the date, the year is going going to give you invalid dates. One way you to get round this to create a key for a birthday that is uniquely identifies the birthday irrespective of the year. More specifically you want a key that can be ordered in "chronological order". For example:
LocalDate d1 = LocalDate.parse("1980-10-31');
int key1 = d1.getMonthValue() * 100 + d1.getDayOfMonth();
LocalDate d2 = LocalDate.parse("2001-10-31');
int key2 = d2.getMonthValue() * 100 + d2.getDayOfMonth();
key1 == key2
So now that you have a way of generating a key, you will want to generate a map of the keys to the birthdays. You can do this easily enough by using a groupingBy collector.
Function<LocalDate, Integer> key = (d) -> d.getMonthValue() * 100 + d.getDayOfMonth();
Map<Integer, List<LocalDate>> lookup = birthdays.stream().collect(Collectors.groupingBy(key));
Note that we are collecting birthdays on the same days as a list, as it may be the case that there are multiple birthdays on the same day, in the same or different years.
Finally you will want a way to find the next three birthdays. One way to do this is to sort the keys, search for the position of the next entry and then read off the following two. Because this is an order-able list of keys we can create an index of the keys, sort it and use a binary search to find the next entry. So let's create the index:
List<Integer> searchIndex = lookup.keySet().stream().sorted().collect(Collectors.toList());
So now we can use the searchIndex
to find the nearest key to the key for the input date. We can then use that key to find the list of birthdays for that key from the lookup
.
We can get the following two dates by just looking at the next two keys in the index. Though note that you will want to wrap to the beginning of the index when you get to the end. For example if you search for next three birthdays on the 31st of December, you will want it to continue checking in January.
List<LocalDate> birthdays = getAllBirthdays();
Function<LocalDate, Integer> key = (d) -> d.getMonthValue() * 100 + d.getDayOfMonth();
Map<Integer, List<LocalDate>> lookup = birthdays.stream().collect(Collectors.groupingBy(key));
List<Integer> searchIndex = lookup.keySet().stream().sorted().collect(Collectors.toList());
int index = Collections.binarySearch(searchIndex, key.apply(LocalDate.now()));
// Find the positive index if the index doesn't contain the current MMdd
if (index < 0) {
index = -index -1;
}
for (int i = 0; i < 3; ++i) {
// Wrap around to the start of the year
if (index >= searchIndex.size()) {
index = 0;
}
System.out.println(lookup.get(searchIndex.get(index++)));
}
I've used java.util.LocalDate
in the above code, but you could as easily use joda's LocalDate
. Both are preferable to using java.util.Date