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"));
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 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();
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.