Migrating to the New Java 8 Date Time API

1. Overview

In this tutorial you will learn how to refactor your code in order to leverage the new Date Time API introduced in Java 8.

2. New API at a Glance

Working with dates in Java used to be hard. The old date library provided by JDK included only three classes: java.util.Date, java.util.Calendar and java.util.Timezone.

These were only suitable for the most basic tasks. For anything even remotely complex, the developers had to either use third-party libraries or write tons of custom code.

Java 8 introduced a completely new Date Time API (java.util.time.*) that is loosely based on the popular Java library called JodaTime. This new API dramatically simplified date and time processing and fixed many shortcomings of the old date library.

1.1. API Clarity

A first advantage of the new API is clarity – the API is very clear, concise and easy to understand. It does not have a lot of inconsistencies found in the old library such as the field numbering (in Calendar months are zero-based, but days of week are one-based).

1.2. API Flexibility

Another advantage is flexibility – working with multiple representations of time. The old date library included only a single time representation class – java.util.Date, which despite its name, is actually a timestamp. It only stores the number of milliseconds elapsed since the Unix epoch.

The new API has many different time representations, each suitable for different use cases:

  • Instant – represents a point in time (timestamp)

  • LocalDate – represents a date (year, month, day)

  • LocalDateTime – same as LocalDate, but includes time with nanosecond precision

  • OffsetDateTime – same as LocalDateTime, but with time zone offset

  • LocalTime – time with nanosecond precision and without date information

  • ZonedDateTime – same as OffsetDateTime, but includes a time zone ID

  • OffsetLocalTime – same as LocalTime, but with time zone offset

  • MonthDay – month and day, without year or time

  • YearMonth – month and year, without day or time

  • Duration – amount of time represented in seconds, minutes and hours. Has nanosecond precision

  • Period – amount of time represented in days, months and years

1.3. Immutability and Thread-Safety

Another advantage is that all time representations in Java 8 Date Time API are immutable and thus thread-safe.

All mutating methods return a new copy instead of modifying state of the original object.

Old classes such as java.util.Date were not thread-safe and could introduce very subtle concurrency bugs.

1.4. Method Chaining

All mutating methods can be chained together, allowing to implement complex transformations in a single line of code.

ZonedDateTime nextFriday = LocalDateTime.now()
  .plusHours(1)
  .with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
  .atZone(ZoneId.of("PST"));

2. Examples

The examples below will demonstrate how to perform common tasks with both old and new API.

Getting current time

// Old
Date now = new Date();

// New
ZonedDateTime now = ZonedDateTime.now();

Representing specific time

// Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();

// New
LocalDate birthDay = LocalDate.of(1990, Month.DECEMBER, 15);

Extracting specific fields

// Old
int month = new GregorianCalendar().get(Calendar.MONTH);

// New
Month month = LocalDateTime.now().getMonth();

Adding and subtracting time

// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBefore = calendar.getTime();

// New
LocalDateTime fiveHoursBefore = LocalDateTime.now().minusHours(5);

Altering specific fields

// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, Calendar.JUNE);
Date inJune = calendar.getTime();

// New
LocalDateTime inJune = LocalDateTime.now().withMonth(Month.JUNE.getValue());

Truncating

Truncating resets all time fields smaller than the specified field. In the example below minutes and everything below will be set to zero

// Old
Calendar now = Calendar.getInstance();
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Date truncated = now.getTime();

// New
LocalTime truncated = LocalTime.now().truncatedTo(ChronoUnit.HOURS);

Time zone conversion

// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
Date centralEastern = calendar.getTime();

// New
ZonedDateTime centralEastern = LocalDateTime.now().atZone(ZoneId.of("CET"));

Getting time span between two points in time

// Old
GregorianCalendar calendar = new GregorianCalendar();
Date now = new Date();
calendar.add(Calendar.HOUR, 1);
Date hourLater = calendar.getTime();
long elapsed = hourLater.getTime() - now.getTime();

// New
LocalDateTime now = LocalDateTime.now();
LocalDateTime hourLater = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(now, hourLater);

Time formatting and parsing

DateTimeFormatter is a replacement for the old SimpleDateFormat that is thread-safe and provides additional functionality.

// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);

// New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);

Number of days in a month

// Old
Calendar calendar = new GregorianCalendar(1990, Calendar.FEBRUARY, 20);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);

// New
int daysInMonth = YearMonth.of(1990, 2).lengthOfMonth();

3. Interacting with Legacy Code

In many cases a user might need to ensure interoperability with third-party libraries that rely on the old date library.

In Java 8 old date library classes have been extended with methods that convert them to corresponding objects from new Date API.
New classes provide similar functionalities.

Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
Date dateFromInstant = Date.from(Instant.now());
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
Instant instantFromDate = new Date().toInstant();
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("PST").toZoneId();

4. Conclusion

In this article we explored the new Date Time API available in Java 8. We took a look at its advantages, compared to the deprecated API and pointed out differences using multiple examples.

Note that we barely scratched surface of the capabilities of the new Date Time API. Make sure to read through the official documentation to discover full range of tools offered by the new API.

Code examples can be found in the GitHub project.

Leave a Reply

Your email address will not be published.