As I understood this problem you rely on an existing API that expects a list of events and want them to be processed altogether as a single list ordered accordingly with their start and end date-time. And that is achievable.
I assume that the time-related event data stored inside a ProgrammData object is of type String. If it's not the case, and these fields are for instance of legacy type Date then only a couple of small changes need to be done.
My idea is to encapsulate all functionality inside the utility class TwoWaySorting so that all implementation details are abstracted away. And in the client code only this line TwoWaySorting.getSorted(programmData) is needed to produce a sorted list of events.
Since the responsibility of the TwoWaySorting class isn't related to the state all its behavior is marked with static modifier and its constructor is private. It has a nested static class Element which is a wrapper on top of the ProgrammData (if my assumption that ProgrammData is part of the existing IPA is correct then it must be used as is without any changes). So Element's class concern is to represent the time-related data of the wrapped ProgrammData object in a convenient form for sorting.
That is a brief overview of what the method getSorted() does:
- it obtains an instance the Comparator, that is based on the current time;
- creates the stream over the list of events and wraps each event with an instance of the
Element class;
- sorts the elements;
- extracts original events and collects them into a list.
To wrap an event the static method LocalDateTime.parse() which accept a CharSequence and an appropriate formatter is used to parse string-based time-related data.
The centerpiece of TwoWaySorting class is a comparator returned by the getComparator() method, let's examine it closely.
The first part of it is responsible for dividing the elements into two groups based on currentTime:
Comparator.<Element, Boolean>comparing(element -> element.getEnd().isBefore(currentTime))
As its name suggest the instance methed isBefore() of the LocalDateTime class returns true if this date-time object is before the date-time object passed as an argument.
According to the natural sorting order of boolean values false comes before true. So for an invent that ends in the future isBefore() will yield false, which means that it'll appear at the beginning of the sorted list.
The second part of the comparator is responsible for ordering of the past and future events:
.thenComparingLong(element -> element.getEnd().isAfter(currentTime) ?
element.getStart().toEpochSecond(ZoneOffset.of("+00:00")) :
element.getEnd().toEpochSecond(ZoneOffset.of("+00:00")) * -1);
Returns:
a lexicographic-order comparator composed of this and then the long sort key
Method thenComparingLong() (a quote from the javadoc is shown above) returns an aggregated comparator comprised of the comparator that was obtained previously (that separates past and future events) and a comparator thas is based on the ToLongFunction provided as an argument, which compares elements accordingly with long values extracted by that function.
Method toEpochSecond() extructs from a date-time object the number of seconds from the epoch as long.
I assume that it is sufficient for this task because in the example time is described with a precision of minutes. ZoneOffset that is expected by toEpochSecond() as an argument, in this case, has no influence on the result, and offset for Greenwich can be substituted with any other valid offset.
Since future events have to be sorted in ascending order value produced by the toEpochSecond() is used as is, for past events that must be sorted in descending order it's multiplied by -1 to reverse the result of the comparison.
Note:
- in order to combine these two comparators described above together we have to provide generic type information explicitly, like that:
<Element,Boolean>comparing(). Without an explicit declaration, the compiler has not enough data to determine the type of the variable element, and inside both comparing() and thenComparingLong() its type will be inferred as Object. If we used only one of these static methods the type of the element will be correctly inferred by the compiler as Element based on the return type of the method getComparator(). But for our case, we need to provide this information explicitly.
for information on the syntax of generic methods, take a look at this tutorial
TwoWaySorting class
public class TwoWaySorting {
private static final DateTimeFormatter PD_FORMATTER
= DateTimeFormatter.ofPattern("yyyy-MM-ddHHmm");
private TwoWaySorting() {} // no way and no need to instantiate this class
private static Comparator<Element> getComparator() {
LocalDateTime currentTime = LocalDateTime.now();
return Comparator.<Element, Boolean>comparing(element -> element.getEnd().isBefore(currentTime))
.thenComparingLong(element -> element.getEnd().isAfter(currentTime) ?
element.getStart().toEpochSecond(ZoneOffset.of("+00:00")) :
element.getEnd().toEpochSecond(ZoneOffset.of("+00:00")) * -1);
}
public static List<ProgrammData> getSorted(List<ProgrammData> programmData) {
Comparator<Element> twoWayComparator = getComparator();
return programmData.stream()
.map(TwoWaySorting::parseData)
.sorted(twoWayComparator)
.map(Element::getData)
.collect(Collectors.toList());
}
private static Element parseData(ProgrammData data) {
return new Element(data,
LocalDateTime.parse(data.getStart(), PD_FORMATTER),
LocalDateTime.parse(data.getEnd(), PD_FORMATTER));
}
private static class Element {
private ProgrammData data;
private LocalDateTime start;
private LocalDateTime end;
// constructor and getters
}
}
This solution is meant to be clean and reusable. So in the main() apart from the source list there's only one line that obtains a sorted list and prints it on the console.
Note: getSorted() doesn't cause mutation of the source but creates and new list.
public static void main(String[] args) {
List<ProgrammData> programmData = // a list dummy ProgrammData objects
List.of(new ProgrammData("event1", "2022-02-220100", "2022-02-220300"),
new ProgrammData("event2", "2022-02-220200", "2022-02-241800"),
new ProgrammData("event3", "2022-02-251200", "2022-02-281500"),
new ProgrammData("event4", "2022-02-261600", "2022-02-262100"));
TwoWaySorting.getSorted(programmData)
.forEach(System.out::println);
}
Output (identical with the provided example)
ProgrammData [event3, 2022-02-251200, 2022-02-281500]
ProgrammData [event4, 2022-02-261600, 2022-02-262100]
ProgrammData [event2, 2022-02-220200, 2022-02-241800]
ProgrammData [event1, 2022-02-220100, 2022-02-220300]