Wednesday, 19 September 2012

Puzzle 61: The Dating Game


The following program exercises some basic features of the Date and Calendar classes. What does it print?
import java.util.*;



public class DatingGame {

    public static void main(String[] args) {

        Calendar cal = Calendar.getInstance();

        cal.set(1999, 12, 31); // Year, Month, Day

        System.out.print(cal.get(Calendar.YEAR) + " ");



        Date d = cal.getTime();

        System.out.println(d.getDay());

    }

}


Solution 61: The Dating Game

This program creates a Calendar instance that appears to represent New Year's Eve, 1999, and prints the year followed by the day. It seems that the program should print 1999 31, but it doesn't; it prints 2000 1. Could this be the dreaded Y2K problem?
No, it's something much worse: It is the dreaded Date/Calendar problem. When the Java platform was first released, its only support for calendar calculations was the Date class. This class was limited in power, especially when it came to support for internationalization, and it had a basic design flaw: Date instances were mutable. In release 1.1, the Calendar class was added to the platform to rectify the shortcomings of Date; most Date methods were deprecated. Unfortunately, this only made a bad situation worse. Our program illustrates a few of the problems with the Date and Calendar APIs.
The program's first bug is in the method invocation cal.set(1999, 12, 31). When months are represented numerically, convention dictates that the first month be assigned the number 1. Unfortunately, Date represents January as 0, and Calendar perpetuates this mistake. Therefore, this method invocation sets the calendar to the thirty-first day of the thirteenth month of 1999. But the standard (Gregorian) calendar has only 12 months; surely this method invocation should cause an IllegalArgumentException? It should, but it doesn't. The Calendar class silently substitutes the first month of the next year, in this case, 2000. This explains the first number printed by our program (2000).
There are two ways to fix this problem. You could change the second parameter of the cal.set invocation from 12 to 11, but that would be confusing. The number 11 would suggest November to readers. It would be better to use the constant that Calendar declares for this purpose, which is Calendar.DECEMBER.
What about the second number printed by the program? The cal.set invocation clearly indicates that the calendar is set to the thirty-first day of the month. The Date instance d represents the same point in time as the Calendar, so its getday method should return 31, but the program prints 1. What is going on?
To find out, you have to read the documentation, which says that Date.getDay returns the day of the week represented by the Date instance, not the day of the month. The returned value is 0-based, starting at Sunday, so the 1 printed by the program indicates that January 31, 2000, fell on a Monday. Note that the corresponding Calendar method, get(Calendar.DAY_OF_WEEK), inexplicably returns a day-of-the-week value that is 1-based, not 0-based like the value returned by its Date counterpart.
There are also two ways to fix this problem. You could call the confusingly named Date.date method, which returns the day of the month. Like most Date methods, however, it is deprecated, so you would be better off dispensing with Date entirely and calling the Calendar method get(Calendar.DAY_OF_MONTH). With both problems fixed, the program prints 1999 31 as expected:
public class DatingGame {

    public static void main(String[] args) {

        Calendar cal = Calendar.getInstance();

        cal.set(1999, Calendar.DECEMBER, 31);

        System.out.print(cal.get(Calendar.YEAR) + " ");

        System.out.println(cal.get(Calendar.DAY_OF_MONTH));

    }

}


This puzzle only scratches the surface of the defects in Calendar and Date. These APIs are minefields. Other serious problems with Calendar include weak typing (nearly everything is an int), an overly complex state-space, poor structure, inconsistent naming, and inconsistent semantics. Be careful when using Calendar or Date; always consult the API documentation.
The lesson for API designers is: If you can't get it right the first time, at least get it right the second; there may not be a third. If your first attempt at an API has serious problems, your customers may be forgiving and give you another chance. If your second attempt has problems, you may be stuck with them for good.

No comments:

Post a Comment

Your comments are welcome!