Skip to main content

JSR 310: A New Java Date/Time API

September 18, 2008

{cs.r.title}







JSR 310 is an
API for time- and calendar-related calculations that has been
proposed for Java SE 7. The API aims to replace the two existing
classes that form Java's current date and time API,
java.util.Date and java.util.Calendar,
while still providing backwards-compatible access to these older
APIs. The JSR is currently in development, and a "https://jsr-310.dev.java.net/nonav/doc-2008-04-20/index.html">tentative
Javadoc
is available for the API.

Improvements on the Java 6 Date/Time API

The JSR 310 date/time API attempts to improve upon Java's
current date/time API by providing increased performance and
ease of use. For example, the Java Calendar class
stores a date simultaneously as a millisecond offset from a
standard epoch, and as a set of calendar fields (e.g., day of week,
day of month, and month of year). This double representation causes
the calendar fields to be recomputed at unexpected times, resulting
in unpredictable performance characteristics. In contrast, JSR 310
classes store date/time representations only as a nanosecond offset
from the same standard epoch used by Date and
Calendar; calendar fields such as day of month are
only computed when needed and are not used for internal
representation of dates.

JSR 310 also improves on the current Java date/time model. The Java 6 API contains no classes that represent local
times (times without an associated time zone), durations, or
intervals. This forces programmers to use confusing design
practices, such as using an int to represent a
duration of time. JSR 310 contains classes that represent each of
these concepts, allowing for cleaner program design.

Finally, the JSR 310 API strives for thread safety through the
use of immutable classes. Java's current date/time classes,
Date and Calendar, are both mutable and
therefore not thread-safe.

JSR 310 Date/Time Concepts

The JSR 310 API draws on experience gained from several third-party
Java date/time APIs. JSR 310 is based mainly on the "http://joda-time.sourceforge.net/">Joda Time API; other
influences include ICU,
Time and
Money
, and "http://calendardate.sourceforge.com">Calendar Date. JSR 310's
API is built around the same five basic date/time concepts used in
Joda Time:

The Discrete Timeline

Like Joda Time, JSR 310 uses a discretized timeline:
time is modeled as a sequence of consecutive instants separated by
small, fixed durations. The discrete timeline of JSR 310 has
nanosecond resolution, so it is possible to express the time "One
nanosecond after midnight on 01/01/08," but not "One picosecond after
midnight on 01/01/08." Each discrete nanosecond on the timeline is
considered an instant, described below.

Instants

An instant is a specific point on a discretized timeline. An
example of an instant is "January 7, 2008, at time 23:00:00.0 UTC."
An instant can also be defined as a nanosecond offset from a
standard epoch, such as "20,000,000 nanoseconds after January 1,
1970 at midnight UTC." Both of these descriptions define a single,
unique point on the discrete timeline. Instants are different from
partials, which define a set of moments on
the timeline, rather than one unique moment.

The JSR 310 API provides several classes that represent
instants: Instant, OffsetDateTime, and
ZonedDateTime, all of which implement the
ReadableInstant interface. The
OffsetDateTime class represents a date, time of day,
and offset from coordinated universal time (UTC), such as +1:00.
The similar ZonedDateTime class incorporates a time
zone ID, such as America/New_York, instead of an offset. A given
time zone may use several different offsets depending on the time
of year; for example, the America/New_York time zone's offset is
-4:00 during daylight savings time and -5:00 at other times. Thus,
the ZonedDateTime class should be used when
locale-specific time rules, such as daylight savings time, must be
taken into account.

The ZonedDateTime class provides several categories
of methods for creating, accessing, and modifying instants. To
create a new instance of ZonedDateTime representing
the current system time in your computer's default time zone, you
can use the Clock.currentZonedDateTime() factory
method, as shown by the following example.


Clock systemClock = Clock.system();
ZonedDateTime currentTime = systemClock.currentZonedDateTime();

To create a ZonedDateTime instance that represents
a specific, predetermined date, you can use one of several
ZonedDateTime.dateTime factory methods. The following
example demonstrates the creation of a ZonedDateTime
representing midnight on January 1, 2000, in your computer's
default time zone.


Clock systemClock = Clock.system();
TimeZone defaultTimeZone = systemClock.timeZone();
int year = 2000;
int month = 1;
int day = 1;
int hour = 0;
int minute = 0;
ZonedDateTime theDate = ZonedDateTime.dateTime(year,
  month, day, hour, minute, defaultTimeZone);

Instances of OffsetDateTime can be created in a
manner similar to that of ZonedDateTime, except that a
ZoneOffset rather than a TimeZone is
passed into the OffsetDateTime.dateTime factory
method. To get an instance of ZoneOffset, you can use
the static ZoneOffset.zoneOffset(int hours) method,
where hours is the hour offset from UTC.

Partials

