ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Making Sense of Java's Dates
Pages: 1, 2

Additional Functionality

Implementation Leakage

It has to be said that implementation details have been oozing into the APIs to an uncommon degree for the "new" date-handling classes. Up to a point, this is a reflection of their intended use as base classes for customized development, but it also seems to occasionally be a consequence of insufficient clarity in the design of the public interfaces. Whether the Calendar abstraction maintains two redundant data sets or not is properly an implementation detail, and should therefore be hidden from clients of the class. This includes clients who intend to reuse the class through inheritance, as well.

The additional functions offered by the Calendar base class fall into three categories. There are several static factory methods to obtain instances initialized for arbitrary time zones and locales. As mentioned above, all instances obtained this way have already been initialized to the current time. No factory methods are provided to obtain a Calendar instance initialized to an arbitrary point in time.

The second group of methods consists of the methods before( Object ) and after( Object ). They take arguments of type Object, thus allowing these methods to be overridden in subclasses for arbitrary types of arguments.

Finally, there are a number of functions to get and set additional properties, such as the current time zone. Among them are several methods that query the possible and actual minimum and maximum values of certain fields for the current calendar implementation.

When Does the Week Begin?

The documentation on the Calendar classes devotes considerable text to the proper counting of weeks in a month or year. Which weekday is considered the beginning of the week differs from country to country. In the U.S., a week is commonly considered to start on Sunday. In parts of Europe, a week starts on Monday and ends on Sunday. This can affect which week is considered the first full week of the year (or month) and also the counting of weeks throughout the year.

java.util.GregorianCalendar

The class GregorianCalendar is the only commonly available subclass of Calendar. It provides an implementation of the basic Calendar abstraction suitable for the interpretation of dates according to the conventions used commonly in the West. It adds a number of public constructors, as well as some functions specific to Gregorian Calendars, such as isLeapYear().

java.util.TimeZone and java.util.SimpleTimeZone

The TimeZone class and its subclasses are auxiliary classes, required by Calendar to interpret dates according to the selected time zone. Semantically, a time zone specifies a certain offset to be added to GMT to reach the local time. Clearly, this offset changes when daylight savings time (DST) is in effect. The TimeZone abstraction therefore needs to keep track not only of the additional offset to be applied if DST is in effect, but also of the rules that determine when DST is in effect, in order to calculate the local time for any given date and time.

The abstract base class TimeZone provides basic methods to handle "raw" (without taking DST into account) and actual offsets (in milliseconds!), but implementation of any functionality related to DST rules is left to subclasses, such as SimpleTimeZone. The latter class provides several ways to specify rules controlling the beginning and ending of DST, such as a giving an explicit day in a month or a certain weekday following a given date. Each TimeZone also has a human-readable, locale-dependent display name. Display names come in two styles: LONG and SHORT.

Time zones are unambiguously determined by an identifier string. The base class provides the static method String[] getAvailableIDs() to obtain all installed "well-known" standard time zones. (There are 557 for my installation, using JDK 1.4.1.) The JavaDoc defines the proper syntax to build custom time zone identifiers, if the need arises. Also provided are static factory methods, to obtain TimeZone instances — either for a specific ID or the default for the current location. SimpleTimeZone also provides some public constructors and, surprisingly for an abstract class, so does TimeZone. (The JavaDoc states: "For invocation by subclass constructors." Apparently, it should have been declared protected.)

java.text.DateFormat

While Calendar and related classes handle the locale-specific interpretation of dates, the DateFormat classes assist with the transformation of dates to and from human-readable strings. When representing points in time, an additional localization issue arises: not only the language, but also the date format is locale-dependent (U.S.: Month/Day/Year, Germany: Day.Month.Year, etc.). The DateFormat utility tries to manage these differences for the application programmer.

The abstract base class DateFormat does not require (and does not permit) the definition of arbitrary, programmer-defined date formats. Instead, it defines four different format styles: SHORT, MEDIUM, LONG, and FULL (in increasing order of verbosity). Given a locale and a style, the programmer can rely on the class to use an appropriate date format.

The abstract base class DateFormat does not define static methods for formatting (date -> text) or parsing (text -> date). Instead, it defines several static factory methods to obtain instances (of concrete subclasses) initialized for a given locale and a chosen style. Since the standard formats always include both date and time, additional factory methods are available to obtain instances treating only the time or date part. The String format( Date ) and Date parse( String ) methods then perform the transformation. Note that concrete subclasses may choose to break this idiom.

The Calendar object used internally to interpret dates is accessible and can be modified, as are the employed TimeZone and NumberFormat objects. However, the locale and style can no longer be changed once the DateFormat has been instantiated.

Also available are (abstract) methods for piece-wise parsing or formatting, taking an additional ParsePosition or FieldPosition argument, respectively. There are two versions for each of these methods. One takes or returns a Date instance and the other takes or returns a general Object, to allow handling of alternatives to Date in subclasses. The class defines several public static variables with names ending in _FIELD to identify the various possible fields for use with FieldPosition (cf. the JavaDoc for java.util.Format).

