|Java SE 8, JSR 310|
In this article of the “Java SE 8 new features tour” series, we will deep dig into explanation, and exploring the code of JSR 310 specification, on how to Calculating timespans with the new DateTime API, Calculating time spans with Instant and Duration, Representing date and time values, Formatting date and time values, and Supporting time-zone offsets.
In the previous article “Processing Collections with Streams API”; I have deeply dived into explanation, and exploration on how to traversing collections with streams, Creating streams from collections and arrays, and finally Aggregating stream values.
Source code is hosted on my Github account: clone it from here.
Table of Content:
- Calculating time spans with Instant and Duration.
- Representing date and time values.
- Formatting date and time values.
- Supporting time-zone offsets.
1- Calculating time spans with Instant and Duration:Introduction:
Java SE 8 includes a complete new API for managing date and time values. The classes that actually hold the data in this new API are all immutable and thread safe. So that means you don't have to worry about passing objects around in a multi threading environment. And if you're using them in parallel streams, everything will always work perfectly. All of the classes in this new API are members of the package
java.time. And I'll start with two of the core classes named
I'll start with this example in the package
eg.com.tm.java8.features.datetime.InstDuration of project
Java8Features. In a class code named
InstantAndDuration. And I'll place all this code inside the
main method. The first class I'll describe is named
Instant. I'll type the name of the class and press Ctrl + Space, and choose the class from the
java.time package, and it's imported. An instant object represents an instant on the Java timeline. Just as with the date class which is a part of the old way of managing dates and times, an instance represents a number of milliseconds.
Since the Java epoch time, January 1st 1970. To declare an instant object, I'll declare it with its type, and give it a name of start. And then I'll call a static method of the instant class called
now(). And this represents the moment on the current machine when the code was called. Then I'll output that value as a string, using standard system output. Run the code and you will see the output starting off with the date in year, month, date format and then the time after the letter T.
Once you have a moment in time, you can use it to calculate a difference between that and another moment in time. So I'll create another instant, which I'll call end. And I'll get it's value from the method
now() as well. Then I'll use system output, and output that value. Notice that there is a slight difference between the two values, and that's the amount of time it is taking on my system. To process this line of code, that's outputting the start value.
If I were to move that line of code down, so I wasn't doing any other processing between the two calls to the now method, the two values would be identical, Or they might be off by a thousandth of a second.
Now, I'll show you how to calculate the difference between these two values. When you compare two instants to each other, you'll get an object called a duration. It's represented by the
Duration class, which is also a member of
Java.time. I'll name this object elapsed. An I'll call a static method of the duration class called
between(Temporal startInclusive, Temporal endExclusive). Notice that it's looking for objects typed as something called
Temporal. The Instant class is a sub-class of Temporal.
I'll pass in start and end as my two temporal values. And then I'll output the difference. I'll pass in a literal label of elapsed, and then I'll pass in my variable. That duration object starts with the letter p and then t for time. This is again an ISO formatted value. And then it shows me zero seconds. Well let's see what happens if we toss in a call to the sleep method. I'll place the cursor here between the start and end calls. And I'll use the
I'll press the period, and then press Ctrl+Space. And then I'll call the
sleep() method and pass in a value of 1,000. Meaning sleep for one second. The sleep method can throw an error, so I'll use a quick fix, and I'll add a throws declaration to the main methods signature. I'll save and run the code, and I see that my lapse time is now 1.001 seconds. You can never really count on things being exact, it all depends on what's going on, on the processing computer.
Next, I'll take this printline call, and move it back to its original location. So now after I get the start value, I'll be executing a printline command. And I'll be sleeping for one second. And I'll run the code. And now my lapse time is 1.057 seconds.
To make this a little bit more readable, I'll add a call to the method of the duration object using
elapsed.to millis. That means, get the milliseconds equivalent. And I'll append to that, milliseconds, and I'll run the code.
Elapsed: 1074 milliseconds
And now I see, a readable value, of 1,054 milliseconds. So, that's the
Instant class and the
Duration class. Two of the core classes, of the new date time API, in Java SE 8.
2- Representing date and time values:Introduction:
I previously described how to use the instant class in the new date time API to represent a moment in the Java timeline. Here are three more useful classes to represent parts of dates and times. They're called local date, local time, and local date time. Lets say, for example, that you only want to represent a date value. And you don't care about times or seconds or milliseconds but only the current date. Create an instance of a class named
I'm working in a package
eg.com.tm.java8.features.datetime.localdt of project
Java8Features. In a class code named
LocalDateAndTime. with a main method. I'll start with the name of the class
LocalDate. And when I press Ctrl + Space, I'll choose the class from the java.time package. I'll name the object
currentDate and I'll get its value with
localDate.now. Notice that there's consistency in the syntax between working with an instant, a date, a time and a date time.
To get the current value on the current machine, you always use the
now method. Now, I'll output that date in its default format. I'll save and run the code, and it shows me the date in year-month-date format.
You can also create a date object using specific year, month, and date values. Once again, I'll create an object typed as
LocalDate. And I'll name this one
specificDate. To get this value, call
And there are a couple of available versions. I'm going to use the one that takes three integer values. They aren't named in the documentation but they represent the year, the month, and the day. I'll patch in the values of 2,000, 1, and 1. Now, in the older version of the date time API using the date class. When you were dealing with months, you always had to do it with a 0 based off set. So for January you'd use 0, for February 1 and so on.
And that wasn't particularly intuitive. In the new day time API, everything is 1 based. So 1 means January, 2 means February and so on. Just as you would normally expect.
I'll once again use system output, and this time I'll, I'll put the new specific date. And when I save and run that, I get the value that I put in, January 1st, 2000.
If you only want to represent a time value, use the
LocalTime class, I'll type the name of the class and import it, I'll name the object
currentTime and I'll get its value from
Again, using the same sort of syntax as with localdate and with instant. Then I'll use system output. And I'll output that current time. The default value for the time is in 24 hour notation, and it shows the hour, the minute, the second, and the milliseconds.
LocalTime. I'll name this
specificTime. And, just as with the local date class, I'll call the method named
of. Again, there are a number of different versions taking varying numbers of arguments.
I'll use the version that's looking for three integer values and I'll type in 14, 0 and 45. And then I'll output that value to the console. And there's the result. 14, 00 and 45 seconds. Notice that because I didn't provide a milliseconds value the formatted version of that time doesn't show values after the dot.
Finally, I'll show how to use the
I'll type the name of the class and import it. I'll name this object
currentDT. And I'll get its value from
LocalDateTime.now. When you output a date time value, you'll get a long format ISO date time. Starting with the date, and ending with the time. And if there are milliseconds in the value, they'll be displayed. And finally, I'll create a specific date, time and I'll do this by combining my specific date and my specific time.
That code will look like the next code. I'll create a
LocalDateTime object. I'll name it,
specificDT and I'll call
LocalDateTime.of again. And this time, I'll use this first version of the method that accepts a local date object and a local time object. You can also construct your date time value from combinations of years, months, dates, and time values. I'll pass in my specific date and my specific time. And then I'll output it to the console. And when I run that code, my specific date time is a combination of my specific date and my specific time.
So, those are the three classes that you can use to represent date and time values on the local machine in the current time zone. There are also classes you can use to get time zone sensitive values. And I'll describe those next.
3- Formatting date and time values:Introduction:
I've previously described how to use the
LocalDateTime classes to represent time values. To present this information to a user, you'll need to format it. And for that purpose, there's a new class named
DateTimeFormatter. I'll show you how to create formatters using some simple syntax. And then how to do very custom work using a class called
Daytime Formatter Builder.
I'm working in a package
eg.com.tm.java8.features.datetime.format of project
Java8Features. In a class code named
DateTimeFormater. with a main method.
First, I'll create a date. I'll give it a type of
LocalDate, making sure to import that class. And I'll name it
currentDate. And I'll get its value from
LocalDate.now. Next I'll create a formatter object. I'll type the name of the class,
DateTimeFormatter, and select it from the
java.time.format package. I'll name this object
df. Now there are a number of ways of creating a formatter. One of the simplest is to use a constant of the
I'll once again type in
DateTimeFormatter. And then after I type in the period I see a list of all of the available constants. I'll choose
ISO Date. And this will provide the default formatting for this object. Then, I'll use System Output. I'll call the formatted object format method and pass in the date object. And here is the result. I'm outputting the value in year month date format. With the month and the date padded out to two characters each. Now you can do the same sort of thing with times and date times.
I'll take above bit of code and I'll duplicate it a couple of times and I'll make some changes. In the second version, I'll change the type from local date to local time. The object name to current time, and the name of the class I'm using to get the value to local time. I'll change the name of the date time formatter from DF to TF for time formatter. And I'll change the constant I'm using to ISO Time. And then I'll change the object that I'm formatting. I'll be sure to import the
And then I'll make similar changes to the third version. The class that I'll be working with this time is
LocalDateTime. I'll be sure to import it. I'll name this object, current DT. And I'll change the class that I'm calling the now method from. I'll change the formatter to DTF for
DateTimeFormatter. And I'll change the constant to ISO Date Time. And then I'll format the current DT object. I'll be sure I'm using the right formatters in each version of the code. I'll save the change, and run the code.
And there are the three formatted values. Now so far, I haven't really accomplished that much, because I've used the constants that represent the default formatting. But let's take a look at some custom formats that are available.
I move down to below the existing code. And i'll create another
DateTimeFormatter, i'll name this one f_long for the long date format and i'll get its value by calling a method of the DateTimeFormatter class called a Localised Date.
Notice that there are methods for date, time and date-time, with a variety of arguments. I'll choose this one, of localized date, and I'll pass in a constant of a class called,
FormatStyle. Be sure to import this class. And then after you type the period, you'll see that there are four constants available. Full, long, medium and short. I'll choose the long version and then I'll output the formatted date by calling F _ long.format and I'll pass in the current DT object.
When i run this code i get the long version of the date.
August 9, 2016
I'll show you another version of this by duplicating these two lines of code and for this version i'll change the formatter name to f_short, i'll change the constant I'm using to short also. And I'll change the name of the formatter that I'm calling. So the long version is the name of the months spelled out. A comma after the date, and then the year in four digit format, and the short version at least for the current locale, is the month and date, without padding, with slashes separating values, and a two character year.
And next, I'll show you how to use locales. I'll create a couple of strings. The first will be called fr_ short, for French, in short format. To get that value i'll call my f_short formatter and then i'll will call method name
withLocal(). To get a locale value i'll use the
Local class, this is an existing class that's been available in previous versions of Java. It's a member of the package
And then, I can call one of the many constants representing various locales. I'll use French. And then from there, I'll call the format method, and pass in the current date time.
I'll duplicate that line of code and for this version I'll use fr_long. I'll use the long formatter and otherwise the code will be the same. And then I will output those two values
And here's the result. Notice for fr_short that the month and the day are reversed from the US version. And that's because in Europe, the date is stated first, and then the month, and then the year. And when I use the long version I get the months spelled in French.
9 août 2016
Finally I'll show you how to build completely custom formatters using a class called the date time formatter builder. It uses the builder design pattern, where you can call multiple methods, each returning an instance of the current builder.
I'll type the name of the class and make sure that it's been imported. And I'll name the object b. I'll instantiate it with the new keyword, and the constructor method.
Now, at the end of that code I won't put in the semicolon because I want to immediately call a series of methods that let me build the formatter from scratch. I'll start with a method named Append Value. Notice there's Append Instant, Append Literal, Append Localized and many many others. I'm going to call a method named
appendValue() that accepts an instance of a class named
TemporalField and then I'll use an enumerator named
ChronoField. Which is extended from that
And from there I'll use a constant name month of the year. Next I will append a literal value. This can be any character or any string. And just to make this thoroughly unique, I'll put in a couple of pipe characters. Now I'll take these two lines of code and duplicate them for the second version instead of month of year. I'll put in day of month. Notice there's also day of week and day of year. Then I'll duplicate that line of code and move it down. And I'll finish the expression with
Once you've created the builder object, you can get the formatter. I'll create new object, typed as
DateTimeFormatter. I'll name it
f for Formatter. And called builder objects, to formatter methods, and then finally I'll format the current date time value. I'll use system output and call
f.format(), and pass in
And now when I run my code, I get completely customized format.
You can use the
DateTimeFormatter builder to build any format you like. And because it uses the builder design pattern, it's easy to create and to maintain the code.
4- Supporting time-zone offsets:Introduction:
The new date time API offers a number of classes that let you manage time zones. Creating day time objects that are offset from Greenwich Mean Time, by certain number of hours, or by particular locations, and calculating differences between time zones.
I'm working in a package
eg.com.tm.java8.features.datetime.zone of project
Java8Features. In a class code named
TimeZones. with a
In its main method, I've created a
DateTimeFormatter and a
LocalDateTime object. The
LocalDateTime represents the current date and time on my system, in my time zone. And that's Egyptian Time because I'm on the Middle East.
And then I'm outputting a formatted value to the console. I'm outputting the value using a short format. And in Egypt notation, it's month, day and year.
8/9/16 10:22 PM
In order to represent a time zone based date time value, use the class
ZonedDateTime. Just like
LocalDateTime, it's immutable and thread safe. I'll type the name of the class, and then press Control + Space to add the import statement. And I'll name the object
gmt for Greenwich Mean Time.
There are a few different ways of creating this object. I'll show you how to create the object calculating an offset from Greenwich Mean Time. I'll use the
ZonedDateTime class again, and after I type the period, I'll see that there are many methods available. I can call
now() again, to get the date time value in my area. I can call
of() methods that let me do various calculations. I can parse strings, but I'm going to use this version of the now method. I'll pass in an instance of the
ZoneId represents a certain number of hours offset from Greenwich Mean Time. And I'll get that value by calling a method named
ZoneId.of(). And I'll pass in a literal string of "GMT+0". That means, show me the current date and time value in Greenwich Mean Time.
Now I'll duplicate my code that's outputting the value to the console. I'll move that down, and I'll change this version to output gmt. I'll run the code, and there's the result.
8/9/16 8:28 PM
I'm in the Middle East Egypt, and right now, Greenwich Mean Time is two hours ahead.
Here is another approach to getting a
ZonedDateTime. Lets say you wanted to get the ZoneDateTime in New York. There are many built-in strings, or constants, that will let you name particular locations, and you'll get back the correct
ZoneId for that location, and you won't have to worry about the math yourself. I'll create another ZonedDateTime object, and this time i'll name it ny for New York, and I'll get it value by calling
ZonedDateTime.now(), and again I'll pass in Z
oneId.of(), but this time i'll pass in a string of America/New_York.
Make sure to spell this string exactly as you see it here. I'll create a line of code to output that value. I'll save the change, and run it. And New York is on East Coast time, three hours ahead of Pacific time.
8/9/16 4:36 PM
To find out about all of the available strings, you can call a method of the
ZoneId class called
getAvailableZoneIds(). You'll get back a set. I'll type
Set and press Control + Space, and then choose set from
And I'll set the generic type of the items in this set to
String. I'm name the set zones. And then I'll call the method,
ZoneId.getAvailableZoneIds. Then I'll loop through the strings with the
forEach() method. And then I'll pass in a Lambda expression. So I can deal with each of the items in turn.
When I run that code, I see all of the available strings.
Now, there are so many it might be hard to find the one that you're looking for. So let's say that I wanted to look for London.
And use the time zone for that particular location, at this particular time of year. As I showed earlier in the article, I could use a
predicate to search the strings. I'll create a predicate object. And I'll set the generic type to
String. And I'll name the object condition. Then I'll implement the predicate with a lambda expression. I'll pass in str, and then I'll implement the predicate with a condition. str.contains, and I'll pass in a string of London.
Then I'll refactor my Lamba expression. I'm going to wrap
System.out.println() in braces. Then I'll expand the code to make it a little bit easier to work with. I'll add the semi colon at the end of the print line, and then I'll create an if statement. And I'll set the condition to
condition.test(), and I'll pass in
z for the current zone. I'll move the p
rintln() statement, to within the condition, and now i'll only printout strings that match my predicate test.
I'll save the change and run the code, and there's the result. I find that the correct string for London is:
So that's a little bit about working with time zones. Again, use the
ZonedDateTime class instead of
LocalDateTime to represent values that you can modify and calculate against. The ZoneId represents an off set from Greenwich Mean Time. And there's also a class called Zone Offset that you can use to calculate different time zones against each other.
- The Java Tutorials, Trail: Date Time
- The LocalDate API
- JSR 310: Date and Time API
- JSR 337: Java SE 8 Release Contents
- OpenJDK website
- Java Platform, Standard Edition 8, API Specification
I hope you enjoyed reading it, as I enjoyed writing it, please share if you like it, spread the word.