A partial is an indication of date or time that is not
sufficient to specify a specific, unique point on the timeline. For
example, "June 7" is a partial, because it specifies a month and
day, but it does not specify a year or time of day. Thus, the above
partial is not sufficient to identify a unique point on the
timeline. Because partials do not identify specific times, they
cannot be expressed as nanosecond offsets from a standard epoch.
Their definition is necessarily field-based, using calendar fields
such as year, month, day of month, and time of day.

Partials can be categorized based on the set of calendar fields
that they define. For example, a partial that represents a yearly
holiday might contain month and day fields, whereas a partial that
represents a store's opening time might contain hour and minute
fields. JSR 310 provides classes for commonly used partials, such
as MonthDay (the aforementioned "holiday" partial),
LocalDate (a date with no time or time zone), and
LocalTime (a time with no time zone). To create an
instance of MonthDay or one of the other ready-made
partial classes, you can use one of the provided static factory
methods. The example below is specific to MonthDay,
but it can easily be adapted for one of the other partial
classes.


//Create a MonthDay representing December 25 (Christmas)
int theMonth = 12;
int theDay = 25;
MonthDay christmas = MonthDay.monthDay(theMonth, theDay);

Durations

A duration represents a length of elapsed time, defined to the
nanosecond level; for example, "100,000 nanoseconds" is a duration.
Durations are somewhat similar to periods,
which also define a length of elapsed time; however, unlike
periods, durations represent a precise number of elapsed
nanoseconds.

A duration can be added to an instant to return a new instant
that is offset from the original instant by the number of
nanoseconds in the duration. For example, if the duration
"86,400,000,000,000 nanoseconds" (24 hours) is added to the instant
"March 1, 2008, at midnight UTC," the resulting instant is "March
2, 2008, at midnight UTC."

Durations may be created in two ways: by supplying a second,
millisecond, or nanosecond timespan for the duration, or by
providing starting and ending instants. The first method is
appropriate for creating a duration of a predetermined length:


System.out.println("Enter a duration length in hours:");
Scanner s = new Scanner(System.in);
int durationSeconds = 3600 * s.nextInt();
Duration d = Duration.duration(durationSeconds);

Alternatively, you may wish to determine the duration between
two predetermined instants. The static

Duration.durationBetween(ReadableInstant startInclusive,
ReadableInstant endExclusive)
factory method is useful in
this case.


Clock systemClock = Clock.system();
ZonedDateTime instant1 = systemClock.currentZonedDateTime();
try{Thread.sleep(1000)} //Use up some time
catch(InterruptedException e){System.out.println("Error")}
ZonedDateTime instant2 = systemClock.currentZonedDateTime();
Duration d = Duration.durationBetween(instant1, instant2);

Periods

Like durations, periods represent a length of elapsed time.
Examples of periods are "4 years, 8 days," and "1 hour." As shown
by these examples, periods are defined using calendar fields
(years, days, hours, etc.), rather than by an exact number of
nanoseconds.

At first, periods and durations may seem to be different ways of
expressing the same concept; however, a given period cannot be
converted to an exact number of nanoseconds, as the following
example of instant/period addition demonstrates. Instant/period
addition adds the value of each of the period's calendar fields to
the corresponding field of the instant. This is in contrast to
instant/duration addition, which adds the duration's length in
nanoseconds to the instant. At first glance, it may seem that
adding an 86,400,000,000,000-nanosecond (24-hour) duration to a
given instant should produce the same result as adding a period of
"1 day," but this is not always the case, because the calendar
field "day" does not have a fixed nanosecond length. Most days are
24 hours long, but some are longer or shorter due to daylight
savings time. Adding a 24-hour duration to a instant will always
advance the instant by exactly 24 hours, whereas adding a one-day
period will advance the day by one, while leaving the time of day
unchanged.

For example, if the period "1 day" is added to the instant
"March 9, 2008, at midnight EST," the resulting field-based
addition produces "March 10, 2008, at midnight EST." However, if a
24-hour duration is added to the instant "March 9, 2008, at
midnight EST," the result is "March 10, 2008, at 01:00:00.0 EST."
The discrepancy stems from the fact that daylight savings time
begins at 02:00:00 EST on March 9, 2008; thus, this day is only 23
hours long.

JSR 310 provides period functionality with several classes, the
most important of which are Period,
Periods.Builder, and the subclasses of
Periods.Builder: Periods.SecondsBuilder,
Periods.MinutesBuilder (which is a subclass of
SecondsBuilder), and so on, up to
Periods.YearsBuilder.

To create an instance of Period, you must first
obtain an instance of Periods.Builder using the static
Periods.periodBuilder() method. This method returns a
Periods.YearsBuilder object, which is an indirect
subclass of Periods.Builder. The
Periods.YearsBuilder class provides a method,
years(int numYears), that adds numYears
years to the period being built. Methods provided by the direct and
indirect superclasses of YearsBuilder
(MonthsBuilder, DaysBuilder,
HoursBuilder, MinutesBuilder, and
SecondsBuilder) allow other calendar fields to be
added to the period. For example, the
MinutesBuilder.minutes(int numMinutes) adds
numMinutes minutes to the period being built. All of
these methods return this, allowing periods with
multiple calendar fields to be built with a single statement. Note
that it is possible to subtract from a period by passing a negative
integer into the appropriate builder method. Once all desired
fields have been added to the period, the
Periods.PeriodBuilder.build() method is called to
return the completed instance of Period.


