1

I want to sort the list based on the date field between the two days, say from now and the next 3 days. The sorted list should the days from now and the next 3 days in ascending order and all previous and future dates in ascending order after that.

e.g.:

List<LocalDate> list = Arrays.asList(
            LocalDate.of(2021, 8, 1),
            LocalDate.of(2021, 8, 2),
            LocalDate.of(2021, 8, 3),
            LocalDate.of(2021, 8, 8),
            LocalDate.of(2021, 8, 9),
            LocalDate.of(2021, 8, 10),
            LocalDate.of(2021, 8, 11),
            LocalDate.of(2021, 8, 12),
            LocalDate.of(2021, 8, 13),
            LocalDate.of(2021, 8, 14),
            LocalDate.of(2021, 8, 15),
            null);`   

Assumed today is 8 Aug 2021 out would be:

null (null date always at the top)
8-Aug-2021
9-Aug-2021
10-Aug-2021 (next 3 days up to here in ascending order)
8-Aug-2021 ( from here all past and future dates in ascending order here)
1-Aug-2021
2-Aug-2021
3-Aug-2021
11-Aug-2021
12-Aug-2021
13-Aug-2021
14-Aug-2021
15-Aug-2021

Comparator<LocalDate> comp = (o1, o2) -> {
    if (o1.isBefore(now) && o2.isBefore(now) || o1.isAfter(now.plusDays(3)) && o2.isAfter(now.plusDays(3))) {
        return o1.compareTo(o2);
    }
    return o2.compareTo(o1);
};
1
  • 2
    Is it intentional that the example output duplicates date 8-Aug-2021, which appears only once in the example input? Commented Aug 8, 2021 at 14:54

3 Answers 3

4

Since you say that you want to sort, and your example code is a Comparator implementation, I'm going to suppose that the duplication of date 8-Aug-2021 in the example output was unintentional. A bona fide sort will of course only reorder the input objects, not duplicate them or otherwise introduce new ones.

What you're looking for is then just a three-key sort. The first key compares null ahead of non-null. The second key is based on whether the dates are in the chosen window -- those that are sort ahead of those that are not. The third is the natural order of the dates.

The main wrinkle is that to ensure that the first key is computed consistently throughout the sort, you must compute the current date once only, at the time the Comparator is instantiated. This will be a property of the Comparator, in the sense that existing instances will not adjust automatically to the system clock rolling over to a new date.

For example:

final LocalDate today = LocalDate.now();

Comparator<LocalDate> comp = (o1, o2) -> {

    if (o1 == null) {
        return (o2 == null) ? 0 : -1;
    } else if (o2 == null) {
        // At this point, o1 is known to be non-null
        return 1;
    } else {
        // Note: plusDays(2) because that's an _inclusive_ end date for the 3-day
        // range
        boolean outsideWindow1 = o1.isBefore(today) || o1.isAfter(today.plusDays(2));
        boolean outsideWindow2 = o2.isBefore(today) || o2.isAfter(today.plusDays(2));

        if (outsideWindow1 != outsideWindow2) {
            // One date is inside the window and the other outside. then
            // The one inside is sorted ahead of the other
            return outsideWindow1 ? 1 : -1;
        } else {
            // Either both dates are in the 3-day window or both are outside it.
            // Sort by their natural order
            return o1.compareTo(o2);
        }
    }
};
Sign up to request clarification or add additional context in comments.

Comments

3

Comparator.nullsFirst(), .comparing() and .thenComparing()

I like John Bollinger’s idea of using an advanced comparator for it all. Here’s my attempt to word it as elegantly as I can.

    LocalDate today = LocalDate.now(ZoneId.systemDefault());
    LocalDate inThreeDays = today.plusDays(3);
    Function<LocalDate, Boolean> notWithinNext3Days = ld -> ld.isBefore(today) || ld.isAfter(inThreeDays);
    Comparator<LocalDate> myComparator = Comparator.nullsFirst(
            Comparator.comparing(notWithinNext3Days)
                    .thenComparing(LocalDate::compareTo));

For consistent results if the code runs across midnight we need to read the clock only once. I am exploiting the fact that the natural ordering of Boolean objects is false before true. Let’s try it with your list.

    list.sort(myComparator);
    list.forEach(System.out::println);

Running on August 9 the output was:

null
2021-08-09
2021-08-10
2021-08-11
2021-08-12
2021-08-01
2021-08-02
2021-08-03
2021-08-08
2021-08-13
2021-08-14
2021-08-15

As you can see, 4 days in all are sorted between null and the rest (August 9 through 12). I didn’t know whether now and the next 3 days meant a 4 day window in all. I trust anyone using my answer to adjust as desired.

1 Comment

I like this (+1): it's cleaner and clearer than the more manual comparator implementation in my answer. However, it does seem to have a small variance from the request: it pulls out a four-day window of dates to the head of the results instead of a three-day window.
2

You can use Stream#concat as shown below:

List<LocalDate> result = 
    Stream.concat(
        list.stream()
            .filter(Objects::nonNull)
            .filter(date -> !date.isBefore(today) && !date.isAfter(thirdDay)),
        Stream.concat(
            Stream.of(today),
            list.stream()
                .filter(Objects::nonNull)
                .filter(date -> 
                    date.isBefore(today) || date.isAfter(thirdDay)
                ).sorted()
        )
    ).collect(Collectors.toList());

ONLINE DEMO

If you do not want to repeat today's date, you can do it as follows:

List<LocalDate> result = 
    Stream.concat(
        list.stream()
            .filter(Objects::nonNull)
            .filter(date -> !date.isBefore(today) && !date.isAfter(thirdDay)),
        list.stream()
            .filter(Objects::nonNull)
            .filter(date -> date.isBefore(today) || date.isAfter(thirdDay))
            .sorted()
    ).collect(Collectors.toList());

ONLINE DEMO

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.