Java Calendar Puzzlers

As an extreme example of what not to do, consider the case of java.util.Calendar. Very few people understand its state-space — I certainly don’t — and it’s been a constant source of bugs for years.

Joshua Bloch talking about API design

In other words, whenever you see Calendar in use, perpare yourself for nasty bugs.

The following three puzzlers, inspired by the Java Puzzlers book concept, will hopefully surprise you and teach you.

Complete code can be found at the end of the article.

Puzzler 1

 final Calendar cal = Calendar.getInstance();
 cal.set(2010, 0, 0);
 System.out.println("1. " + cal.getTime());

 cal.set(2010, 1, 1);
 System.out.println("2. " + cal.getTime());

 cal.set(2010, 0, 1);
 System.out.println("3. " + cal.getTime());

 cal.set(2010, Calendar.FEBRUARY, 29);
 System.out.println("4. " + cal.getTime());

 cal.set(2009, Calendar.UNDECIMBER, 0);
 System.out.println("5. " + cal.getTime());

Can you guess the month and year for each one ?

Hey, guess before reading the solution !

The Disintegration of Persistence of Memory Salvador Dali

The Disintegration of Persistence of Memory Salvador Dali

Solution:

1. 31 December 2009

What happened, we asked for 2010, didn’t we ?

2. 01 February 2010

A bit better, we now have 2010, but still not january !

3. 01 January 2010

Ok that is what we expected for the first one.

4. 01 March 2010

We asked for FEBRUARY, but there is no february 29 in year 2010 (Learn about leap years). So the calendar added one day to february 28 and thus we end up in the next month.

5. 31 December 2009

This one resembles the first one, day zero is not valid, it means ‘remove one day’, because UNIDECEMBER is the 13th month, we end up in 13th month minus one day…

Lesson:

Calendar month are zero-indexed, while days are not. Calendar will not throw an error when passed parameters that are not coherent with what you specified (year, month, …).

Use existing Calendar constants as much as possible and think about leap years.

Puzzler 2

 final Calendar cal1 = Calendar.getInstance();
 final Calendar cal2 = Calendar.getInstance(Locale.US);
 System.out.println(cal1.getFirstDayOfWeek() == cal2.getFirstDayOfWeek());

True or false ?

Solution:

It depends…on your default locale. Calendar is a locale aware class, getInstance() will use the default locale and timezone.

Lesson:

Think about how the locale can impact your code, this is also valid for other classes.

If you know you only want a specific timezone and locale, enforce them by using alternative getInstance methods.

Puzzler 3

 final Calendar calendar1 = Calendar.getInstance();
 final int year = 2010;
 final int month = Calendar.APRIL;
 final int day = 14;
 calendar1.set(year, month, day);

 final int iterationCount = 1000;
 boolean isEqual = false;
 for (int i = 0; i < iterationCount; i++) {
   final Calendar calendar2 = Calendar.getInstance();
   calendar2.set(year, month, day);
   isEqual = calendar1.equals(calendar2);
 }

Solution:

It depends, on many things, try changing the iterationCount.

Calendar.getInstance() method returns more than just the current year, month and day. The hour, minutes and milliseconds are also set. So you may have code working on your machine but it will fail on another. What happens here is that the milliseconds changed, because getInstance returns the current time and time does change while your program is running. Such bugs can be extremely hard to track, if you are unlucky they may only show up from time to time depending on circumstances (CPU load).

Lesson:

To avoid such mistakes, either clone a calendar, or call the clear() method of Calendar before setting. When writing tests on methods using Calendars, rely on getTimeInMillis() instead of other getters, this is what the equals method relies on.

Conclusion:

The puzzlers have shown how counter-intuitive the Calendar class is, if your work involves a lot of time manipulation think about using Joda Time.

http://joda-time.sourceforge.net/


package javarizon;

import java.util.Calendar;
import java.util.Locale;

/**
 * <a href="https://javarizon.wordpress.com/2010/04/25/java-calendar-puzzlers/">
 * Java Calendar Puzzlers</a>
 *
 * @author Christophe Roussy
 */
public class CalendarPuzzlers {

 public static void puzzler1() {
 final Calendar cal = Calendar.getInstance();
 cal.set(2010, 0, 0);
 System.out.println("1. " + cal.getTime());

 cal.set(2010, 1, 1);
 System.out.println("2. " + cal.getTime());

 cal.set(2010, 0, 1);
 System.out.println("3. " + cal.getTime());

 cal.set(2010, Calendar.FEBRUARY, 29);
 System.out.println("4. " + cal.getTime());

 cal.set(2009, Calendar.UNDECIMBER, 0);
 System.out.println("5. " + cal.getTime());
 }

 public static void puzzler2() {
 final Calendar cal1 = Calendar.getInstance();
 final Calendar cal2 = Calendar.getInstance(Locale.US);
 System.out.println(cal1.getFirstDayOfWeek() == cal2.getFirstDayOfWeek());
 }

 public static void puzzler3() {
 final Calendar calendar1 = Calendar.getInstance();
 final int year = 2010;
 final int month = Calendar.APRIL;
 final int day = 14;
 // calendar1.clear();
 calendar1.set(year, month, day);

 final int iterationCount = 1000;
 boolean isEqual = false;
 for (int i = 0; i < iterationCount; i++) {
 final Calendar calendar2 = Calendar.getInstance();
 // calendar2.clear();
 calendar2.set(year, month, day);
 isEqual = calendar1.equals(calendar2);
 }
 System.out.println(isEqual);
 }

 public static void main(String[] args) {
 puzzler1();
 puzzler2();
 puzzler3();
 }
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s