Java 8 Date and Time API

Introduction

Here I am going to discuss about the Java 8 or later version’s new date and time API. The date time package, java.time, was introduced in the Java SE 8 release, which provides a comprehensive model for date and time. The new date and time API offers greatly improved safety and functionalities for developers.

Why do you need for Java 8 date time API?

Non thread-safe

The existing classes (such as java.util.Date and 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. The new API avoids this issue by ensuring that all its core classes are immutable and represent well-defined values.

Poor Design

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. For example, java.util.Date represents 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. The new API models its domain very precisely with classes that represent different use cases for Date and Time closely.

Use of third party date and time library

These issues, and several others, have led to the popularity of third-party date and time libraries, such as Joda-Time. 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.

Design principle of Java 8 date time API

Clear

The methods in the API are well defined and their behavior is clear and expected. For example, invoking a Date-Time method with a null parameter value typically triggers a NullPointerException.

Fluent

The Date-Time API provides a fluent interface, making the code easy to read. Because most methods do not allow parameters with a null value and do not return a null value, method calls can be chained together and the resulting code can be quickly understood. For example:

LocalDate today = LocalDate.now();
LocalDate payday = today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2);

Immutable

Most of the classes in the Date-Time API create objects that are immutable, meaning that, after the object is created, it cannot be modified. To alter the value of an immutable object, a new object must be constructed as a modified copy of the original. This also means that the Date-Time API is, by definition, thread-safe. This affects the API in that most of the methods used to create date or time objects are prefixed with of, from, or with, rather than constructors, and there are no set methods. For example:

LocalDate dateOfBirth = LocalDate.of(2012, Month.MAY, 14);
LocalDate firstBirthday = dateOfBirth.plusYears(1);

Extensible

The Date-Time API is extensible wherever possible. For example, you can define your own time adjusters and queries, or build your own calendar system.

Some features of date time API

There are some features of Java 8 or later version’s date and time API as given below:

LocalDate and LocalTime

The classes which will strike in your mind probably when using the new API are LocalDate and LocalTime. These classes are local in the sense that they represent date and time from the observer’s point of view, such as a calendar in the home or a clock on the wall or a watch on wrist. There is also a composite class called LocalDateTime, which is a pairing of LocalDate and LocalTime.

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. These classes can even be used for representing time on a distributed system that has consistent time zones.

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.

public class LocalDateTimeApi {

	public static void main(String[] args) {

		LocalDateTime localDateTime = LocalDateTime.now(); // The current date and time
		System.out.println("Current Local Date and Time : " + localDateTime);

		LocalDate localDateFromYearMonthDay = LocalDate.of(2012, Month.DECEMBER, 12); // from values
		System.out.println("localDateFromYearMonthDay : " + localDateFromYearMonthDay);

		LocalDate localDateFromEpocDayCount = LocalDate.ofEpochDay(182); // middle of 1970
		System.out.println("localDateFromEpocDayCount : " + localDateFromEpocDayCount);

		LocalTime localTimeFromHourMinute = LocalTime.of(19, 30); // from Hour and Minute
		System.out.println("localTimeFromHourMinute : " + localTimeFromHourMinute);

		LocalTime localTimeFromString = LocalTime.parse("10:15:30"); // From a String
		System.out.println("localTimeFromString : " + localTimeFromString);

	}

}

Output

The output from the above class will be as follows:

Current Local Date and Time : 2021-08-20T08:11:45.775876
localDateFromYearMonthDay : 2012-12-12
localDateFromEpocDayCount : 1970-07-02
localTimeFromHourMinute : 19:30
localTimeFromString : 10:15:30

Standard Java getter conventions are used in order to obtain values from Java SE 8 classes, as shown below.

LocalDate theDate = localDateTime.toLocalDate();
System.out.println("Local date part of the time : " + theDate);

Month month = localDateTime.getMonth();
System.out.println("Moth of the year field : " + month);

int day = localDateTime.getDayOfMonth();
System.out.println("Day of month field : " + day);

int second = localDateTime.getSecond();
System.out.println("Second of minute field : " + second);

Output

The output from the above code snippets is:

Local date part of the time : 2021-08-20
Moth of the year field : AUGUST
Day of month field : 20
Second of minute field : 5

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. There are also methods for calculations based on the different fields.

LocalDateTime dayOfMonthAltered = localDateTime.withDayOfMonth(10).withYear(2010);
System.out.println("Local Date and Time with day-of-month altered : " + dayOfMonthAltered);

LocalDateTime noOfWeeksAddeedToLocalDateTime = dayOfMonthAltered.plusWeeks(3).plus(3, ChronoUnit.WEEKS);
System.out.println("Local date and time after adding no of weeks : " + noOfWeeksAddeedToLocalDateTime);

Output

Local Date and Time with day-of-month altered : 2010-08-10T08:15:56.836603
Local date and time after adding no of weeks : 2010-09-21T08:15:56.836603

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.

LocalDateTime adjustedLocalDateTime1 = localDateTime.with(lastDayOfMonth());
System.out.println("Adjusted copy of local date and time :" + adjustedLocalDateTime1);

