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 Instant
and Duration
.
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.
Result: 2016-08-05T21:21:59.601Z
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.
Result:
2016-08-05T21:33:55.971Z
2016-08-05T21:33:56.048Z
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.
Result:
2016-08-05T21:34:43.365Z
2016-08-05T21:34:43.365Z
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.
Result: Elapsed: PT0S
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 Thread
class.
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.
Result: Elapsed: PT1.001S
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.
Result:
2016-08-05T22:28:42.685Z
2016-08-05T22:28:43.769Z
Elapsed: PT1.084S
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.
Result:
2016-08-05T22:32:52.657Z
2016-08-05T22:32:53.731Z
Elapsed: 1074 milliseconds
Conclusion:
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 LocalDate
.
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.
Result:
2016-08-06
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 LocalDate.of
.
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.
Result:
2016-01-01
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 LocalTime.now
.
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.
Result:
01:18:11.779
I'll use 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.
Result:
14:00:45
Finally, I'll show how to use the LocalDateTime
class.
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.
Result:
2016-08-06T01:30:46.625
2016-01-01T14:00:45
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 LocalDate
, LocalTime
, and 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 DateTmeFormatter
class.
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.
Result:
2016-08-06
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 LocalTime
class.
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.
Result:
2016-08-09
20:37:11.535
2016-08-09T20:37:11.538
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.
Result:
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.
Result:
8/9/16
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 Java.util
.
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 fr_short
and fr_long
.
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.
Result:
09/08/16
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 TemporalField
.
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 ChronoField.year
.
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 currentDT
.
And now when I run my code, I get completely customized format.
Result:
222||9||2016
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 main
method.
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.
Result:
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
class.
A 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.
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 ZoneId.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.
Result:
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 Java.util
.
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.
Result:
When I run that code, I see all of the available strings.
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Africa/Nairobi
America/Marigot
Asia/Aqtau
Pacific/Kwajalein
America/El_Salvador
Asia/Pontianak
Africa/Cairo
Pacific/Pago_Pago
Africa/Mbabane
Asia/Kuching
Pacific/Honolulu
Pacific/Rarotonga
America/Guatemala
Australia/Hobart
Europe/London
America/Belize
America/Panama
Asia/Chungking
America/Managua
America/Indiana/Petersburg
Asia/Yerevan
Europe/Brussels
GMT
Europe/Warsaw
America/Chicago
Asia/Kashgar
Chile/Continental
Pacific/Yap
CET
Etc/GMT-1
Etc/GMT-0
Europe/Jersey
America/Tegucigalpa
Etc/GMT-5
Europe/Istanbul
America/Eirunepe
Etc/GMT-4
America/Miquelon
Etc/GMT-3
Europe/Luxembourg
Etc/GMT-2
Etc/GMT-9
America/Argentina/Catamarca
Etc/GMT-8
Etc/GMT-7
.................
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 println()
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:
Result:
Europe/London
Conclusion:
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.