Guideline to basic java.time date/time classes
When and how to use which of the time classes in the java.time package: An attempt to provide some useful and easily understandable help. Find your way through the time classes even if you're scared to read their lengthy documentation!
Note: the following explanations don’t take into account the accuracy or synchronicity of clocks. That is a much more complex topic. Also, discussing leap seconds is out of scope as it should not matter here.
General remarks about storing and using time values
When thinking about which of the following classes to use as a date/time value for a particular purpose, it helps to ignore how the value looks when converted into a character string representation. Rather, keep the concept in mind and consider the semantics.
When you’re in need of storing a date (day level granularity, no need for hours or shorter divisions), don’t make the mistake of using a class that can store higher precision and set the hour and smaller parts to zero. Use a
LocalDate
that can only store day precision to make the purpose clear and self-documenting. You then don’t risk your mis-used datetime value to be accidentally subject to timezone conversions: A value of midnight, July 14th, might suddenly become 17 hours into July 13th, changing your date.Similar to the previous advice, when storing the end of an interval, try not to use the “last” value in the interval (compatible with a comparison for “less-or-equals”,
<=
), but the first value “after” the interval (compatible with a comparison for “strictly less”,<
).That way, your comparisons will have a uniform
a <= t < b
form and work independently on the resolution of the clock. If you have a milliseconds resolution in your clock, then the “one hour interval” from 16:00:00 to 16:59:59 is nearly one second shorter than an hour.
You will reduce the complexity to reason about your code by avoiding such hidden implicit dependencies influencing the correctness, and by using a uniform comparison approach.Unfortunately, Java’s time classes only have the
isBefore()
andisAfter()
methods to compare time values. If you want to negatea.isBefore(b)
(to express “a is at or after b”), don’t be tempted to usea.isAfter(b)
or (equivalently)b.isBefore(a)
: both are wrong and different from the correct expression!a.isBefore(b)
, even though reading your code will be a bit harder due to the negation. You could write some auxiliary methods to mend that. Correctness should come first!
The last two advices, taken together, are illustrated by the following:
Instead of writing
Instant start = Instant.parse("2023-03-27T13:00:00Z");
Instant end = Instant.parse("2023-03-27T13:59:59Z"); // <- supposedly "latest" point in time inside the interval
Instant now = Instant.now();
if (!now.isBefore(start) && !now.isAfter(end)) { // start <= now <= end
// ...
}
you should write
Instant start = Instant.parse("2023-03-27T13:00:00Z");
Instant end = Instant.parse("2023-03-27T14:00:00Z"); // <- correct earliest point in time after the interval
Instant now = Instant.now();
if (!now.isBefore(start) && now.isBefore(end)) { // start <= now < end
// ...
}
Discussion of classes that store date and/or time
Class java.time.Instant
An Instant
represents a point on the physical timeline. You can always tell if one Instant
lies before or after another Instant
, or if they are identical.
Possible use
Instant
is usually the best choice for concepts like log entries or events: concepts where it is important to know in what order they happen.
Possible text representation
The character string representation of Instant
is usually given by the point in time as observed in UTC, written as defined in the ISO 8601 format. Sticking to that “universal time”, and including the corresponding Z
offset indicator, reduces possible confusion. For example:
2023-03-27T16:38:53.98Z
Class java.time.LocalDateTime
A LocalDateTime
is the date and time you can observe on a mantelpiece in a movie when there’s a clock and a calendar sitting on it. It represents a single point on the timeline, but you cannot tell which one.
You cannot infer a point on the timeline from a LocalDateTime
, because you would additionally need information about the time zone or offset where the fireplace is located. A fireplace in Boston, MA., and one in Ljubljana, Slovenia, showing the same LocalDateTime
, will mean different Instant
s on the timeline.
The other way around, it is similar: you cannot infer a LocalDateTime
from an Instant
, because you need to know the time zone or offset.
You can consider a LocalDateTime
to be an aggregation of a LocalDate
and a LocalTime
(see below). As such it contains information about year, month, day, hour, minutes, seconds, and fraction of a second. It is able to hold the fraction of a second information up to nanosecond precision.
It makes only sense to compare two LocalDateTime
values when they are happening in the same time zone/offset.
Possible use
LocalDateTime
is of lesser practical use. You can use one when arranging a rendezvous, if the place is known and not too far away, and you're in a romantic mood; but technically, an Instant
, ZonedDateTime
or OffsetDateTime
would be better for this.
In my experience, a LocalDateTime
is mostly used for constructing a ZonedDateTime
or an OffsetDateTime
(see below). It’s also what the PostgreSQL JDBC driver considers a timestamp without time zone
SQL value when sending to or reading from the database.
Possible text representation
A character string representation of LocalDateTime
does of course not contain time zone/offset information, for example
2023-03-27T18:38:53.98
Class java.time.LocalDate
A LocalDate
is what you read when you only observe the calendar on the mantelpiece. Consisting of year, month, and day, with a resolution no finer than days. It does not even make sense to try and infer a point on the timeline for it.
Possible use
LocalDate
is a good choice when you need to remember the birthday (including the year) of a person, or the date when a software manufacturer will stop supporting a product.
Possible text representation
A character string representation of LocalDate
in ISO 8601 format looks like
2023-03-27
Class java.time.LocalTime
A LocalTime
represents the time of a day. It consists of hour (0–23), minute (0–59), second (0–59) and a fraction of a second. It is able to hold the fraction of seconds information up to nanosecond precision.
Possible use
You can use LocalTime
to express the time of daily recurring events like “every day at 6 o’clock I need to milk the cows.” It’s also the data to use when specifying that a daily database maintenance job should run at 01:15:00, when all users are sleeping, in case you're lucky and have a locally confined user base.
Possible text representation
A character string representation of LocalTime
in ISO 8601 format is for example
T06:00:00
or just
06:00:00
Class java.time.OffsetDateTime
An OffsetDateTime
holds the same information as LocalDateTime
does and extends it with information about how much this value differs from the value that would be shown at the same point on the timeline on a mantel clock at Greenwich, England (really meaning UTC). This additional information is called “offset” and is represented by the class java.time.ZoneOffset
.
The offset is usually a multiple of hours, sometimes half an hour, even though it can be stored with seconds precision. The offset is positive if the local time has a higher value (looks like it’s later) than at UTC, and negative if it has a lower value (looks like it’s earlier) than at UTC. Further, the offset has nothing to do with daylight savings time changes (see ZonedDateTime
). It is a fixed amount of time attached to the LocalDateTime
which you can subtract to calculate the corresponding UTC time, and thus, the Instant
.
An OffsetDateTime
represents a single point on the timeline and also defines which one. In other words, you can infer the Instant
that corresponds to the OffsetDateTime
. The other way around, you additionally need the ZoneOffset
to construct an OffsetDateTime
from an Instant
.
Possible use
Not so much for real world use cases, rather for technical purpose like communicating to a database. For example, you should use it when you want to send a timestamp with time zone
SQL value to a PostgreSQL database using JDBC.
Possible text representation
A character string representation of OffsetDateTime
in ISO 8601 format is for example
2023-03-28T12:16:28.686074+02:00
Class java.time.ZonedDateTime
A ZonedDateTime
holds information as LocalDateTime
does, together with information about the timezone where it happens. The time zone is represented by class java.time.ZoneId
.
In contrast to ZoneOffset
, which contains a fixed offset to UTC, a ZoneId
contains the name of a timezone, like "Europe/Berlin"
. This brings politics into the game, because it is a reference to the laws telling in what part of the world which time offset is effective at a certain point in time. This political information is collected in the tzdata database, previously also known as Olson database, after its founding contributor. This database tries to collect all information (including historical) about the rules when the time offset changed/changes in which part of the world. For example, it will resolve the ZoneId
of "Europe/Berlin"
at a LocalDateTime
of "2023-03-28T12:11"
to an offset of 2 hours, because daylight saving time was effective at that time in Germany.
As OffsetDateTime
, ZonedDateTime
represents a single point in time (there’s a rule which one is chosen in case of ambiguity during the daylight saving time switchback). You can always determine the Instant
corresponding to a ZonedDateTime
. The other way around, you additionally need the ZoneId
to construct a ZonedDateTime
from an Instant
.
Similarly, you can always convert a ZonedDateTime
to an OffsetDateTime
, but for the inverse way, you need the ZoneId
.
Be aware, however, that the conversion to/from Instant
(or OffsetDateTime
) can depend on when you do the conversion, because political rules change when new laws are passed, and the tzdata database changes over time to reflect that. So it is wise to not always immediately convert a ZonedDateTime
to an Instant
and use that, but only convert when it is needed.
In other words, if you want to meet with someone in Paris at 10 o’clock on the 25th of March 2033, you should check the daylight savings time rules a short time before that. Otherwise, you risk being an hour late in case the French legislative chooses to switch to daylight savings time a week earlier than you originally thought.
Possible use
ZonedDateTime
is the preferred data to store a point on the timeline when you need to know the timezone and the local date and time at that zone and instant, for example, appointments in a calendar.
Possible text representation
There is no well-established convention for representing a ZonedDateTime
as a character string. Java’s toString()
method results for example in
2023-03-28T12:16:28.686074+02:00[Europe/Berlin]
Be aware that the offset information in the above string ("+02:00"
) is not stored with the ZonedDateTime
, it is determined from the tzdata database at the time the string representation is created.
Published by Dirk L.
Visit author page