Dates & Time in Modern Java
Why do we have a hard time programming with dates and time?
The answers to this question are numerous, but what we need in order to handle dates and time clearly is what in Star Trek is called “Stardate”.
What a “stardate” provides is a way to refer to events in time independently of the location in space where these events are referred to. Without such a location-independent reference, we tend to confuse the actual moment where an event occurred with its description through days, hours, and — the most harming part — time zones.
How to properly handle time in software has always been a challenge. Here are only a few infamous bugs that occurred due to bad handling of time:
- NASA Deep Impact probe: “Communication was unexpectedly lost” on August 11, 2013, 00:38:49 due to overflow in time measurement.
- US Army counter missile: Rounding error due to bad representation of time caused a hostile missile get through the defensive line.
- iOS 11.1.2 crash loop: Daylight Saving Time causes reboot loop because of “missing” hour.
The Good News
The good news is that modern Java (8 or later) gives us the tools to properly handle time concepts. The Date
class before Java 8 was so confusing that, after being introduced, it was already reworked in Java 1.1. In 2005, the well-known third-party JodaTime introduced clear concepts and APIs for Java 5 to 7. In Java 8, the java.time
package was openly inspired by JodaTime though totally re-architectured. (By the way, joda.org recommends migrating to java.time
if you are using Java 8 or later.)
Instants vs. Calendar Points
One of the key contributions of the Java 8 model is to separate instants from all sorts of calendar points. An instant is a moment in life, by nature independent of any location. It is often referred to as “a point on the timeline”. On the other hand, a calendar introduces all the usual concepts of human representation of time, from minutes to centuries. A calendar point is a combination of calendar concepts. Some of these combinations have names in the Java 8 model, like local dates such as March 24th, 2020, or local times such as 16:30.
While instants refer to a precise and unique point on the timeline, a calendar point is an element of discussion that refers to a more or less precise span in the representation of time of the persons who take part in the discussion — typically users of a software system. Take a precise enough calendar point, add a time zone to it, and you can translate it into an instant; and vice versa.
How does Java 8 handle instants?
An instant is a point on the timeline and is measured, as a Unix time (aka Posix time, aka Unix/Posix timestamps), by the number of seconds (or milliseconds) since Jan 1st, 1970 UTC.
There are two ways to handle instants with Java 8:
- Long integers, as returned by the good old
System.currentTimeMillis()
, are a good way to manage timestamps if you just need to store a timestamp and you don’t need to perform operations on timestamps beyond comparison or simple differences. - The
java.time.Instant
class in Java 8 encapsulates a unique timestamp and offers a collection of methods to create, handle, and compute on instants. Instants are typically built with.now()
or with.ofEpochMillis(long)
.
How does Java 8 handle calendar points?
The Java 8 model recognizes that in many cases, we want to talk about a date or a time without it relating immediately to an actual point on the timeline. Consider the following sentences: “France’s national day is July 14.” “All our shops across the world open at 9am and close at 6pm.” “This certification expires on Dec 31, 2021.” In all these sentences, the dates and times are expressed independently of any location. Symmetrically, they require a location to be mapped onto actual instants.
Java 8 provides three central classes (and many more useful enums and types) to express, handle, and perform computations on dates and times that do not correspond to a given instant:
LocalDate
: a date without a time, like April 28th, 2020.LocalTime
: a time without date, like 15:13 (or 15:13.30.123456789).LocalDateTime
: a date and time without a time zone, like April 28th, 2020 at 15:13.
The link between instants and dates, in real life
The human-readable representation of instants requires calendars and time zones.
There are different calendars around the world: the one probably most commonly used has been standardized as the ISO 8601 calendar, and introduces minutes, days, months and years as follows:
- 1 minute = 60 seconds. 1 hour= 60 minutes.
- 1 day = 24 hours = 86,400 seconds (modulo leap seconds—see below).
1 month = 28 to 31 days. 1 year= 12 months = 365 or 366 days. - 1 day = 1 rotation of the Earth. 1 year = 1 revolution.
The last two bullets above illustrate that there are two ways of defining days and years. The importance of taking into account both the mathematical definition and the astronomical one is because everything just works better if the two match.
Let’s just take a side step and talk about a few different standards to measure time:
- TAI (Temps Atomique International) is a precise measure of seconds as defined by the International System of Units.
- UT1 (Universal Time 1) relies on the observation of the rotation of the Earth to define time using the average duration of one day. It tends to be somewhat slower than TAI.
- UTC (“Coordinated Universal Time”) increases at the same pace as TAI and is adjusted so that it does not differ from UT1 by more than 0.9s, by adding a leap second when needed. To date, it has been adjusted by 34 seconds.
- Unix time counts seconds from Jan 1st, 1970 UTC, without the leap seconds. It is the job of the instant-to-UTC converter to take leap seconds into account.
Time zones
A time zone is a set of rules for a region in the world in which it has been decided that the time is the same:
- UTC is the most neutral translation of instants in the ISO 8601 calendar. It is the primary time standard with respect to which time zones are defined.
- A time-zone offset is the difference (in hours and minutes) from UTC. For example on January 1st, 2020: Paris = +01:00, Rio de Janeiro = -04:00, Hong Kong = +08:00.
- A time zone is a function that provides an offset for a given date. It considers the time zone definition and daylight-saving time rules on the given date. It is denoted by a zone ID, such as Europe/Paris, America/Rio de Janeiro, Asia/Hong Kong. Time zones are political and can change over time.
Converting between UTC and/or time zones is then “just” a matter of adding the offset provided by the definition of the time zone of interest, at the moment of interest.
How does Java 8 handle dates?
Now that we have addressed instants and calendar points separately, addressing human-readable dates with a time zone seems to become seizable.
For this, Java 8 introduces two classes:
ZoneId
implements the IDs of time zones. For example:ZoneId.of("Europe/Paris")
.ZonedDateTime
implements the human-readable representation of an instant in the ISO 8601 calendar, in a given time zone. For example:Instant.now().atZone(zoneId)
.
The ZonedDateTime
class provides lots of accessors such as .getMinute()
or .getMonth()
or .getYear()
to retrieve the individual elements of the representation of a date.
Conversion from/to a string is provided by .format(DateTimeFormatter)
and .parse(String, DateTimeFormatter)
. Conversion to another time zone is implemented by .withZoneSameInstant(ZoneId)
.
Importantly, there are methods that provide a safe way to perform operations on dates (and simplify programmers life!), such as .plusSeconds(long)
or .minusDays(long)
.
Good practices
You may often read the following recommendation:
“Many applications can be written only using LocalDate, LocalTime, and Instant, with the time zone added at the UI layer.”
I share this point of view, even if application development in real-life brings some questions for this statement, such as:
- Which time zone? The browser’s time zone or the back-end time zone? And if the back-end’s: where does it come from? Does it depend on the user, on some other data? (You probably don’t want to depend on the time zone in which the server that hosts your application is running…)
- In addition to the UI layer, time zone considerations will happen in the persistence layer. The representation of dates in databases is not always as clean as the Java 8 model. And the time zone of the database server should not have an influence.
- Similar issues will happen when integrating with external systems, or even external file formats (who said “Excel”?).
These questions, and others, will arise. Still, the reference representation in the application logic should usually be instants (or timestamps), or local date/time. Beyond implementation, I found this to be a guideline for a clearer design.
Moreover, it is important to understand and use the dates and time concepts and tools that Java 8 offers in order to correctly handle time in our software, and avoid errors that would result in undecipherable bugs, unpaid technical debt, and developer headaches.
This article was co-authored with my colleague Alexa Salles. Many thanks to her for her contribution.