LocalDateTime adjustedLocalDateTime2 = localDateTime.with(previousOrSame(DayOfWeek.WEDNESDAY));
System.out.println(
		"Adjusted copy of previous or same day of week for local date and time :" + adjustedLocalDateTime2);

// Using value classes as adjusters
LocalDateTime adjustedLocalDateTime3 = localDateTime.with(LocalTime.now());
System.out.println("Adjusted copy of local date and time :" + adjustedLocalDateTime3);

Output

The above code snippets produce the following output:

Adjusted copy of local date and time :2021-08-31T08:18:11.907853900
Adjusted copy of previous or same day of week for local date and time :2021-08-18T08:18:11.907853900
Adjusted copy of local date and time :2021-08-20T08:18:11.926852400

Truncation

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.

The truncatedTo() method exists to support such use cases, and it allows you to truncate a value to a field, as shown below.

public class TimeTruncation {

	public static void main(String[] args) {

		LocalTime localTime = LocalTime.now();

		LocalTime truncatedTime = localTime.truncatedTo(ChronoUnit.SECONDS);
		System.out.println("Local time with truncated : " + truncatedTime);

	}

}

Output

The above code produces the following output:

Local time with truncated : 08:20:54

Time Zones

A time zone is a set of rules, corresponding to a region in which the standard time is the same. 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, “America/New_York”. When designing your application, you should consider what scenarios are appropriate for using time zones and when offsets are appropriate.

ZoneId is an identifier for a region (see Listing 6). Each ZoneId corresponds 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.

public class TimeZones {

	public static void main(String[] args) {

		LocalDateTime localDateTime = LocalDateTime.now();
		ZoneId id = ZoneId.of("America/New_York");

		ZonedDateTime zoned = ZonedDateTime.of(localDateTime, id);
		System.out.println("Zoned date-time from local date-time : " + zoned);

	}

}

Output

The above code produces the following output:

Zoned date-time from local date-time : 2021-08-20T08:22:33.417448900-04:00[America/New_York]

ZoneOffset is the period of time representing a difference between Greenwich/UTC and a time zone. This can be resolved for a specific ZoneId at a specific moment in time.

ZoneOffset offset = ZoneOffset.of("+05:30");
System.out.println("ZoneOffset using ID : " + offset);

Output

The above code snippets produce the following output:

ZoneOffset using ID : +05:30

Time Zone Classes

ZonedDateTime is a date and time with a fully qualified time zone. This can resolve an offset at any point in time. If you want to represent a date and time without relying on the context of a specific server, you should use ZonedDateTime.

ZonedDateTime zoneddateTime = ZonedDateTime.parse("2016-09-09T08:30:42.546+05:30[America/New_York]");
System.out.println("ZonedDateTime from a string : " + zoneddateTime);

Output

The output from the above code snippets is:

ZonedDateTime from a string : 2016-09-08T23:00:42.546-04:00[America/New_York]

OffsetDateTime is 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. OffsetTime is a time with a resolved offset.

OffsetTime time = OffsetTime.now();

System.out.println("Current time from the system clock for default time zone : " + time);
OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(
		"OffsetTime with the specified offset ensuring that the result is at the same instant on an implied day : "
				+ sameTimeDifferentOffset);

OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(ZoneOffset.UTC);
System.out.println(
		"OffsetTime with the specified offset ensuring that the result has the same local time : " + time);

OffsetTime hoursOfDayAltered = changeTimeWithNewOffset.withHour(3).plusSeconds(2);
System.out.println("OffsetTime with the hour-of-day altered with no of seconds added : " + hoursOfDayAltered);

Output

The output from the above code snippets is:

Current time from the system clock for default time zone : 08:28:27.690721+05:30
OffsetTime with the specified offset ensuring that the result is at the same instant on an implied day : 02:58:27.690721Z
OffsetTime with the specified offset ensuring that the result has the same local time : 08:28:27.690721+05:30
OffsetTime with the hour-of-day altered with no of seconds added : 03:28:29.690721Z

Durations

A Duration is a distance on the timeline measured in terms of time, and it fulfills a similar purpose to Period, but with different precision. 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 Duration.

public class Durations {

	public static void main(String[] args) {

		ZonedDateTime today = ZonedDateTime.now();
		ZonedDateTime yesterday = today.minusDays(1);

		// A duration of 3 seconds and 5 nanoseconds
		Duration duration = Duration.ofSeconds(3, 5);
		System.out.println("Duration representing a number of seconds and an adjustment in nanoseconds : " + duration);

		Duration oneDay = Duration.between(today, yesterday);
		System.out.println("duration between two temporal objects : " + oneDay);

	}

}

Output

The output you will get from the above code snippets:

Duration representing a number of seconds and an adjustment in nanoseconds : PT3.000000005S
duration between two temporal objects : PT-24H

Chronologies

In order to support the needs 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:

These interfaces are used for a date without time-of-day or time-zone in an arbitrary chronology, intended for advanced globalization use cases.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *