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!
Table Of Contents
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
@Deprecated public void setDate(int date) @Deprecated 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
@Deprecated 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 JDK
team 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.
helloWorld.jsp
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 TemporalField
s 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
Instant.ofEpochSecond(5)
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 DateTimeFormatter
s 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
@FunctionalInterface 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.