Working with Java dates and times used be a trying exercise. Things improved dramatically in Java 81 with the introduction of a host of new classes, but I usually end up having to re-learn them every time I have to deal with dates. Most sources will tell you literally what the classes do, but not their intended use, which is not always obvious. There also seems to be a good amount of misleading information, particularly when dealing with time zones. Ideally, this can serve as more readable and opinionated quick reference guide. Classes are listed in rough order, from most often used to most niche.

The Major Players

Instant

An Instant represents a single point in time. Under the covers, it is represented by 2 big numbers:

  1. The number of seconds from midnight, Jan 1, 1970 UTC (AKA the Unix Epoch)
  2. The number of nanoseconds since then

But you don’t really need to worry about that. Just think of it as a timestamp with nanosecond accuracy. Given an instance of an Instant, you can figure out pretty much anything you would need to know. If you are storing when an event occurred, this is the class you want. It’s UTC, and your back end and database should be talking in UTC.

    // create an instant from the system clock
    Instant thisInstant = Instant.now();
    System.out.println(thisInstant);
    // 2018-06-03T23:24:16.950Z (the Z at the end means it is in UTC, or Zulu, time

    // create an instant from a string
    Instant myInstant = Instant.parse("2007-12-03T10:15:30.00Z");
    DateTimeFormatter formatter = ISO_DATE_TIME.withZone(ZoneId.of("-05:00"));
    System.out.println(formatter.format(myInstant));
    // 2007-12-03T05:15:30-05:00 (the -05:00 is the offset, EST is 5 hours behind UTC)

LocalDate

LocalDate would be probably be just called Date, if not for the dreaded java.util.Date and the even more sinister java.sql.Date2 already hogging the name. It’s a bit misleadingly named, as there is no ZonedDate that would be analogous to LocalDateTime’s ZonedDateTime, because, well, you can’t have a timezone without a time.

So LocalDate is just a date. Day, Month, Year, that’s it. Use it to represent things that have a date but no specific time, like a birthday, holiday, or employment start date. No evil timezones to worry about, nice and friendly and simple.

    LocalDate today = LocalDate.now();
    System.out.println(today);
    // 2018-06-03

DateTimeFormatter

DateTimeFormatter is your one stop shop for converting to a String (using .format())) and parsing from a String (using .parse()). Nothing tricky here. Replaces the infamous SimpleDateFormat.

The Supporting Cast

LocalDateTime

I’ve seen this one trip up a lot of people, myself included. LocalDateTime contains ONLY the date and time, no timezone. If you do a LocalDateTime.now() at the exact same time on one machine in New York, and another in LA, you will get two different times. Which makes this totally useless when tracking events. The rather narrow use case here is for things that occur at the same local time in different time zones. For instance, New Years this year will be celebrated 1/1/2019, at local midnight. Each timezone celebrates at a different Instant, but at the same LocalDateTime

An observation: Instant and LocalDateTime both have .atZone() methods that return a ZonedDateTime. But the behavior is totally different. For Instant, the time is translated from UTC to the given time zone. For LocalDateTime the zone is merely tacked on, there is nothing to translate from.

    Instant myInstant = Instant.now();
    System.out.println(myInstant.atZone(ZoneId.of("-01:00")));
    System.out.println(myInstant.atZone(ZoneId.of("-02:00")));
    System.out.println(myInstant.atZone(ZoneId.of("-03:00")));

    // 2018-06-03T23:23:20.029064-01:00 (time changes as timezone changes)
    // 2018-06-03T22:23:20.029064-02:00
    // 2018-06-03T21:23:20.029064-03:00
    
    LocalDateTime myLdt = LocalDateTime.now();
    System.out.println(myLdt.atZone(ZoneId.of("-01:00")));
    System.out.println(myLdt.atZone(ZoneId.of("-02:00")));
    System.out.println(myLdt.atZone(ZoneId.of("-03:00")));
    
    // 2018-06-03T20:23:20.055610-01:00 (time does not change)
    // 2018-06-03T20:23:20.055610-02:00
    // 2018-06-03T20:23:20.055610-03:00 

My main usage of LocalDateTime has been when I’m pulling data into the system that has timestamps without zone information. The problem is, the time part of that timestamp is not very useful without a zone. If you want to figure out the actual time something happened, you are going to need to “manually” add a timezone like above. If I’m the one creating the timestamp, it’s going to be an Instant.

ZonedDateTime

ZonedDateTime is basically an Instant or a LocalDateTime plus a timezone, AKA ZoneId. It’s usually the intermediate step between an Instant and a formatted String.

LocalTime

Just a clock time with no other context. Would be useful to represent something like a daily alarm clock time. Probably most commonly used when pulled out of other representations.

Bit Players

Duration

Duration is a quantity of time, something like “5 hours” or “7 days, 33 minutes, 22 seconds”. A common usage is to measure the time between two events. One thing to consider is that you can’t use a duration to compare a LocalDateTime to something with a time zone.

    Instant startInstant = Instant.parse("2007-12-03T10:15:30.00Z");
    Instant endInstant = Instant.parse("2007-12-07T10:09:33.00Z");

    Duration durationFromInstants = Duration.between(startInstant, endInstant);
    System.out.println(durationFromInstants);
    // PT95H54M3S (the ISO-8601 representation - Period, Time, 95 hours, 54 Minutes, 3 seconds)

    ZonedDateTime zdt = ZonedDateTime.now();
    Duration fromInstantToZdt = Duration.between(startInstant, zdt);
    System.out.println(fromInstantToZdt);
    // PT92066H20.779689S
    
    LocalDateTime startLdt = LocalDateTime.parse("2008-08-03T10:15:30.00");
    LocalDateTime endLdt = LocalDateTime.parse("2008-09-03T10:02:30.55");

    // Duration mixedDuration = Duration.between(startInstant, endLdt);
    // causes exception, can't mix local and zoned time

    Duration durationFromLdt = Duration.between(startLdt, endLdt);
    System.out.println(durationFromLdt);
    // PT743H47M0.55S

Period

Period is similar to Duration but it only worries about dates. Thus, if you are trying to do a .between(), it only works with LocalDate

    LocalDate startLd = LocalDate.parse("2018-06-03");
    LocalDate endLd = LocalDate.parse("2018-07-15");

    Period myPeriod = Period.between(startLd, endLd);
    System.out.println(myPeriod);
    // P1M12D

Most of the time, you’ll want to use Period when dealing with LocalDate and Duration when dealing with Instant, LocalDateTime, and ZonedDateTime.

1 If your project is stuck with Java 7 or earlier, use JodaTime

2 How many thousands of developer-hours have been lost due to accidental imports of java.sql.date?