Java 8 Date and Time API Tutorial

JDK 1.0 had java.util.Date class giving access to Java’s first Date API. However soon after its usage, it started to show cracks of problems on its brittle surface. It did not take any longer to realize that java.util.Date was not thread-safe!

1. Introduction – Why yet another Date and Time API in Java

Put in a multi-threaded environment, an instance of java.util.Date class could easily become inconsistent since the mutators (setters) methods change the very state of the java.util.Date object without any proper synchronization. Even though today most of the mutator methods are deprecated, however, their mere presence itself is extremely dangerous. Following are some of the deprecated mutator methods, notice the java.lang.Deprecated annotations:

Problematic mutators in legacy Date-Time API

public void setDate(int date)  

public void setHours(int hours)

The problem with java.util.Date did not stop there; it was, in my humble opinion, a bit clunky to use. Take for instance its (deprecated!) public constructor:

The legacy not-so-intuitive constructor

public Date(int year, int month, int date)

Here the offset of year starts from 1900, therefore, if we had to instantiate a Date to represent, say, year 2016, we would code something like,

Date (116, month, date)

Quite obviously 116 is not as straight forward as 2016. Similarly, the indexing of month parameter is also not quite consistent with the general human understanding of months wherein months starts from 1 and ends on 12. However, for this constructor the months begin from 0 and end on 11! So the developer has to take that extra care of mapping January to 0, December to 11 so on and so forth! This could well be a probable cause of a painstaking bug!

Finally the String returned by default toString() implementation returns the default time zone of the JVM. So printing a java.util.Date instance would append time zone as well. Something like the following:

Wed May 04 00:00:00 IST 2016

Notice IST (Indain Standard Time) – that is the default time zone of my Hotspot. This by itself does seem to be an innocent looking naive problem, however, the problem is that it gives a false impression as if the intance of Java.util.Date class is time zone context-aware, which is simply not the case!

The JDKteam did worke really hard to address all of these issues. One of their standard fix was the introduction of java.util.Calendar class. However, it also had problems with it, especially, the perennial problem of thread vulnerability. Running parallel to the java.util.Date class was java.text.DateFormat class used to format java.lang.String objects to make them parse-able into java.util.Date instances. However, as you might have guessed already, this class was also thread un-safe.

In the coming sections we would see how JDK engineers worked on these problems and gave us solution in the form of new Date and Time API!

2. The new Date and Time API

The new Date and Time API is packaged under java.time and other java.time.* packages. At the base of this API is the java.time.Temporal interface. This interface binds the basic contract as to how the states of temporal objects such as date (java.time.LocalDate), time (java.time.LocalTime), or combination of these two (java.time.LocalDateTime), would be accessed. Please note that this interface does not bind on its implementation to be immutable but it is nevertheless officially and strongly recommended to make it immutable (obviously for thread safety!). In the subsequent sections we would see how to use these classes.

2.1 LocalDate

The java.time.LocalDate is our first class and it is closer to the java.util.Date class in it that LocalDate encapsulates a particular date (day of month, month of year and year itself) on the time line. By itself the LocalDate class does not contain any information about a particular time (the minute, seconds, hour etc) instant, we have other alternative for doing so.

Before we move on to actually using this class there are a few important aspects about this class. Firstly, this class is immutable and consequently Thread-Safe. Furthermore, an instance of this class is value-type and therefore: (i) we have to use static factory methods to have an immutable instance of LocalDate and (ii) there are no guarantees if any of the identity operations are used on these instances, so it is discouraged to use identity equality with ==, or use intrinsic monitor locks of these objects etc.

Since we have already seen the problem of intuitiveness by using one of the constructors of java.util.Date class, we would be using a public factory method from java.time.LocalDate which takes the same parameters of year, month and day of month:

Public constructor from new Dat-Time API

public static LocalDate of(int year, int month, int dayOfMonth)

LocalDate currDate = LocalDate.of (2016, 7, 3);

