Working with dates and times without the proper tools can be a
chore. Fortunately, Java has three classes that handle most of the work
for you. The java.util.Date
class
encapsulates a point in time. The java.util.GregorianCalendar
class, which extends
the abstract java.util.Calendar
,
translates between a point in time and calendar fields like month, day,
and year. Finally, the java.text.DateFormat
class knows how to generate
and parse string representations of dates and times in many
languages.[32]
The separation of the Date
and
Calendar
classes is analogous to having
a class representing temperature and a class that translates that
temperature to Celsius units. A Date
represents an absolute point in time as defined by a number of
milliseconds from the reference point: midnight, Jan 1, 1970, GMT. This is
the same frame of reference used by the System.currentTimeMillis()
call. A Calendar
encapsulates a point in time and maps
it to higher-level (and messier) notions like years, months, weeks, and
days, and deals with discontinuities like leap years. Conceivably, we
could define subclasses of Calendar
other than the default GregorianCalendar
, say JulianCalendar
or LunarCalendar
, that map time using other
sociological or cultural conventions.[33]
The default GregorianCalendar
constructor creates a
calendar initialized to the current time, in the current time
zone:
GregorianCalendar
now
=
new
GregorianCalendar
();
However, more generally we can just ask the Calendar
class for an appropriate calendar
instance without worrying about what type of calendar system the world
is using this century:
Calendar
now
=
Calendar
.
getInstance
();
In either case, all the real work is done through the main
set()
and get()
methods of
Calendar
. These methods use static
identifiers to refer to calendar fields and values. For example:
Calendar
birthday
=
Calendar
.
getInstance
();
birthday
.
set
(
Calendar
.
YEAR
,
1972
);
birthday
.
set
(
Calendar
.
MONTH
,
Calendar
.
MAY
);
birthday
.
set
(
Calendar
.
DATE
,
20
);
Here, we set the year, month, and day values on the calendar,
altering the internal Date
of the
Calendar
object. Any remaining fields
that we did not set are left as they were initialized (to the current
date and time when it was constructed). In this case, we did not really
specify a full date and time; we simply overrode individual fields in
the calendar.
The Calendar
class contains
identifiers for all of the standard date and time fields, as well as
values such as days of the week and months of the year. The following
are the most common identifiers:
DATE
and DAY_OF_MONTH
are synonymous. HOUR
is a 12-hour clock that can be combined
with AM_PM
. The values are just what
you would expect, as shown in the following:
SUNDAY
,MONDAY
,TUESDAY
...JANUARY
,FEBRUARY
,MARCH
...AM
,PM
In addition to the set()
method
for changing field values, the Calendar
class has two additional methods for
performing date math, add()
and roll()
. Using add()
, you can move a calendar forward or
backward in any unit of time easily, without having to calculate the
other fields. For example, we can move our calendar forward four
weeks:
Calendar
cal
=
Calendar
.
getInstance
();
System
.
out
.
println
(
cal
.
getTime
()
);
// Thu Nov 04 16:39:06 CST 2004
cal
.
add
(
Calendar
.
WEEK_OF_YEAR
,
4
);
System
.
out
.
println
(
cal
.
getTime
()
);
// Thu Dec 02 16:39:06 CST 2004
The roll()
method, by contrast,
does not alter the other fields of the calendar, but arbitrarily adjusts
individual fields. See the Spinner
example in Chapter 17 for additional
information about adding and subtracting time periods using the add()
method.
Finally, you can always get the internal Date
of the Calendar
object or reinitialize the calendar
to a specific Date
using the
getTime()
and
setTime()
method:
// Get the absolute time the Calendar references
Date
date
=
calendar
.
getTime
();
// Reinitialize this calendar to the current date and time
Date
now
=
new
Date
();
calendar
.
setTime
(
now
);
An instance of the TimeZone
class
represents a time zone and the knowledge of daylight savings time at
that location. You can construct a time zone from a string specifier in
a number of ways. The most general approach is to use an offset from
GMT, but many human-readable formats are included. (For a list, use
TimeZone.getAvailableIDs()
.)
TimeZone
.
getTimeZone
(
"US/Central"
);
// CST
TimeZone
.
getTimeZone
(
"GMT-06"
);
// CST
TimeZone
.
getTimeZone
(
"America/Chicago"
);
// CST
A Calendar
inherits the default
time zone from the platform on which it was created. You can set a
different time zone with the setTimeZone()
method:
GregorianCalendar
smokey
=
new
GregorianCalendar
();
smokey
.
setTimeZone
(
TimeZone
.
getTimeZone
(
"US/Mountain"
)
);
It’s important to think about dates and time zones in the right
way. Remember that a Date
is an absolute
point in time, while a Calendar
translates
that Date
into localized fields that
may depend on where you are. In a sense, it is meaningless to talk about
the date “Nov 1, 2004,” without specifying a time zone because at any
given moment on earth, “now” could be one of two different calendar
days. Even specifying a date and time such as “Nov 1, 2004, 9:01 pm” is
ambiguous, because that particular combination of calendar and time
fields occurs at 24 separate times over the span of a day as the world
turns (see Figure 11-1). Only a complete
date, time, and time zone specifies an absolute point in time, such as
“Nov 1, 2004, 9:01 pm EST.” So it’s important to remember that the
Calendar
class defaults all of these
fields for you even if you haven’t set them.
The following example prints the day of the week for the same
Date
object in two different time
zones:
Date
date
=
new
Date
();
// point in time
TimeZone
CST
=
TimeZone
.
getTimeZone
(
"America/Chicago"
);
Calendar
usa
=
Calendar
.
getInstance
(
CST
);
usa
.
setTime
(
date
);
System
.
out
.
println
(
usa
.
get
(
Calendar
.
DAY_OF_WEEK
)
);
// 1
TimeZone
GMT8
=
TimeZone
.
getTimeZone
(
"GMT+08"
);
// Beijing
Calendar
china
=
Calendar
.
getInstance
(
GMT8
);
china
.
setTime
(
date
);
System
.
out
.
println
(
china
.
get
(
Calendar
.
DAY_OF_WEEK
)
);
// 2
In this example, we could also have simply changed the time zone
on the calendar usa
using the
setTimeZone()
method. Unlike the
field set()
methods, setting the time
zone does not change the underlying Date
value of the calendar, only the
interpretation of the fields.
The meaning of the Date
object and its
relationship to Calendar
become
particularly important when dealing with APIs for things such as
databases that construct dates from incomplete date and time fields. If,
as is entirely possible, you end up sending your Date
object from a client application in one
part of the world to a server in another, you may be surprised that the
calendar fields have changed. In these situations, it’s important to
work with Calendar
s to translate the
date fields and avoid the temptation to “fix” the problem by adding or
subtracting real time from the date.
It should be clear now that Calendar
is not just a fancy Date
, but rather is something in between a
time-keeping device and a time-formatting device. This point is
brought home by the fact that the Calendar
class is also locale-sensitive. In
addition to the notion of a time zone, a Calendar
has a Locale
that governs conventions such as on
which day the week begins and ends. You can specify an alternate
locale with the setLocale()
method.
Most locale-specific details, however, are handled by the DateFormat
class, which we’ll discuss
next.
As its name suggests, the DateFormat
class formats Date
objects and not Calendars
, so the first step in formatting
dates and times from a Calendar
is to
get back to a Date
with the getTime()
method:
Date
birthDate
=
calendar
.
getTime
();
To create string representations of dates and times, create a
DateFormat
object and apply its
format()
method to a
Date
object. Like the NumberFormat
object we looked at in the
previous chapter, DateFormat
itself
is abstract, but it has several static (“factory”) methods that return
useful DateFormat
subclass instances.
To get a default DateFormat
, simply
call getInstance()
:
DateFormat
simple
=
DateFormat
.
getInstance
();
String
now
=
simple
.
format
(
new
Date
()
);
// 4/12/06 6:06 AM
You can generate a date string or a time string, or both, using
the getDateInstance()
,
getTimeInstance()
, and
getDateTimeInstance()
factory methods. The argument to these methods describes what level of
detail you’d like to see. DateFormat
defines four constants representing detail levels: they are SHORT
, MEDIUM
, LONG
, and FULL
. There is also a
DEFAULT
, which is the
same as MEDIUM
. The following code
creates three DateFormat
instances:
one to format a date, one to format a time, and one to format a date and
time together. getDateTimeInstance()
requires two arguments: the first specifies how to format the date, the
second how to format the time:
// 12-Apr-06
DateFormat
df
=
DateFormat
.
getDateInstance
(
DateFormat
.
DEFAULT
);
// 9:18:27 AM
DateFormat
tf
=
DateFormat
.
getTimeInstance
(
DateFormat
.
DEFAULT
);
// Wednesday, April 12, 2006 9:18:27 o'clock AM EDT
DateFormat
dtf
=
DateFormat
.
getDateTimeInstance
(
DateFormat
.
FULL
,
DateFormat
.
FULL
);
We’re showing only how to create the DateFormat
objects here. In order to actually
generate a String
from a date, you’ll
need to call the format()
method of
these objects, passing a Date
as an
argument.
Formatting dates and times for other countries is just as easy.
Overloaded factory methods accept a Locale
argument:
// 12 avr. 06
DateFormat
df
=
DateFormat
.
getDateInstance
(
DateFormat
.
DEFAULT
,
Locale
.
FRANCE
);
// 9:27:49
DateFormat
tf
=
DateFormat
.
getTimeInstance
(
DateFormat
.
DEFAULT
,
Locale
.
GERMANY
);
// mercoledi 12 aprile 2006 9.27.49 GMT-04:00
DateFormat
dtf
=
DateFormat
.
getDateTimeInstance
(
DateFormat
.
FULL
,
DateFormat
.
FULL
,
Locale
.
ITALY
);
To parse a string representing a date, we use the parse()
method of the DateFormat
class. The result is a Date
object. The parsing algorithms are
finicky, so it’s safest to parse dates and times that are in the same
format produced by the DateFormat
.
The parse()
method throws a
ParseException
if it
doesn’t understand the string you give it. All of the following calls to
parse()
succeed except the last; we
don’t supply a time zone, but the format for the time is LONG
. Other exceptions are occasionally thrown
from the parse()
method. To cover all
the bases, catch NullPointerExceptions
and StringIndexOutOfBoundsExceptions
also:
try
{
Date
d
;
DateFormat
df
;
df
=
DateFormat
.
getDateTimeInstance
(
DateFormat
.
FULL
,
DateFormat
.
FULL
);
d
=
df
.
parse
(
"Wednesday, April 12, 2006 2:22:22 o'clock PM EDT"
);
df
=
DateFormat
.
getDateTimeInstance
(
DateFormat
.
MEDIUM
,
DateFormat
.
MEDIUM
);
d
=
df
.
parse
(
"12-Apr-06 2:22:22 PM"
);
df
=
DateFormat
.
getDateTimeInstance
(
DateFormat
.
LONG
,
DateFormat
.
LONG
);
d
=
df
.
parse
(
"April 12, 2006 2:22:22 PM EDT"
);
// throws a ParseException; detail level mismatch
d
=
df
.
parse
(
"12-Apr-06 2:22:22 PM"
);
}
catch
(
Exception
e
)
{
...
}
The printf-style formatting covered in Chapter 10 can render dates and times to strings in
completely arbitrary ways, without having to resort to Calendar
methods to get components.
All date and time format strings use the same conversion
character, t
or T
, followed by a suffix
character that identifies the actual format or date/time component to be
generated. For example, the format string %tc
turns a Date
argument into the string equivalent of
what you get with the standard Date
toString()
method:
System
.
out
.
printf
(
"The date is %tc\n"
,
new
Date
()
);
// The date is Thu Nov 04 22:32:00 CST 2004
As with other conversion characters, the only difference between
t
and T
is that the latter forces all of the output
to uppercase. All time and date formatting is locale-sensitive,
including the names of days and months and the A.M./P.M. identifier. To
format a Date
for another language,
simply pass the Locale
as the first
argument:
System
.
out
.
printf
(
Locale
.
ITALIAN
,
"The date is %tc\n"
,
new
Date
()
);
// The date is gio nov 04 22:32:00 CST 2004
There are two additional composite, date-only formats and three composite time-only formats, as shown in the following table. The format string description in the third column of Table 11-4 refers to date and time component formats discussed in Tables 11-5 and 11-6.
Table 11-5 lists formats for accessing date components.
Table 11-5. Date component formats
Format suffix | Examples | Description |
---|---|---|
Sun, Mon, Tue... | Abbreviated day of week | |
Sunday, Monday... | Full day of week | |
Jan, Feb, Mar, ... | Abbreviated month | |
January, February, ... | Full month | |
Y | 1999, 2004 | Four-digit year |
2004 = 20 | High two digits of year | |
1999 = 99 | Low two digits of year | |
| 001 ... 366 | Day of year |
01 ... 13 | Month of year | |
01 ... 31 | Day of month | |
1 ... 31 | Day of month, no leading zeros |
Table 11-6 lists formats for accessing time components.
Table 11-6. Time component formats
Format suffix | Examples | Description |
---|---|---|
00 ... 23 | 24-hour clock | |
0 ... 23 | 24-hour clock, no leading zeros | |
01 ... 12 | 12-hour clock | |
1 ... 12 | 12-hour clock, no leading zeros | |
00 ... 59 | Minute | |
00 ... 60[a] | Second | |
000 ... 999 | Millisecond | |
am, pm | Morning or afternoon designator | |
CST, EST | Time zone name | |
-0600 | ||
[a] The second value ( |
[32] Prior to Java 1.1, the Date
class handled some of the functions of a calendar as well. Most of
these methods have now been deprecated. Today, the only purpose of the
Date
class is to represent a point
in time.
[33] Java’s GregorianCalendar
class is actually both a Julian and Gregorian calendar with a
programmable cut-over date. For a wealth of information about time and
world time-keeping conventions, see the U.S. Navy Directorate of
Time.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.