Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Java SE 8 New Features Tour: Calculating timespans with the New DateTime API

4.50/5 (2 votes)
14 Aug 2016CPOL20 min read 9.2K  
How to calculate timespans with the New DateTime API

Cleaner, readable, and powerful coding with Java SE 8 New DateTime API JSR 310 .....

Java SE 8, JSR 310
Java SE 8, JSR 310

In this article of the “Java SE 8 new features tour” series, we will dig deep into the explanation, and explore the code of JSR 310 specification, on 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 dived into the explanation and exploration on how to traverse collections with streams, creating streams from collections and arrays, and finally aggregating stream values.

The source code is hosted on my Github account: clone it from here.

Table of Contents

  1. Calculating time spans with Instant and Duration
  2. Representing Date and Time Values
  3. Formatting Date and Time Values
  4. 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.

How It Works

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 above. 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.

Java
package eg.com.tm.java8.features.datetime.InstDuration;
 
import java.time.Instant;
import static java.time.Instant.now;
/**
 *
 * @author mohamed_taman
 */
public class InstantAndDuration {
 
    public static void main(String[] args) {
 
        Instant start = now();
        System.out.println(start);
    }
}

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 its 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.

Java
public static void main(String[] args) {
 
       Instant start = now();
       System.out.println(start);
 
       Instant end = now();
       System.out.println(end);
 }

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.

Java
public static void main(String[] args) {
 
       Instant start = now();
       Instant end = now();
 
       System.out.println(start);
       System.out.println(end);
 }

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.

Java
Instant start = now();
 
Instant end = now();
System.out.println(start);
System.out.println(end);
      
Duration elapsed = Duration.between(start, end);
System.out.println("Elapsed: "+ elapsed);

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.

Java
public static void main(String[] args) throws InterruptedException {
 
     Instant start = now();
      
     Thread.sleep(1000);
      
     Instant end = now();
     System.out.println(start);
     System.out.println(end);
      
     Duration elapsed = Duration.between(start, end);
     System.out.println("Elapsed: "+ elapsed);

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.

Java
public static void main(String[] args) throws InterruptedException {
 
     Instant start = now();
     System.out.println(start);
      
     Thread.sleep(1000);
      
     Instant end = now();
     System.out.println(end);
      
     Duration elapsed = Duration.between(start, end);
     System.out.println("Elapsed: "+ elapsed);
   }

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.

Java
Duration elapsed = Duration.between(start, end);
   System.out.println("Elapsed: "+ elapsed.toMillis() +" milliseconds");

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. Let's 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.

How It Works

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.

Java
package eg.com.tm.java8.features.datetime.localdt;
 
import static java.time.LocalDate.now;
import java.time.LocalDate;
/**
 *
 * @author mohamed_taman
 */
public class LocalDateAndTime {
 
    public static void main(String[] args) throws InterruptedException {
 
        LocalDate currentDate = now();
        System.out.println(currentDate);
    }
}

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 put the new specific date. And when I save and run that, I get the value that I put in, January 1st, 2000.

Java
LocalDate specificDate = LocalDate.of(2016,1,1);
System.out.println(specificDate);

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.

Java
LocalTime currentTime = LocalTime.now();
System.out.println(currentTime);

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.

Java
LocalTime specificTime = LocalTime.of(14,0,45);
System.out.println(specificTime);

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.

Java
LocalDateTime currentDT = LocalDateTime.now();
System.out.println(currentDT);
 
LocalDateTime specificDT = LocalDateTime.of(specificDate, specificTime);
System.out.println(specificDT);

Result

2016-08-06T01:30:46.625
2016-01-01T14:00:45

Conclusion

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.

How It Works

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 DateTimeFormatter 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.

Java
package eg.com.tm.java8.features.datetime.format;
 
import static java.time.LocalDate.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
 *
 * @author mohamed_taman
 */
public class DateTimeFormater {
 
    public static void main(String[] args) {
 
        LocalDate currentDate = LocalDate.now();
        DateTimeFormatter df = DateTimeFormatter.ISO_DATE;
 
        System.out.println(df.format(currentDate));
    }
}

Result

2016-08-06

I'll take the 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.

Java
public static void main(String[] args) {
 
    LocalDate currentDate = LocalDate.now();
    DateTimeFormatter df = DateTimeFormatter.ISO_DATE;
    System.out.println(df.format(currentDate));
     
    LocalTime currentTime = LocalTime.now();
    DateTimeFormatter dt = DateTimeFormatter.ISO_TIME;
    System.out.println(dt.format(currentTime));
     
    LocalDateTime currentDT = LocalDateTime.now();
    DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
    System.out.println(dtf.format(currentDT));
}

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.

Java
DateTimeFormatter f_long = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
System.out.println(f_long.format(currentDT));

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.

Java
DateTimeFormatter f_short = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
System.out.println(f_short.format(currentDT));

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 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 the 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.

Java
String fr_short = f_short.withLocale(Locale.FRENCH).format(currentDT);
   String fr_long  = f_long.withLocale(Locale.FRENCH).format(currentDT);
    
   System.out.println(fr_short);
   System.out.println(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.

Java
DateTimeFormatterBuilder b = new DateTimeFormatterBuilder()
               .appendValue(ChronoField.DAY_OF_YEAR)
               .appendLiteral("||")
               .appendValue(ChronoField.DAY_OF_MONTH)
               .appendLiteral("||")
               .appendValue(ChronoField.YEAR);
        
DateTimeFormatter f = b.toFormatter();
System.out.println(f.format(currentDT));

And now, when I run my code, I get completely customized format.

Result

222||9||2016

Conclusion

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.

How It Works

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.

Java
DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
LocalDateTime currentDT = LocalDateTime.now();
System.out.println(dtf.format(currentDT));

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.

Java
ZonedDateTime gmt = ZonedDateTime.now(ZoneId.of("GMT+0"));
System.out.println(dtf.format(gmt));

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. Let's 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 its 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.

Java
ZonedDateTime ny = ZonedDateTime.now(ZoneId.of("America/New_York"));
      System.out.println(dtf.format(ny));

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.

Java
Set<string> zones = ZoneId.getAvailableZoneIds();
zones.forEach(z -> System.out.println(z));</string>

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.

Java
Set<string> zones = ZoneId.getAvailableZoneIds();
Predicate<string> condition = str -> str.contains("London");
zones.forEach(z -> {
    if(condition.test(z))
    System.out.println(z);
});</string></string>

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 offset 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.

Resources

  1. The Java Tutorials, Trail: Date Time
  2. The LocalDate API
  3. JSR 310: Date and Time API
  4. JSR 337: Java SE 8 Release Contents
  5. OpenJDK website
  6. 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.

History

  • 14th August, 2016: Initial version

Image 2

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)