The above construct would instantiate an instance of LocalDate representing a time instance of 3rd July, 2016. There is yet another convenience factory method to get a LocalDate instance representing system clock:

public static LocalDate now()

Apart from these, there are various getter methods exposed in LocalDate class to get the month, date and year properties respectively.


int year = localDate.getYear();
java.time.Month month = localDate.getMonth();
java.time.DayOfWeek dayOfWeek = localDate.getDayOfWeek();

We would visit Month and DayOfWeek class very shortly, nevertheless, they are pretty straight forward and self explanatory.

We just saw how to get individual attributes from a LocalDate instance can be accessed; the same properties can also be fetched by passing an instance of java.time.temporal.TemporalField. java.time.temporal.ChronoField is an concrete implementation of TemporalField and its fields can be used to represent any date-time attribute. For instance, we would use ChronoField fields to fetch some of the common date attributes:

Using ChronoField to fetch attributes from LocalDate API

int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);

2.2 LocalTime

Running parallel to LocalDate is the java.time.LocalTime class. It is used to represent a particular time (without any time zone) from the time line. Thus, in order to, say, represent 04 hours, 30 minutes, and 45 seconds, we can write:

Using static factory methods of the new Date-Time API

LocalTime localTime = LocalTime.of(4, 30, 45);

Just like LocalDate, we have friendly getter methods in LocalTime as well; some which accept TemporalField instances and return time attributes based on the TemporalFields and others which directly return the time attribute without accepting any TemporalField instance.

2.3 Amalgamating LocalDate/Time

There are various business requirements where the date or the time alone would not fit the bill in all such situations it is desired to have one instance which could represent both date and time from the time line. The LocalDateTime class helps in this regard. It is again really straightforward to use its APIs:

Using static factory methods of the new Date-Time API

LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); //2014-03-18 13:45:20

There is yet another overloaded version of LocalDateTime.of (…) which accepts instances of LocalDate and LocalTime and combines them:

API to combine Date and Time in one object

LocalDateTime dt2 = LocalDateTime.of(date, time);

It is also possible to provide a specific time to a LocalDate instance using the atTime(...) method:
API to convert from LocalDate to LocalDateTime

LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(LocalTime);

Conversely, we can also provide a date to a LocalTime instance using the atDate(...) instance:
API to convert from LocalTime to LocalDateTime

LocalDateTime dt5 = time.atDate(date);

Likewise it is also possible to separate out date and time factors as LocalDate and LocalTime instances from LocalDateTime reference:

Fetching LocalDate/Time from LocalDateTime instance

LocalDate ld = dt1.toLocalDate(); //2014-03-18
LocalTime lt = dt1.toLocalTime(); //13:45:20

2.4 Instant

Thus far we have seen at least three date-time classes, namely, LocalDate, LocalTime, LocalDateTime. All of these help make developer’s life easy. However, the new JDK8 Date Time API has got java.time.Instant class which is meant more for the machine. All earlier classes we have seen works in terms of years, months, days, hours, minutes, seconds etc. However, the Instant class represents a date-time instant on the timeline via single large number. This number basically represents the number of seconds elapsed since the Unix epoch time conventionally set at 1st January, 1970 UTC.

With static factory methods in Instant class, its pretty straightforward to get an Instance of Instant class representing anything valid on the time line. For instance:

Using static factory method to instantiate Instant class


The above statement represents a time instance which is 5 seconds away from the Unix epoch time! There is an overloaded version of this method which takes a second argument adjusting nanoseconds to the passed number of seconds.

And in case if we need, let’s say, milliseconds elapsed since Unix epoch time we can even do that using the following API:

public static Instant ofEpochMilli(long epochMilli)

Instant class has yet another static factory method now() which provides the current time instant since the epoch time according to the system clock:

public static Instant now()

However, note that Instant class only represents seconds elapsed since epoch time, it does not support differentiating between different date-time parameters which humans understand like year, week, month, hour, minutes etc. However, in order to still work with these parameters we can use java.time.Duration and java.time.Period

2.5 Duration

