Making Sense of Java's Dates
by Philipp K. Janert06/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).
|
Related Reading Java In a Nutshell |
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.Calendarjava.util.GregorianCalendar extends java.util.Calendar |
Interpretation and manipulation of Dates. |
abstract java.util.TimeZonejava.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.Datejava.sql.Time extends java.util.Datejava.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.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 |
