tags:

views:

49

answers:

4

I just encountered a strange behaviour with the GregorianCalendar class, and I was wondering if I really was doing something bad.

This only appends when the initialization date's month has an actualMaximum bigger than the month I'm going to set the calendar to.

Here is the example code :

    // today is 2010/05/31  
    GregorianCalendar cal = new GregorianCalendar();

    cal.set(Calendar.YEAR, 2010);
    cal.set(Calendar.MONTH, 1); // FEBRUARY

    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
    cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
    cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
    cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
    cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

    return cal.getTime(); // => 2010/03/03, wtf

I know the problem is caused by the fact that the calendar initialization date is a 31 day month ( may ), which mess with the month set to february (28 days). The fix is easy ( just set day_of_month to 1 before setting year and month ), but I was wondering is this really was the wanted behaviour. Any thoughts ?

A: 

Yes, this is how it is intended to work. If you start from a GregorianCalendar that has a precise date and you modify it by making it inconsistent then you shouldn't trust the results you obtain.

According to the documentation about getActualMaximum(..) it states:

For example, if the date of this instance is February 1, 2004, the actual maximum value of the DAY_OF_MONTH field is 29 because 2004 is a leap year, and if the date of this instance is February 1, 2005, it's 28.

So it is supposed to work but you have to feed it with consistent values. 31 February 2010 is not correct and applying things that relies on the date value (like getActualMaximum) can't work. How should it fix it by itself? By deciding that month is wrong? or that the day is wrong?

By the way, as everyone always states use JodaTime.. :)

Jack
If you set the month to February, a normal API would realize that that invalidates the day and it needs to be adjusted. But `java.util.Calendar` is many things, but not a normal API. See the first comment here for how JodaTime does it: http://blog.smart-java.nl/blog/index.php/2010/01/20/java-util-calendar-getactualmaximum-returns-strange-results/
Yishai
+1  A: 

Maybe setLenient(boolean lenient) will sort it out for you. I get an exception when I run the code below.

If not, Joda is a better answer.

import java.util.Calendar;

public class CalTest
{
    public static void main(String[] args)
    {
        // today is 2010/05/31
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);

        cal.set(Calendar.YEAR, 2010);
        cal.set(Calendar.MONTH, 1); // FEBRUARY

        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        System.out.println(cal.getTime());
    }
}
duffymo
Unfortunately even a strict calendar is of limited usefulness. It would throw an exception at `getTime` but nothing else. http://blog.smart-java.nl/blog/index.php/2010/01/20/java-util-calendar-getactualmaximum-returns-strange-results/
Yishai
Agreed - that's what I observe as well.
duffymo
A: 

I'm sure it is not wanted behavior. I'm just equally sure no one really thought that use case through when they made the class. The fact of the matter is that Calendar has a very big problem with internal state and how it manages all of the potential transitions in all the set methods.

If you can't use JodaTime or JSR-310 in your project, unit test heavily when using the Calendar class. As you can see in this case Calendar code behaves differently depending on what day of the month (or what time of the day) you run the code.

Yishai
+3  A: 

It is getting the actual maximums of the current date/time. May has 31 days which is 3 more than 28 February and it will thus shift to 3 March.

You need to call Calendar#clear() after obtaining/creating it:

GregorianCalendar cal = new GregorianCalendar();
cal.clear();
// ...

This results in:

Sun Feb 28 23:59:59 GMT-04:00 2010

(which is correct as per my timezone)

As said in one of the answers, the java.util.Calendar and Date are epic failures. Consider JodaTime when doing intensive date/time operations.

BalusC