Reputation: 33
I have a list of objects that have a LocalDateTime
field.
I want to sort all these objects very specially per date and time.
I explain how it should be done :
Sort by Date Descending BUT by Time Ascending
Here is an example :
Non sorted LocalDateTime
:
Should be sorted in this order :
Remember that I need to sort an object with a field, not a list of LocalDateTime
, but a List of Object with a LocalDateTime
field.
Thank you for helping me :)
Upvotes: 3
Views: 11806
Reputation: 338730
LocalDateTime
objects know how to sort themselves chronologically. Your desire to sort by date descending (reverse chronological, later dates come first) yet also sort secondarily by time-of-day ascending (chronological) means the built-in functionality of the class’ implementation of compareTo
method (required by the Comparable
interface) cannot do the job.
For custom sorting, write your own Comparator
implementation. That interface requires one method be implemented: compare
.
The logic here is simple:
Code.
package work.basil.example;
import java.time.LocalDateTime;
import java.util.Comparator;
public class LocalDateTimeComparator implements Comparator < LocalDateTime >
{
@Override
public int compare ( LocalDateTime o1 , LocalDateTime o2 )
{
// Compare the date portion first. If equal, then look at time-of-day.
int result = o1.toLocalDate().compareTo( o2.toLocalDate() ); // Consider only the date portion first.
result = ( ( - 1 ) * result ); // Flip the positive/negative sign of the int, to get ascending order. Or more simply: `= - result ;`.
if ( 0 == result ) // If dates are equal, look at the time-of-day.
{
System.out.println( "reversing " );
result = o1.toLocalTime().compareTo( o2.toLocalTime() );
}
return result;
}
}
Try out your example data with this comparator.
List < LocalDateTime > ldts = List.of(
LocalDateTime.parse( "2016-10-06T09:10" ) ,
LocalDateTime.parse( "2016-10-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T11:15" ) ,
LocalDateTime.parse( "2016-11-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T10:10" ) ,
LocalDateTime.parse( "2016-12-06T06:56" )
);
List < LocalDateTime > sorted = new ArrayList <>( ldts );
Comparator < LocalDateTime > comparator = new LocalDateTimeComparator();
sorted.sort( comparator );
Dump to console. We see success. The dates are in order of the October, November, and December dates of 2016, while the time-of-day
System.out.println( "ldts = " + ldts );
System.out.println( "sorted = " + sorted );
ldts = [2016-10-06T09:10, 2016-10-06T10:34, 2016-12-06T11:15, 2016-11-06T10:34, 2016-12-06T10:10, 2016-12-06T06:56]
sorted = [2016-12-06T06:56, 2016-12-06T10:10, 2016-12-06T11:15, 2016-11-06T10:34, 2016-10-06T09:10, 2016-10-06T10:34]
The comment by Ole V.V. shows how do do the equivalent work using functional lambda syntax in modern Java. That comment inspired me to try the functional approach.
The idea here is to use two Comparator
objects: one for the date, and one for the time-of-day. We can nest one Comparator
within another, in effect, by calling Comparator::thenComparing
. So we need to establish both comparators, then feed one to the other. We instantiate a comparatorDate
, then feed that one a comparatorTime
, to get a comparatorDateThenTime
. We pass comparatorDateThenTime
to the sort
method to actually get the sorting work performed.
List < LocalDateTime > ldts = List.of(
LocalDateTime.parse( "2016-10-06T09:10" ) ,
LocalDateTime.parse( "2016-10-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T11:15" ) ,
LocalDateTime.parse( "2016-11-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T10:10" ) ,
LocalDateTime.parse( "2016-12-06T06:56" )
);
List < LocalDateTime > sorted = new ArrayList <>( ldts );
Comparator < LocalDateTime > comparatorDate =
Comparator
.comparing( ( LocalDateTime ldt ) -> ldt.toLocalDate() )
.reversed();
Comparator < LocalDateTime > comparatorTime =
Comparator
.comparing( ( LocalDateTime ldt ) -> ldt.toLocalTime() );
Comparator < LocalDateTime > comparatorDateThenTime =
comparatorDate
.thenComparing(
comparatorTime
);
sorted.sort( comparatorDateThenTime );
// Dump to console.
System.out.println( "ldts = " + ldts );
System.out.println( "sorted = " + sorted );
ldts = [2016-10-06T09:10, 2016-10-06T10:34, 2016-12-06T11:15, 2016-11-06T10:34, 2016-12-06T10:10, 2016-12-06T06:56]
sorted = [2016-12-06T06:56, 2016-12-06T10:10, 2016-12-06T11:15, 2016-11-06T10:34, 2016-10-06T09:10, 2016-10-06T10:34]
We can pull that all together using a one-liner using anonymous Comparator
objects returned from calls to Comparator.comparing
and Comparator.reversed
.
List < LocalDateTime > ldts = List.of(
LocalDateTime.parse( "2016-10-06T09:10" ) ,
LocalDateTime.parse( "2016-10-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T11:15" ) ,
LocalDateTime.parse( "2016-11-06T10:34" ) ,
LocalDateTime.parse( "2016-12-06T10:10" ) ,
LocalDateTime.parse( "2016-12-06T06:56" )
);
List < LocalDateTime > sorted = new ArrayList <>( ldts );
sorted.sort(
Comparator
.comparing( ( LocalDateTime ldt ) -> ldt.toLocalDate() )
.reversed()
.thenComparing(
Comparator
.comparing( ( LocalDateTime ldt ) -> ldt.toLocalTime() )
)
);
// Dump to console.
System.out.println( "ldts = " + ldts );
System.out.println( "sorted = " + sorted );
ldts = [2016-10-06T09:10, 2016-10-06T10:34, 2016-12-06T11:15, 2016-11-06T10:34, 2016-12-06T10:10, 2016-12-06T06:56]
sorted = [2016-12-06T06:56, 2016-12-06T10:10, 2016-12-06T11:15, 2016-11-06T10:34, 2016-10-06T09:10, 2016-10-06T10:34]
I think I would prefer to see the first one, the multi-line one, in production code. But I am not sure.
The real problem stated in the Question involves a LocalDateTime
as a member field of another class. So let's expand our solution to include that nesting class. Here we invent a Happening
class consisting of a description string with a LocalDateTime
object.
package work.basil.example;
import java.time.LocalDateTime;
import java.util.Objects;
public class Happening
{
private String description;
private LocalDateTime localDateTime;
public Happening ( String description , LocalDateTime localDateTime )
{
this.description = Objects.requireNonNull( description );
this.localDateTime = Objects.requireNonNull( localDateTime );
}
public String getDescription ( ) { return this.description; }
public LocalDateTime getLocalDateTime ( ) { return this.localDateTime; }
@Override
public String toString ( )
{
return "Happening{ " +
"description='" + description + '\'' +
" | localDateTime=" + localDateTime +
" }";
}
}
Let's make a collection of those objects, and sort using code similar to that seen above. We must go one extra step, extracting a LocalDateTime
object from within each Happening
object.
List < Happening > happenings = List.of(
new Happening( "aaa" , LocalDateTime.parse( "2016-10-06T09:10" ) ) ,
new Happening( "bbb" , LocalDateTime.parse( "2016-10-06T10:34" ) ) ,
new Happening( "ccc" , LocalDateTime.parse( "2016-12-06T11:15" ) ) ,
new Happening( "ddd" , LocalDateTime.parse( "2016-11-06T10:34" ) ) ,
new Happening( "eee" , LocalDateTime.parse( "2016-12-06T10:10" ) ) ,
new Happening( "fff" , LocalDateTime.parse( "2016-12-06T06:56" ) )
);
List < Happening > sorted = new ArrayList <>( happenings );
sorted.sort(
Comparator
.comparing( ( Happening happening ) -> happening.getLocalDateTime().toLocalDate() )
.reversed()
.thenComparing(
Comparator
.comparing( ( Happening happening ) -> happening.getLocalDateTime().toLocalTime() )
)
);
// Dump to console.
System.out.println( "happenings = " + happenings );
System.out.println( "sorted = " + sorted );
When run we go from a-b-c-d-e-f to f-e-c-d-a-b order.
happenings = [Happening{ description='aaa' | localDateTime=2016-10-06T09:10 }, Happening{ description='bbb' | localDateTime=2016-10-06T10:34 }, Happening{ description='ccc' | localDateTime=2016-12-06T11:15 }, Happening{ description='ddd' | localDateTime=2016-11-06T10:34 }, Happening{ description='eee' | localDateTime=2016-12-06T10:10 }, Happening{ description='fff' | localDateTime=2016-12-06T06:56 }]
sorted = [Happening{ description='fff' | localDateTime=2016-12-06T06:56 }, Happening{ description='eee' | localDateTime=2016-12-06T10:10 }, Happening{ description='ccc' | localDateTime=2016-12-06T11:15 }, Happening{ description='ddd' | localDateTime=2016-11-06T10:34 }, Happening{ description='aaa' | localDateTime=2016-10-06T09:10 }, Happening{ description='bbb' | localDateTime=2016-10-06T10:34 }]
Upvotes: 9