The only commonly available concrete subclass of DateFormat is SimpleDateFormat. It provides all of the aforementioned functionality, additionally allowing the definition of arbitrary date-formatting patterns. There is a rich syntax to specify formatting patterns; the JavaDoc gives the full details. The pattern can be specified as an argument to the constructors of this class or set explicitly.

Printing a Timestamp: A Cut-and-Paste Example

Imagine you want to print the current time in a user-defined format; for instance, to a log file. Here is how to do this:

// Create a formatter with the following pattern: Hour(0-23):Minute:Second
SimpleDateFormat formatter = new SimpleDateFormat( "HH:mm:ss" ); 
Date now                   = new Date();
String logEntry            = formatter.format( now );

// To read the string back in
try {
    Date sometime = formatter.parse( logEntry );
} catch ( ParseException exc ) {
    exc.printStackTrace();
}

Note the ParseException that needs to be caught. It is thrown when the beginning of the input string cannot be parsed.

The Classes in java.sql.*

The date-and-time-handling classes in the java.sql.* all extend java.util.Date. The fact that there are three of them reflects the need to model the three standard SQL92 types DATE, TIME, and TIMESTAMP.

Like java.util.Date, all three classes in the SQL package are thin wrappers around a numeric value representing a point in time. The Date and Time classes ignore the information regarding the time of day or the calendar date, respectively.

The Timestamp class, however, not only includes the usual time and date information up to millisecond precision, but also allows storing additional data to accurately represent a point in time with nanosecond precision. (A nanosecond is a billionth of a second.)

Besides shadowing the corresponding SQL datatypes, these classes handle transformations to and from SQL-conforming String representations. To this end, each of the three classes overrides the toString() method. Furthermore, each class provides a static factory method, valueOf( String ), which returns an instance of the class that it has been invoked on, initialized to the time value represented by the String passed to it. The format of the String representation for all of these methods is fixed by the SQL standard and cannot be changed by the programmer.

The additional data required to store nanosecond information has not been very well integrated with the rest of the data representing the usual time and date information in the Timestamp class. For example, calling getTime() on a Timestamp instance will return the number of milliseconds since the start of the Unix epoch, ignoring the nanosecond data. Similarly, according to the JavaDoc, the hashCode() method has not been overridden in the subclass, and therefore also ignores the nanosecond data.

The JavaDoc for java.sql.Timestamp states that the "inheritance relationship (...) really denotes implementation inheritance, and not type inheritance," but even this statement is incorrect, since Java has no notion of private (i.e. implementation) inheritance. Instead of inheriting from java.util.Date, all of the classes in the java.sql.* package should have been designed to encapsulate a java.util.Date object, exposing only the methods required — at the very least, methods such as hashCode() should have been properly overridden.

A final comment concerns the handling of time zones by the database engine. The classes in the java.sql.* package do not allow one to specify the intended time zone explicitly. Database servers (or drivers) are free to interpret this information as being valid in the server's local time zone, which may be subject to change (for instance, due to daylight savings time).

Summary

From the foregoing discussion it should be clear that Java's date-handling classes are not just complicated, but also poorly designed. Encapsulation is leaky, the APIs are baroque and not well-organized, and uncommon idioms are employed frequently for no good reason. The implementation holds additional surprises (I suggest a look at the actual type of the object returned from Calendar.getInstance( Locale ) for all available locales!) On the other hand, the classes manage to treat all of the difficulties inherent in internationalized date handling and, in any case, are here to stay. I hope that this article was a little contribution in helping to clarify their proper usage.

Call Me By My True Names

As a last example of the wonderful consistency and orthogonality of Java's APIs, I would like to list three (maybe there are more!) different methods to obtain the number of milliseconds since the start of the Unix epoch:

  • long java.util.Date.getTime()
  • long java.util.Calendar.getTimeInMillis() (New with JDK 1.4.1. Note that java.util.Calendar.getTime() returns a Date object!)
  • long java.lang.System.currentTimeMillis()

Acknowledgements

The author would like to thank Wilhelm Fitzpatrick (Seattle) for a careful reading of the manuscript and valuable comments.

References

  • International Calendars in Java at IBM : A detailed white paper by one of the original authors on the genesis and intended usage of Java's date-handling classes. Highly recommended.
  • IBM alphaWorks: International Calendars: Additional subclasses of Calendar for Buddhist, Hebrew, Muslim, and Japanese calendars used to be available at IBM's alphaWorks. Unfortunately, they seem to be temporarily unavailable.
  • Reingold on Calendars: Web site of Edward M. Reingold, author of Calendrical Calculations, the standard reference on calendars.
  • About the Calendars: A brief overview of some of the more common international calendars.
  • Thread on JavaLobby: A brief, but interesting, thread on JavaLobby. Apparently, some people considered the APIs of Java's date classes to be so bad that they filed an official bug-report to have them changed. Unfortunately, the request has been rejected.

Philipp K. Janert is a software project consultant, server programmer, and architect.


Return to ONJava.com