Denis Yakovenko
Denis Yakovenko

Reputation: 3535

Weird behaviour of compareTo(GregorianCalendar c)

Could you tell me why the fillowing code:

int a = new GregorianCalendar(2015,3,31,7,45).compareTo(
        new GregorianCalendar(2015,4,1,7,45);
System.out.println(a);

prints out 0?

Is there a way to make it work right?

PS: I need to sort strings out by date and I use this comparator:

array.sort(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        GregorianCalendar cal1 = new GregorianCalendar(Integer.parseInt(o1.replaceAll(p, "$7")),
                Integer.parseInt(o1.replaceAll(p, "$6")), Integer.parseInt(o1.replaceAll(p, "$5")),
                Integer.parseInt(o1.replaceAll(p, "$8")), Integer.parseInt(o1.replaceAll(p, "$9")));
        GregorianCalendar cal2 = new GregorianCalendar(Integer.parseInt(o2.replaceAll(p, "$7")),
                Integer.parseInt(o2.replaceAll(p, "$6")), Integer.parseInt(o2.replaceAll(p, "$5")),
                Integer.parseInt(o2.replaceAll(p, "$8")), Integer.parseInt(o2.replaceAll(p, "$9")));
        return cal1.compareTo(cal2);
    }
});

It uses regular expressions, but it is sorted correctly and only the dates provided are not sorted right.

Upvotes: 1

Views: 98

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 338516

java.time

In modern Java, use java.time classes.

Unlike the legacy date-time classes, the java.time classes use sane numbering. So January-December is 1-12. No monkey-business, no surprises.

int a = 
    LocalDateTime
    .of ( 2015 , 3 , 31 , 7 , 45 )
    .compareTo(
        LocalDateTime.of ( 2015 , 4 , 1 , 7 , 45 )
    );

See this code run at Ideone.com.

-1

I would write that code this way:

LocalDateTime x = LocalDateTime.of ( 2015 , Month.MARCH , 31 , 7 , 45 ) ;
LocalDateTime y = LocalDateTime.of ( 2015 , Month.APRIL , 1 , 7 , 45 ) ;
boolean xIsBeforeY = x.isBefore( y ) ;

2015-03-31T07:45/2015-04-01T07:45

xIsBeforeY: true

Or use Duration class to represent a span-of-time. If positive, you know the first date-time comes before, or is equal to, the second.

Duration duration = 
    Duration.between(
        LocalDateTime.of ( 2015 , Month.MARCH , 31 , 7 , 45 ) ,
        LocalDateTime.of ( 2015 , Month.APRIL , 1 , 7 , 45 )
    );
boolean isPositive = duration.isPositive() ;

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1500495

You're comparing "April 31st" with May 1st. There is no April 31st, so it's rolling over to May 1st anyway. (Okay, it would make more sense to just throw an exception, but hey... that's far from the worst piece of API design in Calendar.)

I would strongly recommend using SimpleDateFormat to parse string representations of date/time values, instead of doing it yourself. Aside from anything else, SimpleDateFormat "knows" that months are 0-based in Java... which is the basic bug you've fallen foul of. The code would also be a lot more readable, I suspect.

Do you really need to keep the collection as a collection of strings anyway? If they're just dates, convert it to a collection of some date type (ideally using Joda Time or Java 8's java.time package). If they're something like log entries which have a date but also other information, convert them into a representation of that first. Either way, you've then got a collection which more naturally represents the information it holds.

Upvotes: 5

Related Questions