Thursday, 20 November 2014

Do the new Java 8 Date and Time objects make 3rd party date libraries redundant?

This is the first of two blog posts which are a follow up to Virtual Pair Programmers’ popular Java Fundamentals training course. This course was written with Java 7, and while everything in the course is still valid for Java 8, I thought a blog post about dates and times was worthwhile.

There are other changes in Java 8, although I’d say that these don’t affect the fundamentals. The biggest change is the introduction of lambda expressions, and I’m currently working on an “Advanced Java” course for Virtual Pair Programmers, which will cover this amongst other topics…. more on that later!

In the Java Fundamentals course, we say that the Date library in Java has always been a messy affair, and that while the GregorianCalendar object can be useful, if you need to do any kind of date manipulation in Java, you probably will be using an external library. In the course we JodaTime which seems to be the go-to library for most developers.

However Java 8 introduces some new date and time functionality, through the new java.time library, so I thought I’d take a look and see whether this might now make JodaTime redundant. In this post we’ll look at how to manipulate dates and times in Java 8, and in the following post we’ll explore in more detail some of the different objects within the Java 8 date and time libraries.

Manipulating Dates with java.time


So our starting point for this post is the most common operation I find myself writing code for when it comes to working with dates… and that is comparing two dates to see if they’re the same. When I teach this to new programmers, I sometimes use the following example, as a way to practice date manipulation:

Imagine that there’s a secret date that we’re trying to find out. We know it’s between 1st January 2000 and 31st December 2020.



To find out what the secret date is, we can only ask the question in the format “how does it compare to, 16th November 2012 at 6.15am”?, and we’ll get the answer “the secret date is before, after or matches that date”.



The implementation of this comparison question in JodaTime is really simple – we use the DateTime object to store the date, and use the compareTo function to compare 2 dates.  This simple method does the job:

 public int compareDate(DateTime sampleDate) {
  return sampleDate.compareTo( new DateTime(2012, 11, 16,6,15));
 }


The method takes a date and time (sampleDate) as a parameter and returns a +1 if the date and time we supply is after the date we’re looking for (the secret date), a 0 if it matches, or a -1 if the date and time we supply is before the date we’re looking for.

So the work is, given that we can only ask this question, how do we find out what the secret date is?

To find the answer, we play a game. We know that 1st Jan 2000 is before the secret date, and 31st December 2020 is after it. So let’s pick a date in the middle – say 1st January 2010, and ask the question for that date. The answer comes back “the secret date is after 1st January 2010”.



Now we know the date lies between 1st January 2010 and 31st December 2020. So we’ll try a date between those two – perhaps 1st January 2015.



We get the answer that the secret date is before 1st January 2015, so we now know it falls between 1st January 2010 and 1st January 2015.



We can keep repeating this - choosing a date between our known upper and lower limits, and revising one of those limits each time until we find the date. Here’s the code that does this and the output it produces:

import org.joda.time.DateTime;

public class Main {

 public static int compareDate(DateTime sampleDate) {
  return sampleDate.compareTo( new DateTime(2012, 11, 16,06,15));
 }
 
 public static void main(String[] args) {

  DateTime lower = new DateTime(2000,1,01,0,0);
  DateTime higher = new DateTime(2020,12,31,23,59);

  int result = 100;
  int steps = 0;
  DateTime foundDate = new DateTime(); 
  
  while (result != 0) {
   int difference = (int) ((higher.getMillis() - lower.getMillis()) / 2000);
   foundDate = lower.plusSeconds(difference); 
   result = compareDate(foundDate);
  
   if (result == -1) {
    lower = foundDate;
   }
   else if (result == 1) {
    higher = foundDate;
   }
   steps++;
  }
  
  System.out.println("Found date " + foundDate + " in " + steps + " steps.");
 }

}

Found date 2012-11-16T06:15:00Z in 29 steps. 

 I think this is a useful exercise for students new to JodaTime as it shows us:
  • how to compare two dates (using compareTo), 
  • how to determine the length of time between two dates (using getMillis), and 
  • how to add time to a date (using plusSeconds). 

In a full lesson we would explore the other options to add days or weeks rather than seconds, but you get the idea. We cover a number of the main things that people tend to want to do with dates in 1 simple exercise.

 So let’s now suppose we have Java 8 and are not allowed to use JodaTime – does the new Java 8 Time library allow us to achieve this task?

 Well the good news is that the answer is yes, and in fact the code is almost identical to what we have above. The two key objects we need are:
  • java.time.Instant – this represents a Date and Time* – we'll consider that the equivalent to our JodaTime DateTime object, and 
  • java.time.Duration – this static object has a between() method which can give us the difference between two Instants, and a toMillis() function which converts that difference to milliseconds… 

 Here’s the new code… the output is identical to the JodaTime version!

import java.time.Duration;
import java.time.Instant;

public class Main {

 public static int compareDate(Instant sampleDate) {
  return sampleDate.compareTo( Instant.parse("2012-11-16T06:15:00Z"));
 }
 
 public static void main(String[] args) {

  Instant lower = Instant.parse("2000-01-01T00:00:00Z");
  Instant higher = Instant.parse("2020-12-31T23:59:00Z");

  int result = 100;
  int steps = 0;
  Instant foundDate = Instant.now();
  
  while (result != 0) {
   
   int difference = (int) (Duration.between(lower, higher).toMillis() / 2000);
   foundDate = lower.plusSeconds(difference); 
   result = compareDate(foundDate);
  
   if (result == -1) {
    lower = foundDate;
   }
   else if (result == 1) {
    higher = foundDate;
   }
   steps++;
  }
  
  System.out.println("Found date " + foundDate + " in " + steps + " steps.");
 }
}

On the basis that you understand the JodaTime version, the new Java 8 version is straight-forward.

 So it seems that for many date manipulation tasks the new Java 8 objects will meet our needs, and can be considered a success. JodaTime can do much more than just this, so it won’t be redundant just yet, but I think it will be an extra overhead we won’t need for basic date manipulation.

 What we have seen so far is 2 of the Java 8 classes from the java.time library – the Instant and the Duration class. There are other useful classes in this library however, and we’ll look at some of these in the next post.



 * To say that an Instant represents a Date and Time is not the full story - it's got a very precise definition and we'll be exploring that in the next blog post.

2 comments:

  1. This code:
    int difference = (int) (Duration.between(lower, higher).toMillis() / 2000);
    foundDate = lower.plusSeconds(difference);

    can be replaced by:
    foundDate = lower.plus(Duration.between(lower, higher).dividedBy(2));

    In general, if you are calling methods to get millis/seconds and working with them you are doing it wrong in both Joda-Time and java.time (JSR-310). Use the objects for manipulation, not the low level numbers.

    ReplyDelete
    Replies
    1. Good point - thanks for that Stephen... Although what I have done is technically wrong, I personally like it because I like to query the difference and understand it - but I agree what you suggest is neater and more compliant with the specification

      Delete