Handling time and date in Java has always been somewhat complicated. At least until the new Date and Time API was published along with Java 8 release. Before that, developers had to struggle with not so great java.util.Date and java.util.Calendar classes which both had their flaws. In this post, a third in a series introducing new Java 8 language features (see Java 8 Stream API and Lambda expressions tutorial posts), I’m going to present the new Date and Time API in Java 8 and explain why it’s a much better choice than previous date and time APIs.
Time in Java
Historically, there have been two different APIs for working with time and date in Java. java.util.Date was part of the Java version 1.0 and it was the only time and date API in Java back then. Due to many flaws and issues with the first API, a new java.util.Calendar API has been introduced to provide a better alternative to the existing one. Unfortunately, that didn’t bring much of an improvement as the new API had new issues while fixing only a subset of the old API’s problems.
Later, a new project called Joda-Time has started. The purpose of the project was to provide a quality date and time library for Java. The project succeeded and became very popular among the developers.
It was only a matter of time before it would be decided to include such a date and time API in Java Development Kit (JDK) itself. Therefore, JSR-310 process has been started in order to create and integrate a new Date and Time API for the JDK.
But before we head over to introducing the new API in Java 8, let’s have a look at the old date and time API in Java first.
Old Date API
There are several issues with the oldest Date and Time API in Java. Namely, the most important one is that the Date objects are mutable. This means that you can still change its value after it has been created. This is a bad approach to use for instance in multi-threaded applications.
Other issues include for example the fact that you are unable to capture only a date value without time or only time without a date. To represent a date only, you could put a default midnight time. However, that might introduce new problems later.
The epoch for the old date API is set to January 1, 1900. This is quite unpractical as it is far in the past and hence results in larger calculation. This also means that the smallest possible value to work with are milliseconds.
Lastly, Java’s Date class doesn’t support working with time zones.
Old Calendar API
After the Date API, a little smarter Calendar class with the time zone support has been introduced into the Java programming language. The epoch for this Date and Time API has been set to a more reasonable January 1, 1970.
The Calendar objects are however still mutable and therefore error-prone in multi-threaded environments. Date and time formatting is provided by the SimpleDateFormat class. However, this class cannot work with Calendar instances directly. Instead, you have to convert the Calendar object to Date object which can be then used for human-readable formatting.
Prior to Java 8 launch, when the new Date and Time API was introduced, the Joda-Time library was considered de facto the standard library for dealing with date and time in Java. Joda-Time API addresses most of the shortcomings of the Java’s early Date and Time APIs.
It supports eight different calendars (Gregorian, Julian, Buddhist, etc.), provides various classes for working with time and date, the class instances are immutable, therefore can be safely used in multi-threaded environments, and overall the library provides an extensible, simplified and easy to use API for working with time and date in Java.
Nothing is perfect though, not even the Joda-Time library, which had a few design flaws that would be hard to fix without rewriting a large portion of the code. Therefore, it has been decided to develop a completely new API from scratch and include it into the JDK. Therefore, the new Date and Time API in Java was created in process called JSR-310.
Local Date and Time
In the new Date and Time API in Java, an Instant object represents a point on the time line. An origin (or epoch) is set at midnight of January 1, 1970. From this point, time is measured in 86,400 seconds per day with nanosecond precision.
The current date and time can be obtained by calling a static method Instant.now(). To find out a difference between two instants, you can use Duration.between(start, end) static method.
Example usage of Instant and Duration is here:
Instant now = Instant.now(); // output: 2016-09-24T14:23:28.744Z Instant later = now.plus(Duration.ofSeconds(5)); // output: 2016-09-24T14:23:33.744Z Duration diff = Duration.between(later, now); // output: PT-5S later.isAfter(now); // output: true
Let’s focus now on so-called human time. In Java, there are two types of human time, a local time and zoned time. A local date is for example January 3, 1991. A zoned date (and time) is on the other hand July 22, 1992, 09:32:00 EDT.
You should use local time when you want to represent a certain date (and time), not an absolute time instance. Examples include birthdays, schedule times, etc. To construct a LocalDate, use one of the following ways:
LocalDate today = LocalDate.now(); // output: 2016-09-24 LocalDate birthday = LocalDate.of(1991, Month.JANUARY, 1); // output: 1991-01-01
To calculate a difference between two local dates, use a Period class, which expresses a number of elapsed years, months and days. For example to find the number of days until the end of the current year, you could use an expression similar to this one:
LocalDate.now().until(LocalDate.of(2016, Month.DECEMBER, 31), ChronoUnit.DAYS); // output: 98
Adding five days to a local date can be done this way:
LocalDate.now().plus(Period.ofDays(5)); // or LocalDate.now().plusDays(5);
For scheduling applications, it is often required to compute dates such as ‘the first Monday of every month‘. Such common adjustments are provided by the TemporalAdjusters class. The result of an adjustment method is passed to the with method like this:
LocalDate firstMonday = LocalDate.of(2016, 3, 1).with( TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
If the provided default adjusters simply don’t fit your needs, you can also create your own by implementing the TemporalAdjuster interface.
A LocalTime class represents a time of day, for example 15:00:00. Usage is similar to LocalDate:
LocalTime.now(); LocalTime.of(22, 10, 0); LocalTime morning = LocalTime.of(22, 0).plus(8, ChronoUnit.HOURS);
There is also a LocalDateTime class which joins the two values into one object.
If you need to work with time zones in your application, there is a ZonedDateTime class just for that purpose. Given a time zone ID, the static method ZoneId.of(id) yields a ZoneId object. You can than use that object to turn a LocalDateTime object into a ZonedDateTime object by calling local.atZone(zoneId), or simply construct it by calling the static method ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneId). For example:
ZonedDateTime meeting = ZonedDateTime.of(2016, 10, 12, 9, 30, 0, 0, ZoneId.of("America/New_York"));
Most of the methods of ZonedDateTime are the same as those of LocalDateTime. The methods are additionally adjusted to handling daylight savings time.
You need to pay attention when adjusting a date across daylight savings time boundaries. Instead of adding a duration of seven days, use the Period class which can handle DST changes:
ZonedDateTime nextEvent = event.plus(Duration.ofDays(7)); // NOT OK with DST ZonedDateTime nextEvent = event.plus(Period.ofDays(7)); // OK
Formatting and Parsing
Three kinds of date and time formatters are provided by the DateTimeFormatter class:
- predefined standard formatters
- locale-specific formatters
- custom pattern formatters
The standard formatters are usually used for machine-readable date and time representations. To present dates and times to humans, use a locale-specific formatter instead. There are four styles, SHORT, MEDIUM, LONG, and FULL. Here are some examples of date and time formatters usage:
DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()); // output: 2016-09-24T23:32:53.237 DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(LocalDateTime.now()); // output: 9/24/16 11:32 PM DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).format(ZonedDateTime.now()); // output: September 24, 2016 11:32:53 PM CEST DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.FRENCH) .format(ZonedDateTime.now()); // output: 24 septembre 2016 23:34:28 CEST
If none of the above mentioned formatters meets your needs, you can use your own format by specifying a pattern. For example:
formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm"); // output: Sat 2016-09-24 23:35
Finally, to parse a string into a date and time value, use one of the static parse methods. Example usages are:
LocalDate birthday = LocalDate.parse("1992-06-14"); // uses default ISO_LOCAL_DATE formatter ZonedDateTime date = ZonedDateTime.parse("2016-07-26 15:32:00-0200", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"));
Working with the old API
With the old date and time APIs being used for many years in Java, the new API has to interoperate with the existing code.
The Instant class is close to java.util.Date. In Java 8, two conversion methods exist: toInstant method which converts a Date to an Instant, and the opposite static method from. Similarly, ZonedDateTime is a close analog to Calendar class. As for the previous case, there are toZonedDateTime and from methods for the type conversion.
Several another conversion methods also exist for the java.sql package.
Date and Time API in Android
Unfortunately, the integration of Java 8 features into Android doesn’t include the new Date and Time API.
If you want to use the API in Android, there is a library called ThreeTen Android Backport which you can include in your project. It is specially customized for Android and adds only a small amount of methods to your application while providing the whole set of the new Date and Time API classes.
In this post, I’ve introduced yet another Java language feature: New Date and Time API in Java 8. The difference between LocalDateTime and ZonedDateTime has been explained, date and time adjusters and formatting as well as parsing from strings have been introduced. Finally, the interoperability between the new and old date and time API in Java has been explained together with the possible ways of using the API on Android.