Java SE 8 Date and Time
by Ben Evans and Richard Warburton
Why do we need a new date and time library?
A long-standing bugbear of Java developers has been the inadequate support for the date and time use cases of ordinary developers.
Originally published in the January/February 2014 issue of Java Magazine. Subscribe today.
For example, the existing classes (such as
SimpleDateFormatter) aren’t thread-safe, leading to potential concurrency issues for users—not something the average developer would expect to deal with when writing date-handling code.
Some of the date and time classes also exhibit quite poor API design. For example, years in
java.util.Date start at 1900, months start at 1, and days start at 0—not very intuitive.
These issues, and several others, have led to the popularity of third-party date and time libraries, such as Joda-Time.
In order to address these problems and provide better support in the JDK core, a new date and time API, which is free of these problems, has been designed for Java SE 8.
The project has been led jointly by the author of Joda-Time (Stephen Colebourne) and Oracle, under JSR 310, and will appear in the new Java SE 8 package
The new API is driven by three core ideas:
- Immutable-value classes. One of the serious weaknesses of the existing formatters in Java is that they aren’t thread-safe. This puts the burden on developers to use them in a thread-safe manner and to think about concurrency problems in their day-to-day development of date-handling code. The new API avoids this issue by ensuring that all its core classes are immutable and represent well-defined values.
Domain-driven design. The new API models its domain very precisely with classes that represent different use cases for
Timeclosely. This differs from previous Java libraries that were quite poor in that regard. For example,
java.util.Daterepresents an instant on the timeline—a wrapper around the number of milli-seconds since the UNIX epoch—but if you call
toString(), the result suggests that it has a time zone, causing confusion among developers.
This emphasis on domain-driven design offers long-term benefits around clarity and understandability, but you might need to think through your application’s domain model of dates when porting from previous APIs to Java SE 8.
Separation of chronologies. The new API allows people to work with different calendaring systems in order to support the needs of users in some areas of the world, such as Japan or Thailand, that don’t necessarily follow ISO-8601. It does so without imposing additional burden on the majority of developers, who need to work only with the standard chronology.
LocalDate and LocalTime
The first classes you will probably encounter when using the new API are
LocalTime. They are local in the sense that they represent date and time from the context of the observer, such as a calendar on a desk or a clock on your wall. There is also a composite class called
LocalDateTime, which is a pairing of
Time zones, which disambiguate the contexts of different observers, are put to one side here; you should use these local classes when you don’t need that context. A desktop JavaFX application might be one of those times. These classes can even be used for representing time on a distributed system that has consistent time zones.
The existing classes aren’t thread-safe, leading to potential concurrency issues for users—not something the average developer would expect.
All the core classes in the new API are constructed by fluent factory methods. When constructing a value by its constituent fields, the factory is called
of; when converting from another type, the factory is called
from. There are also parse methods that take strings as parameters. See Listing 1.
LocalDateTime timePoint = LocalDateTime.now( ); // The current date and time LocalDate.of(2012, Month.DECEMBER, 12); // from values LocalDate.ofEpochDay(150); // middle of 1970 LocalTime.of(17, 18); // the train I took home today LocalTime.parse("10:15:30"); // From a String
Standard Java getter conventions are used in order to obtain values from Java SE 8 classes, as shown in Listing 2.
LocalDate theDate = timePoint.toLocalDate(); Month month = timePoint.getMonth(); int day = timePoint.getDayOfMonth(); timePoint.getSecond();
You can also alter the object values in order to perform calculations. Because all core classes are immutable in the new API, these methods are called
with and return new objects, rather than using setters (see Listing 3). There are also methods for calculations based on the different fields.
// Set the value, returning a new object LocalDateTime thePast = timePoint.withDayOfMonth( 10).withYear(2010); /* You can use direct manipulation methods, or pass a value and field pair */ LocalDateTime yetAnother = thePast.plusWeeks( 3).plus(3, ChronoUnit.WEEKS);
The new API also has the concept of an adjuster—a block of code that can be used to wrap up common processing logic. You can either write a
WithAdjuster, which is used to set one or more fields, or a
PlusAdjuster, which is used to add or subtract some fields. Value classes can also act as adjusters, in which case they update the values of the fields they represent. Built-in adjusters are defined by the new API, but you can write your own adjusters if you have specific business logic that you wish to reuse. See Listing 4.
import static java.time.temporal.TemporalAdjusters.*; LocalDateTime timePoint = ... foo = timePoint.with(lastDayOfMonth()); bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY)); // Using value classes as adjusters timePoint.with(LocalTime.now());
The new API supports different precision time points by offering types to represent a date, a time, and date with time, but obviously there are notions of precision that are more fine-grained than this.
truncatedTo method exists to support such use cases, and it allows you to truncate a value to a field, as shown in Listing 5.
LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);
The local classes that we looked at previously abstract away the complexity introduced by time zones. A time zone is a set of rules, corresponding to a region in which the standard time is the same. There are about 40 of them. Time zones are defined by their offset from Coordinated Universal Time (UTC). They move roughly in sync, but by a specified difference.
Time zones can be referred to by two identifiers: abbreviated, for example, “PLT,” and longer, for example, “Asia/Karachi.” When designing your application, you should consider what scenarios are appropriate for using time zones and when offsets are appropriate.
ZoneIdis an identifier for a region (see Listing 6). Each
ZoneIdcorresponds to some rules that define the time zone for that location. When designing your software, if you consider throwing around a string such as “PLT” or “Asia/Karachi,” you should use this domain class instead. An example use case would be storing users’ preferences for their time zone.
// You can specify the zone id when creating a zoned date time ZoneId id = ZoneId.of("Europe/Paris"); ZonedDateTime zoned = ZonedDateTime.of(dateTime, id); assertEquals(id, ZoneId.from(zoned));
ZoneOffsetis the period of time representing a difference between Greenwich/UTC and a time zone. This can be resolved for a specific
ZoneIdat a specific moment in time, as shown in Listing 7.
ZoneOffset offset = ZoneOffset.of("+2:00");
Time Zone Classes
ZonedDateTimeis a date and time with a fully qualified time zone (see Listing 8). This can resolve an offset at any point in time. The rule of thumb is that if you want to represent a date and time without relying on the context of a specific server, you should use
OffsetDateTimeis a date and time with a resolved offset. This is useful for serializing data into a database and also should be used as the serialization format for logging time stamps if you have servers in different time zones.
OffsetTimeis a time with a resolved offset, as shown in Listing 9.
OffsetTime time = OffsetTime.now(); // changes offset, while keeping the same point on the timeline OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant( offset); // changes the offset, and updates the point on the timeline OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal( offset); // Can also create new object with altered fields as before changeTimeWithNewOffset .withHour(3) .plusSeconds(2);
There is an existing time zone class in Java—
java.util.TimeZone—but it isn’t used by Java SE 8 be-cause all JSR 310 classes are immutable and time zone is mutable.
Period represents a value such as “3 months and 1 day,” which is a distance on the timeline. This is in contrast to the other classes we’ve looked at so far, which have been points on the timeline. See Listing 10.
// 3 years, 2 months, 1 day Period period = Period.of(3, 2, 1); // You can modify the values of dates using periods LocalDate newDate = oldDate.plus(period); ZonedDateTime newDateTime = oldDateTime.minus(period); // Components of a Period are represented by ChronoUnit values assertEquals(1, period.get(ChronoUnit.DAYS));
Duration is a distance on the timeline measured in terms of time, and it fulfills a similar purpose to
Period, but with different precision, as shown in Listing 11.
// A duration of 3 seconds and 5 nanoseconds Duration duration = Duration.ofSeconds(3, 5); Duration oneDay = Duration.between(today, yesterday);
Java SE 8 will ship with a new date and time API in java.time that offers greatly improved safety and functionality for developers. The new API models the domain well, with a good selection of classes for modeling a wide variety of developer use cases.
It’s possible to perform normal plus, minus, and “with” operations on a
Duration instance and also to modify the value of a date or time using the
In order to support the needs of developers using non-ISO calendaring systems, Java SE 8 introduces the concept of a
Chronology, which represents a calendaring system and acts as a factory for time points within the calendaring system. There are also interfaces that correspond to core time point classes, but are parameterized by
Chronology: ChronoLocalDate ChronoLocalDateTime ChronoZonedDateTime
These classes are there purely for developers who are working on highly internationalized applications that need to take into account local calendaring systems, and they shouldn’t be used by developers without these requirements. Some calendaring systems don’t even have a concept of a month or a week and calculations would need to be performed via the very generic field API.
The Rest of the API
Java SE 8 also has classes for some other common use cases. There is the
MonthDay class, which contains a pair of
Day and is useful for representing birthdays. The
YearMonth class covers the credit card start date and expiration date use cases and scenarios in which people have a date with no specified day.
JDBC in Java SE 8 will support these new types, but there will be no public JDBC API changes. The existing generic
getObject methods will be sufficient.
These types can be mapped to vendor-specific database types or ANSI SQL types; for example, the ANSI mapping looks like Table 1.
|ANSI SQL||Java SE 8|
|TIME WITH TIMEZONE||OffsetTime|
|TIMESTAMP WITH TIMEZONE||OffsetDateTime|
Java SE 8 will ship with a new date and time API in
java.time that offers greatly improved safety and functionality for developers. The new API models the domain well, with a good selection of classes for modeling a wide variety of developer use cases.
Ben Evans (@kittylyst) is CEO of jClarity, an organizer for the London Java Community (LJC), and a member of the Java SE/EE Executive Committee.
Richard Warburton is an empirical technologist and solver of deep-dive technical problems. Most recently, he has been working on data analytics for high-performance computing at jClarity.