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

advertisement

AddThis Social Bookmark Button

Making Sense of Java's Dates

by Philipp K. Janert
06/04/2003

Introduction

Proper handling of calendar dates in computer programs is hard. Not only are there obvious internationalization requirements (English: January, French: Janvier, German: Januar, etc.), but also issues regarding different calendar systems (not every culture counts years starting with the birth of Jesus Christ). If very high precision or very long time scales have to be treated properly, additional concerns need to be addressed, such as the possibility of leap seconds or calendar system changes. (The Gregorian calendar commonly used in the West was adopted only in 1582, and not by all countries on the same day!)

Over all of the issues concerning leap seconds, time zones, daylight savings time (DST), and lunar calendars, it is easy to forget that measuring time is a very simple concept: time progresses linearly. Once an origin of the time axis has been defined, any point in time is uniquely identified by the time elapsed since the origin. Note that this is independent of the geographical location or the local time zone — for a given point in time, the duration since the origin is the same for any location (ignoring relativistic corrections).

Java In a Nutshell

Related Reading

Java In a Nutshell
By David Flanagan

The difficulties arise when we try to interpret this point in time according to some calendar, i.e., representing it in terms of months, days, or years. Geographical information becomes relevant at this step: the same point in time corresponds to different times of day, depending on the location (i.e., time zone). Modifications based on interpreted dates are often required (which date corresponds to the day a month from today?) and pose additional difficulties: over- and underflows (a month from Dec. 15 is next year), as well as ambiguities (which day exactly corresponds to a month from Jan. 30?).

In the original JDK 1.0, the representation for a point in time was lumped together with the responsibility to interpret it into the class java.util.Date. While relatively easy to handle, it was not amenable to internationalization. This was recognized relatively early; since JDK 1.1.4 or JDK 1.1.5, the various responsibilities for handling dates have been distributed among the following classes:

java.util.Date Represents a point in time.
abstract java.util.Calendar
java.util.GregorianCalendar extends java.util.Calendar
Interpretation and manipulation of Dates.
abstract java.util.TimeZone
java.util.SimpleTimeZone extends java.util.TimeZone
Representation of an arbitrary offset from Greenwich Mean Time (GMT), including information about applicable daylight savings rules.
abstract java.text.DateFormat extends java.text.Format
java.text.SimpleDateFormat extends java.text.DateFormat
Transformation into well-formatted, printable String and vice versa.
java.text.DateFormatSymbols Translation of the names of months, weekdays, etc., as an alternative to using the information from Locale.
java.sql.Date extends java.util.Date
java.sql.Time extends java.util.Date
java.sql.Timestamp extends java.util.Date
Represent points in time, and also include proper formatting for use in SQL statements.

Note that DateFormat and related classes are in the java.text.* package. All date-handling classes in the java.sql.* package extend java.util.Date. All other classes are in the java.util.* package.

The "new" classes form three separate inheritance hierarchies, with the top-level classes (Calendar, TimeZone, and DateFormat) being abstract. For each abstract class, the Java Standard Library provides one concrete implementation.

java.util.Date

The class java.util.Date represents a point in time. In many applications, such an abstraction would be called a "TimeStamp." In the standard Java library implementation, this point in time is represented by the number of milliseconds since the start of the Unix epoch on January 1, 1970, 00:00:00 GMT. Conceptually, this class is therefore a very thin wrapper around a long.

In concordance with this interpretation, observe that the only methods in this class that are not deprecated (besides those getting and setting the number of milliseconds) are those required to allow ordering.

This class depends on System.currentTimeMillis() to obtain the current point in time. Its accuracy and precision is therefore determined by the implementation of System and the underlying layer (essentially the OS) that it calls.

The java.util.Date API

The names and conventions used in the API of the original Date class have caused no end of confusion. While the decision to count months from 0-11 and years from 1900 mimicked the C Standard Library's convention, the decision to call the function returning the number of milliseconds since the start of the Unix epoch getTime() and the one returning the day of the month getDate() apparently was the Java class' designer's own.

java.util.Calendar

Semantics

The Calendar class represents a point in time (a "Date"), interpreted appropriately for some locale and time zone. Each Calendar instance wraps a long variable containing the number of milliseconds since the epoch for the represented point in time.

This means that Calendar is neither a (stateless) transformer or interpreter, nor a factory for modified dates. It does not support idioms such as:

Month Interpreter.getMonth( inputDate )

or

Date Factory.addMonth( inputDate )

Instead, a Calendar instance must be initialized to some Date. This Calendar instance can then be modified or queried for interpreted properties.

Bizarrely, instances of this class are always initialized to the current time. It is not possible to obtain a Calendar instance initialized to an arbitrary Date — the API forces the programmer to set the date explicitly by a subsequent method call such as setTime( date ) on an existing instance.

Access to Interpreted Fields and Class Constants

The Calendar class follows an unusual idiom for allowing access to the individual fields of the interpreted date instance. Rather than offering a number of dedicated property getters and setters (such as getMonth()), it offers only one, which takes an identifier for the requested field as argument:

int get( Calendar.MONTH ) etc.

Notice that this function always returns an int!

The identifiers for the fields are defined in the Calendar class as public static final variables. (These identifiers are raw integers, not wrapped into an enumeration abstraction.)

Besides the identifiers (or keys) for the fields, the Calendar class defines a number of additional public static final variables holding the values for the fields. So, to test whether a certain date (represented by the Calendar instance calendar) falls into the first month of the year, one would write code like this:

if( calendar.get( Calendar.MONTH ) == Calendar.JANUARY ) {...}

Note that the months are called JANUARY, FEBRUARY, etc., irrespective of location (as opposed to more neutral names such as MONTH_1, MONTH_2, and so on). There is also a field UNDECIMBER, representing the 13th month of the year, which is required by some (non-Gregorian) calendars.

Unfortunately, keys and values are neither distinguished by name nor by grouping into separate nested interfaces.

Manipulation

The Calendar offers three ways to modify the date represented by the current instance: set(), add(), and roll(). The set() method simply sets the specified field to the desired value. The difference between add() and roll() concerns the way they treat over- and underflows: while add() propagates changes to "smaller" or "larger" fields, roll() does not. For instance, when adding a month to a Calendar instance representing Dec. 15, the year will be incremented when using add(), but left untouched when using roll(). The decision to have two different functions for either case was motivated by their possible uses in GUI situations.

The way Calendar is implemented, it contains redundant data: all of the individual fields can be computed from the number of milliseconds since the epoch given a time zone, and vice versa. The class declares the abstract methods computeFields() and computeTime() for these operations, respectively, as well as the complete() method, which performs a complete round-trip. Because there are two sets of redundant data, the two sets can get out of synch. According to the class' documentation, dependent data is recomputed lazily when changes are made. Subclasses must maintain a set of dirty flags to signal when recomputation is required.

Pages: 1, 2

Next Pagearrow