Java 8 Date/Time API Tutorial
In this article we are going to explain the main features of the new Date/Time API coming with Java 8. We are going to briefly explain why a new Date/Time API is necessary in Java and what benefits it has in comparison with the “old” world.
All examples listed in this article have been done using Eclipse Luna version 4.4 and Java version 8 update 5.
Why do we need a new Date/Time API in Java
In the “old” Java world there were basically two main possibilities when dealing with dates and times: java.util.Date
and all the related classes and interfaces; and the Joda
library.
The usage of the java.util.Date
related classes had several problems:
Date
is not a date, but a timestamp, Calendar
is mix of dates and times…SimpleDateFormat
and Calendar
have problems while working together.The Joda
library is a very good approach and solves some of these issues but has some performance and design problems that the new API solves. The Java 8 Date/Time API is based in the Joda library and has been implemented by the Joda library team.
The new API has solutions for all the problems mentioned at the beggining of this chapter. The main design principles are that the used classes are inmmutable, that dates and times are separated and that it supports global calendars (although it is based on the ISO calendar). The new Date/Time API was developed under the JSR 310.
In the following chapters we are going to show how to use the new API by going through a bunch of examples listing its main features:
LocalDateTime, LocalDate, LocalTime, Instant and others
There are many classes that we have to know before we start to use the API productively. In this chapter we are going to show several snippets with code using these classes. Among them we have LocalDateTime
, LocalDate
, LocalTime
or Instant
.
It is possible to create a local date time directly with the method now()
:
LocalDateTime localDateTime = LocalDateTime.now();
or by using a clock to pass to the now()
method:
Clock clock = Clock.systemDefaultZone(); localDateTime = LocalDateTime.now( clock );
or using a zoneId. In this article we are going to see how to use zone ids more in deep:
ZoneId zoneId = ZoneId.systemDefault(); localDateTime = LocalDateTime.now( zoneId );
It is also possible to create a local date time by passing arguments with predefined values:
localDateTime = LocalDateTime.of( Year.now().getValue(), Month.FEBRUARY, DayOfWeek.SATURDAY.getValue(), 1, 1, 1 );
Until here, we just saw how to create LocalDateTime
that contains date and time. For sure the new API offers the possiblity to work just with dates (LocalDate
) and just with times (LocalTime
), we are going to see this in the next chapters.
Mentioned that, it is also possible to create a date time by combining a date and a time:
LocalTime time = LocalTime.NOON; LocalDate date = LocalDate.now(); localDateTime = LocalDateTime.of( date, time );
it is also possible to create a date (or a time) using epoch values (days or seconds since 1970):
LocalDate localDate = LocalDate.ofEpochDay( 150 );
Here only the date is created, the time part is “ignored”. This would be the output of printint this localDate
in the console:
1970-05-31
So, 150 days after 1970-01-01, ignoring the time.
There are many other possiblities to create dates and times (and other structures like Instants, periods or durations) and to combine them. In this article we are going to see some of them:
Stateless
One of the best things that the new API offers is that it is stateless. This means that variables created using the Date/Time API are thread safe, so it is much easier to implement thread safe applications using this API than before.
We are going to show this with some examples:
LocalDateTime timeInThePast = LocalDateTime.now().withDayOfMonth( 5 ).withYear( 2005 ); System.out.println( "timeInThePast: " + timeInThePast ); LocalDateTime moreInThePast = timeInThePast.minusWeeks( 2 ).plus( 3, ChronoUnit.DAYS ); System.out.println( "timeInThePast: " + timeInThePast ); System.out.println( "moreInThePast: " + moreInThePast );
In the code above we create a date time based on the current moment, we change the month to May and the year to 2005, then we print it out. After that we create a new date time by subtracting 2 weeks and adding 3 days to the date time created before. At the end we print both out to the console. This is the output:
timeInThePast: 2005-07-05T22:35:53.874 timeInThePast: 2005-07-05T22:35:53.874 moreInThePast: 2005-06-24T22:35:53.874
As we can see, the first variable is not modified, although some operations have been made to it. So we can rely on that and use this API in our concurrent applications. This is a big advantage in comparison with the “old” API.
Temporal Adjusters
Adjusters are classes and interfaces with methods that “adjust” any kind of temporal value preserving its state, i.e. the state and values of the used temporal value does not change after applying the adjuster operations.
Here is an piece of code showing how to use a temporal adjuster (in the project attached at the end of the article you can find more examples):
LocalDate now = LocalDate.now(); LocalDate adjusted = now.with( TemporalAdjusters.lastDayOfMonth() ); System.out.println( "now with last day of month " + adjusted ); System.out.println( "now " + now );
and the output would be something similar to:
now with last day of month 2014-07-31 now 2014-07-24
We can see that the value of the variable now
did not change.
Adjusters can be used in combination with a zone id (or with a ZonedDateTime
) and its computations take into consideration the proper zone.
You can also create your own custom adjuster. To do this, you create a class that implements the TemporalAdjuster
interface with a adjustInto(Temporal)
method
It is good to mention the interface TemporalQuery
that can be used to retrieve information from a temporal-based object.
Time Zones
All the classes in the API can be used in combination with a different time zone. There are aprox. 40 time zones available in the API. They can be retrieved via its key or via the long name:
ZoneId zoneIdParis = ZoneId.of( "Europe/Paris" ); ZoneId zoneIdAGT = ZoneId.of( ZoneId.SHORT_IDS.get( "AGT" ) );
and a time (or date) can be created using these time zones:
LocalDateTime dateTime = LocalDateTime.now( zoneIdAGT );
There is a class called ZonedDateTime
that contains information about a zone and about a concrete date and time:
ZonedDateTime zonedDateTimeAGT = ZonedDateTime.of( dateTime, zoneIdAGT ); System.out.println( "Zoned Date Time AGT " + zonedDateTimeAGT );
the variable zonedDateTimeAGT
holds informatoin about the zone AGT and about the LocalDateTime
passed as parameter. The output would be something like:
2014-07-23T17:55:51.612-03:00[America/Argentina/Buenos_Aires]
If we are interested in knowing the current time in all the available time zones we can write a Lambda
expression as follows:
ZoneId.SHORT_IDS.keySet(). stream().forEach( zoneKey ->System.out.println( ZoneId.of( ZoneId.SHORT_IDS.get( zoneKey ) ) +":"+ LocalDateTime.now(ZoneId.of(ZoneId.SHORT_IDS.get( zoneKey ) ) ) ) );
This expression iterates using an stream
through all the available zones (ZoneId.SHORT_IDS
is a map that contains all the zones) and prints them out. Maybe it does not look that nice…but I like Lambdas! so the output would be something like:
Asia/Shanghai : 2014-07-25T05:14:37.206 Africa/Cairo : 2014-07-24T23:14:37.207 America/St_Johns : 2014-07-24T18:44:37.209 America/Puerto_Rico : 2014-07-24T17:14:37.210 America/Phoenix : 2014-07-24T14:14:37.210 Asia/Karachi : 2014-07-25T02:14:37.210 America/Anchorage : 2014-07-24T13:14:37.210 Asia/Dhaka : 2014-07-25T03:14:37.211 America/Chicago : 2014-07-24T16:14:37.212 -05:00 : 2014-07-24T16:14:37.212 -10:00 : 2014-07-24T11:14:37.212 Asia/Tokyo : 2014-07-25T06:14:37.212 Asia/Kolkata : 2014-07-25T02:44:37.213 America/Argentina/Buenos_Aires : 2014-07-24T18:14:37.213 Pacific/Auckland : 2014-07-25T09:14:37.213 -07:00 : 2014-07-24T14:14:37.213 Australia/Sydney : 2014-07-25T07:14:37.214 America/Sao_Paulo : 2014-07-24T18:14:37.215 America/Los_Angeles : 2014-07-24T14:14:37.215 Australia/Darwin : 2014-07-25T06:44:37.216 Pacific/Guadalcanal : 2014-07-25T08:14:37.216 Asia/Ho_Chi_Minh : 2014-07-25T04:14:37.216 Africa/Harare : 2014-07-24T23:14:37.216 Europe/Paris : 2014-07-24T23:14:37.216 Africa/Addis_Ababa : 2014-07-25T00:14:37.216 America/Indiana/Indianapolis : 2014-07-24T17:14:37.217 Pacific/Apia : 2014-07-25T10:14:37.217
Powerful, isn’t it?
Instants and timestamps
An instant is a point of time counting from the first second of 1.1.1970, also known as epoch. This timestamps are very useful and used in several applications and operating systems. The Instant
class is the API answer for this machine view of the time.
An Instant can be created in a similar way as a date or a time:
Instant now = Instant.now();
Or using an epoch directly (in seconds or days):
Instant epochNow = Instant.ofEpochSecond( 60 * 60 * 24 * 30 );
Instants support several getter operations:
System.out.println( "epoch seconds " + now.getEpochSecond() ); System.out.println( "nano seconds " + now.getNano() );
and operations like plus and minus in order to modify them:
Instant tenSecondsAfter = now.plusSeconds( 10 );
Instant values can be negative as well.
Periods
A period is a distance on the timeline. Its precision is in years, months and days. This is a very important innovation to all the classes seen until this point, that were basically points of time (instants, dates, times). It is possible to create a period using an amount of years, months and days or a set of those:
Period period = Period.of( 3, 2, 1 ); Period period4Months = Period.ofMonths( 4 );
And also by specifying an start and an end dates:
period = Period.between( LocalDate.now(), LocalDate.of( 2015, Month.JANUARY, 1 ) );
Periods support different operations.
period4Weeks.get( ChronoUnit.DAYS )
It is possible to modify a date using a period by applying operations like plus or minus:
LocalDate newDate = LocalDate.now().plus( period4Months );
Durations
A Duration is similar to a period but its precision is based on hours, minutes, seconds, miliseconds…It is also a distance on the timeline. A Duration can be created using an amount of seconds (or minutes, hours…) or by specifying an start and an end times:
Duration duration = Duration.ofSeconds( 59 ); duration = Duration.between( LocalTime.now(), LocalTime.MIDNIGHT ); duration = Duration.between( LocalTime.now( ZoneId.of( ZoneId.SHORT_IDS.get( "AGT" ) ) ), LocalTime.MIDNIGHT );
As we can see in the code above, zones can be used as well.
Durations support operations like plus, minus, gets and others.
duration59Mins.get( ChronoUnit.SECONDS )
By using these operations it is possible to modify a date and a time using a desired duration.
LocalTime timeNow = LocalTime.now().plus( duration59Mins );
Formatting and parsing
We can’t finish the article without showing the several options available while parsing and formatting dates and times.
It is possible to parse a given date using the desired pattern (can be predefined or customized):
LocalDateTime dateTime = LocalDateTime.of( 2014, Month.DECEMBER, 15, 15, 0, 30 ); System.out.println( "without formatting " + dateTime ); String isoDateTime = dateTime.format( DateTimeFormatter.ISO_DATE_TIME ); System.out.println( "iso date time " + isoDateTime ); String isoDate = dateTime.format( DateTimeFormatter.ISO_DATE ); System.out.println( "iso date " + isoDate ); String isoTime = dateTime.format( DateTimeFormatter.ISO_TIME ); System.out.println( "iso time " + isoTime ); String patternDateTime = dateTime.format( DateTimeFormatter.o System.out.println( "using pattern " + patternDateTime );
and the output would be:
without formatting 2014-12-15T15:00:30 iso date time 2014-12-15T15:00:30 iso date 2014-12-15 iso time 15:00:30 using pattern 2014.12.15 03:00:30
It is also possible to parse a string into a date (or a time or both):
LocalDate fromString = LocalDate.parse( "2014-01-20" ); System.out.println( "parsed from an string " + fromString ); LocalDate parsedFromPatern = LocalDate.parse( "2014/03/03", DateTimeFormatter.ofPattern( "yyyy/MM/dd" ) ); System.out.println( "using pattern " + parsedFromPatern );
As we can see in the code above we can also force the application to parse only with the desired pattern, throwing a DateTimeParseException
otherwise.
The class DateTimeFormatter
has several features that are not explained in this article. For more information about these please visit the official page: http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
So that’s it!
Summary
In this article we explained many of the features and possibilities offered by the new Date/Time API like differentiation between dates and times; definition and usages of instants, durations and periods. We made some example to show that the new API is stateless, we explained the temporal adjusters briefly and we saw how to parse and format dates.
Apart of all the new date/time related functions and operations, it offers safety and functionality for developers while handling time related information. And it is thread safe!
We mentioned several times other features that came out with the Java 8 update. If you want to read more about all these features please visit the page: http://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html.
If you want to have more information about the implementation details of the Date/Time API please visit following links:
– Oracle tutorial: http://docs.oracle.com/javase/tutorial/datetime/TOC.html.
– API summary: http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html.
– API temporal package also mentioned in this article: http://docs.oracle.com/javase/8/docs/api/java/time/temporal/package-summary.html.
– JSR 310: https://jcp.org/en/jsr/detail?id=310.
Download the examples
All examples from this article (and some more) can be downloaded in the following link: datetimeapi.zip