All of the classes we have visited thus far have represented a particular date-time on the time line, say, t1, t2 etc. Quite naturally we might sometimes need to find the duration between two date-time instances, something like, t2-t1:

Different ways of using Duration class to get difference between two date-time instance

Duration d1 = Duration.between(localTime1, localTime2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2); 

Since the semantics and usage of Instant class is different from LocalDate/Time/DateTime classes, it would be a java.time.temporal.UnsupportedTemporalTypeException if we try to use them together to get the Duration instance. For instance, the following usage of Duration.between(...) is exceptional:

Duration.between(localTime, instant); // UnsupportedTemporalTypeException

The Duration class models a “time-based amount of time” between two date-time instance and as such it does not provide us with specific date-time related properties like year, month, hour etc. For such requirements, we may use another flexible API – the java.time.Period class:

Using Period clas

Period tenDays = Period.between(LocalDate.of(2016, 6, 5), LocalDate.of(2016, 6, 15));

Using one of the convenient factory method from the Period class, we can even instantiate a Period directly, that is, without defining it as a difference between two java.time.temporal.Temporal instances.

Period twoYearsFiveMonthsOneDayPeriod = Period.of(2, 5, 1);

3. Formatting and Parsing

Thus far, we have seen quite a few date-time API from the JDK8 which helps us work with date and time instances, however, there are quite a lot use-cases wherein we would want to parse strings into dates and times. In this section we would checkout the parsing API available to us.

The java.time.format package has a class called DateTimeFormatter which facilitates in formatting to and parsing from java.lang.String to various date-time classes we have seen thus far. Before we move on into seeing DataTimeFormatter in action, we should consider the fact that this class is thread-safe and hence singleton instances of this class can be safely published to various accessing threads; on the contrary, java.text.DateFormat was not inherently thread-safe!

DateTimeFormatter class provides various standard date formatters via its static fields. These all return references of DateTimeFormatter type and can be used in conjunction with date-time APIs that we have seen. Consider for example the following case where we will format an instance of LocalDate to a particular String representation:

Using standard DateTimeFormatter

LocalDate localDate = LocalDate.of(1985, 6, 5);
String strDate = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); //1985-06-05

Conversely, we may parse a valid String (representing a date or time) into its corresponding LocalDate or LocalTime equivalents:
Parse pareable string to LocalDate using DateTimeFormatter

LocalDate localDate = LocalDate.parse ("1985-06-05", DateTimeFormatter.ISO_LOCAL_DATE);

Although DateTimeFormatter provides standard formatters to parse and format, however, it also provides provisions to come up with our own custom DateTimeFormatters using its ofPattern(...) method:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/YYYY");

Using the above DateTimeFormatter (formatter) we can format and parse dates as follows:

Using custom DateTimeFormatter

LocalDate localDate = LocalDate.of(1985, 6, 5);
String strFormat = localDate.format(formatter); // 05/06/1985
LocalDate dt = LocalDate.parse(strFormat, formatter);

Quite interestingly, DateTimeFormatter.ofPattern(...) also accepts a java.util.Locale instance to cater to Local related requirements:

public static DateTimeFormatter ofPattern(String pattern, Locale locale)

4. The TemporalAdjuster

So far so good, however, there are situations when we want to perform special manipulations with date-time instances. Say, for instances, we are interested in the next working day assuming that Saturday and Sundays are non-working days. In all such situation where special date-time adjustments are required we can use the java.time.temporal.TemporalAdjuster.

TemporalAdjuster is an interface, albeit, its a functional interface with only one abstract method, namely, the Temporal adjustInto(Temporal temporal). From the signature of this method it is easy to understand what TemporalAdjuster is meant to do! Basically, it will accept a java.time.temporal.Temporal instance (all of the classes that we have seen so far implement the Temporal interface which defines how the different date-time API are meant to be handled by the application) and after modifying the passed-in Temporal according to the business logic, the adjustInto method will return another Temporal!

Its pretty straight forward to use TemporalAdjuster instance to adjust any date-time entity! Most commonly used date-time APIs like the LocalDate or the LocalTime classes all have the with(...) method which accepts a TemporalAdjuster instance which would adjust this date-time accordingly.

Consider the signature of TemporalAdjuster:

TemporalAdjuster signature

public interface TemporalAdjuster {
  Temporal adjustInto(Temporal temporal);

Next we would implement this interface to calculate the next working day.

Implementing TemporalAdjuster

public class NextOfficeDay implements TemporalAdjuster{

   public Temporal adjustInto (Temporal temporal){
     DayOfWeek dow = DayOfWeek.of(temporal.get(Chronofield.DAY_OF_WEEK));
     int dayToAdd = 1;
     if (dow==DayOfWeek.FRIDAY) dayToAdd = 3;
     else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
     return temporal.plus (dayToAdd, ChronoUnit.DAYS);

With an implementation of TemporalAdjuster in hand, we can easily use it:
Using TemporalAdjuster with other Date-Time APIs

LocalDate lastFridayOfJuly2016 = LocalDate.of(2016, 7, 29);
LocalDate nextWorkingDay = lastFridayOfJuly2016.with(new NextOfficeDay ()); //2016-08-01

Lambda Lovers may use java.time.temporal.TemporalAdjusters.ofDateAdjuster(UnaryOperator dateBasedAdjuster):

TemporalAdjuster with Lambda implementation

TemporalAdjuster tempAdj = TemporalAdjusters.ofDateAdjuster(temporal->{
      DayOfWeek dow = DayOfWeek.of(temporal.get(Chronofield.DAY_OF_WEEK));
     int dayToAdd = 1;
     if (dow==DayOfWeek.FRIDAY) dayToAdd = 3;
     else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
     return temporal.plus (dayToAdd, ChronoUnit.DAYS);

And then we may use this TemporalAdjuster the normal way:

nextWorkingDay = lastFridayOfJuly2016.with(tempAdj);

For one more note, TemporalAdjusters class has some predefined utility TemporalAdjuster implementations exposed through various static factory methods that it has!

5. Time Zone

Thus far all the date-time APIs that we have seen had nothing to do with the time zones of various regions. Nevertheless, in real time software challenges, time zones play a critical role notwithstanding its complications. Therefore, in order to further abstract the complication of time zones, the new date time API provides java.time.ZoneId class as an alternative for java.util.TimeZone class.

The ZoneId class holds unique IDs representing various well-known regions across the globe. For instance, "Europe/Paris" is a valid zone id. With each time zone, there is a specific set of rules associated which guarantees that across the entire region the standard timing is going to remain the same! All such rules are encapsulated inside the java.time.zone.ZoneRules class.

With that in mind its really easy to get a reference representing a particular time zone; we may say:
Using ZoneId class via its static factory method

ZoneId parisTimeZone = ZoneId.of("Europe/Paris");

Then onwards its pretty straight forward to associate a time zone to a particular date. For instance:

Associating Time Zone to a LocalDate

LocalDate ld = LocalDate.of (1985, 6, 5);
ZonedDateTime zonedDateTime = date.atStartOfDay(parisTimeZone);

Notice the return type of method atStartOfDay(...). It is java.time.ZonedDateTime. As name suggests a ZonedDateTime encapsulates all relevant information. It has Date, Time and the Time Zone (for offset calculation purposes) all embedded into it.

java.time.ZoneOffset is a subclass of ZoneId which is used to represent the time-zone offset of the given time from the standard Greenwich/UTC such as -05:00. In order to work with, simply use its static factory of(...) method:

Using ZoneOffset class

ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");

Since New York which corresponds to US Eastern Standard Time is factually 5 hours behind the Greenwich zero meridian, newYorkOffset correctly represents it (although ZoneOffset does not take into account the Day Light Saving complications).

With ZoneOffset ready in hand we can apply it with any of the date-time APIs that we have seen earlier to manage the time zone offset differences between different regions of the world:

Using OffsetDateTime class

LocalDate ld = LocalDate.of (1985, 6, 5);
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of (ld, newYorkOffset);

Notice the class java.time.OffsetDateTime. This class encapsulates the date, time and the offset difference from the standard Greenwich zero meridian!

6. Calendar

Up until JDK8, ISO-8601 was the defacto calendar system in usage. However, all around the world there are numerous other local calendar systems used, at times, very religiously by various communities across the globe. JDK8 has included four such calendar systems:

  • Japanese calendar
    Hijrah calendar (Islamic Calendar)
    Minguo calendar
    Thai Buddhist calendar

All of these new calendar systems and their respective dates are packaged under java.time.chrono package. The java.time.chrono.Chronology interface defines the various non-standard calendar systems and the java.time.chrono.ChronoLocalDate interface defines the various dates used in the Chronology calendars.

Since it seems like out of the four non-ISO calendars included, the Hijrah Calendar is most complicated as it is based on Lunar astronomy and may even have variants, for the sake of demonstration we would consider only Hijrah Calendar herein.

6.1 Chronology and ChronoLocalDate

As mentioned earlier that Chronology and ChronoLocalDate model non-ISO calendar and its corresponding dates respecticely, we would start by demonstrating their usage!

The Chronology interface has a static factory of(...) which takes in a String parameter identifying the particular calendar system we need. These are standard identifier specified by the CLDR and Unicode Locale Data Markup Language (LDML) specifications!

Fetching a Hijrah Calendar

Chronology islamicCalendar = Chronology.of ("islamic");

Once we have an instance of the calendar, we can access its dates:

Using Hijrah Calendar

ChronoLocalDate currIslamicDate = islamicCalendar.dateNow();

However, there are dedicated Islamic calendar and dates also available via the java.time.chrono.HijrahChronology and java.time.chrono.HijrahDate classes. In the next section we would see their usage.

6.2 HijrahChronology and HijrahDate

The new Java 8 Date and Time API also provides specific extensions and implementations of Chronology and ChronoLocalDate in the form of java.time.chrono.HijrahChronology and java.time.chrono.HijrahDate respectively. HijrahChronology is based on the Islamic Hijrah calendar system which itself depends on lunar movements. The length of each month according to this calendar is judged by any authorized seeing of the new moon! Generally the length of each month is 29 or 30 days. Ordinary years have 354 days; leap years have 355 days. The HijrahDate class operates on this calendar. Below we would see some of the usages of these entities.

Using the HijrahDate API

 HijrahDate ramadhaan = HijrahDate.now() //fetches the current Hijrah date based on Hijrah Calendar
 .with(ChronoField.DAY_OF_MONTH, 1) // we change to first day of that Islamic month
 .with(ChronoField.MONTH_OF_YEAR, 9); // we change the month itself to Ramdhaan!

7. Conclusion

The intial Date Time API has multiple flaws; perhaps the most glaring of them all was the design choice of making the original Date and Time APIs Thread-Unsafe! In contrast to this, the current Date and Time API with JDK8 is immutable and consequently thread safe.

Lastest Date and Time API is very intutive as well since it provides different set of APIs for dealing with machines and humans, respectively.TemporalAdjuster is special inclusion, in it, it provides for flexible ways to modify date and time instances. The Formatter is also made thread safe in new date-time API which further bolsters its robustness. Finally, we have new, non-standard, region and locale specific calendar systems to work with. This would prove to be very handy in projects all around the world.

Nawazish Khan

I am Nawazish, graduated in Electrical Engineering, in 2007. I work as a Senior Software Engineer with GlobalLogic India Ltd (Banglore) in the Telecom/Surveillance domain. A Java Community Process (JCP) member with an unconditional love (platform, technology, language, environment etc does not matter) for computer programming. Extremely interested programming multi-threaded applications, IoT devices (on top of JavaME) and application containers. The latest language of interest being Google Go; and Neo4j as the NoSQL Graph Database solution.
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Newest Most Voted
Inline Feedbacks
View all comments
Back to top button