Ilkan
Ilkan

Reputation: 33

Sort list by LocalDate descending and LocalTime ascending

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 :

  1. 2016-12-06T06:56
  2. 2016-12-06T10:10
  3. 2016-12-06T11:15
  4. 2016-11-06T10:34
  5. 2016-10-06T09:10
  6. 2016-10-06T10:34

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

Answers (1)

Basil Bourque
Basil Bourque

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.

Conventional syntax

For custom sorting, write your own Comparator implementation. That interface requires one method be implemented: compare.

The logic here is simple:

  • Compare the date portions.
    • If the two dates differ, reverse sort on that basis, and move on down the list.
    • If the same date on both, then dig deeper to compare their time-of-day portions, sorting chronologically.

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]

Lambda syntax

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

Related Questions