//Build a period representing "8 years, 3 months."
Period thePeriod = 
  Periods.periodBuilder().years(8).months(3).build();              

Intervals

An interval represents a period of time between two instants on
the timeline. Thus, "midnight UTC on January 1, 2007 (inclusive) to
midnight UTC on January 1, 2008 (exclusive)" is an interval. The
interval can be inclusive or exclusive with regard to the starting
and ending instants.

JSR 310 provides the InstantInterval class to
represent intervals. To create an instance of
InstantInterval, you can use one of several factory
methods:


//Create some intervals.
ZonedDateTime time1 = systemClock.currentZonedDateTime();
try{Thread.sleep(1000)} //Use up some time
catch(InterruptedException e){System.out.println("Error")}
ZonedDateTime time2 = systemClock.currentZonedDateTime();

//Create an interval with inclusive start and exclusive end.
InstantInterval interval1 = InstantInterval.intervalBetween(
  time1.toInstant(), time2.toInstant());

//Create an interval with exclusive start and inclusive end.
boolean startInclusive = false; boolean endInclusive = true;
InstantInterval interval2 = InstantInterval.intervalBetween(
  time1.toInstant(), startInclusive, 
  time2.toInstant(), endInclusive);

Using JSR 310 Date/Time Classes

The previous sections describe the five basic JSR 310 date/time
concepts and explain how to instantiate the classes representing
those concepts. This section illustrates some calendrical
computations that are possible with the JSR 310 API.

For illustrative purposes, suppose that you are developing a
program that takes as input several events that occur at different
times and recur at particular intervals. The program outputs all
times at which two or more events occur simultaneously. You can use
a ZonedDateTime and a Duration to
represent an instantaneous event that recurs after a fixed length
of time has passed; to generate the next occurrence of the event,
you add the Duration to the ZonedDateTime
representing the previous occurrence:


//ZonedDateTime firstTime is the time the event first occurs
//Duration repeatTime is the duration after which event recurs
//TimeZone defaultZone is your computer's default time zone
ZonedDateTime secondTime = ZonedDateTime.dateTime(
  firstTime.toInstant().plus(repeatTime), defaultZone);

If you have an event that repeats after a certain number of
days, months, or years, you can generate the next occurrence by
adding a Period to the ZonedDateTime
representing the previous occurrence:


//ZonedDateTime firstTime is the time the event first occurs
//Period repeatPeriod is the period after which event recurs
ZonedDateTime secondTime = firstTime.plus(repeatPeriod);

To test whether or not two instantaneous events occur
simultaneously, you can use the

ZonedDateTime.equals(Object
other)
method:


//time1 and time2 are ZonedDateTimes representing events
if (time1.equals(time2)){
  System.out.println("The two events occur simultaneously.");
}

If you need to represent a non-instantaneous event, using a
ZonedDateTime is not the best choice. Partials are
more appropriate for representing non-instantaneous events such as
yearly holidays. To determine whether an event represented by a
ZonedDateTime and one represented by a partial (e.g.,
MonthDay) occur simultaneously, you can use the
MonthDay.matchesDate(LocalDate date) method. This
method returns true if the month and day fields in the
LocalDate, which is a date without a time, match the
month and day fields in the MonthDay.


//Monthday christmas is a partial representing December 25.
//ZonedDateTime time1 represents an instantaneous event
if (christmas.matchesDate(time1.localDate())){
  System.out.println("The event occurs on Christmas.");
}

Finally, events that occur over a specific timespan may best be
represented by an InstantInterval. To test whether or
not an InstantInterval overlaps a
ZonedDateTime, you can use the
InstantInterval.contains(InstantProvider instant)
method. (InstantProvider is an interface implemented
by the three JSR 310 instant classes.)


//InstantInterval event1 is an event that occurs over a timespan
//ZonedDateTime event2 is an instantaneous event
if (event1.contains(event2)){
 System.out.println("Event 2 occurs during event 1.");
}

Conclusion

This article has illustrated some features of JSR 310, a new
Java date/time API that is proposed for Java 7. In particular, it
has illustrated the use of classes representing the five JSR 310
date/time concepts: instants, partials, durations, periods, and
intervals. There is much additional functionality in JSR 310, such
as advanced date/time matching, that this article does not
describe. Furthermore, because this article was based mainly on a
snapshot of the current JSR 310 API, additional functionality may
be added after the publication of this article. See the "#resources">Resources section to learn more about the features
of JSR 310.

Resources


width="1" height="1" border="0" alt=" " />
Jesse Farnham is currently a first-year graduate student in computer science at Princeton University, where he is planning to research the simulation of biological systems.
Related Topics >> JSR   |   Programming   |