A Mostly Definitive Guide To Representing Date And Time In Modern Java
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:
- The number of seconds from midnight, Jan 1, 1970 UTC (AKA the Unix Epoch)
- 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.Date
2 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?