I'm trying to calculate the difference between an epoch miliseconds and the currenttime on local machine. I was hoping to use idiomatic java.time
api with:
val eventTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochinMilis), ZoneId.systemDefault())
val now = LocalDateTime.now.atZone(ZoneId.systemDefault())
Duration.between(now, eventTime).toHours
However it produces:
java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: 2023-06-26T22:26:36.917 of type java.time.LocalDateTime
at java.base/java.time.ZonedDateTime.from(ZonedDateTime.java:566)
at java.base/java.time.ZonedDateTime.until(ZonedDateTime.java:2130)
at java.base/java.time.Duration.between(Duration.java:490)
With
now = LocalDateTime.now.atZone(ZoneId.systemDefault())
(epochinMilis/1000 - now.toEpochSecond()) / 3600
I'm getting what i want, but I would like to know what goes wrong when using the designated(?) api?
Your problem stems from comparing apples and oranges. In this case, you are trying to compare a LocalDateTime
and a ZonedDateTime
. Those two are very different things and cannot be compared directly.
A ZonedDateTime
describes a specific point in time, expressed in the terms of a certain time zone. It is unambiguous what point in time such a value means.
A LocalDateTime
describes some date and some time, without any information about which time zone they're presented in. This value is fundamentally ambiguous and open to interpretation; two people in different time zones reading the same LocalDateTime
value would disagree over when exactly that point in time occurs.
(For instance, if someone in London sees "1 PM on June 27, 2023", the exact point in time that it represents for them differs by five hours to how someone in New York would interpret the exact same value.)
As such, you cannot directly compare a ZonedDateTime
(an unambiguous point in time that everyone agrees on) with a LocalDateTime
(a point in time that's relative to whoever uses the value). You can compare two ZonedDateTime
with each other, and two LocalDateTime
with each other; but not one to the other.
If you nonetheless want to compare them, you need to decide how the two relate. For instance, you can use the .atZone(...)
on LocalDateTime
to add time zone information, converting it into a ZonedDateTime
, which can then be compared with the other value.
As an additional note, LocalDateTime
has other issues that one should be aware of. For instance, during DST transitions in the fall, the same LocalDateTime
value may represent two different points in time (since the period 2:00:00 AM-2:59:59 AM occurs twice in the same night). For reasons like this, a general rule of thumb is to only use LocalDateTime
for presenting the value to the user, not for doing calculations or similar.
And as a final "By the way, I technically lied to you" note, one might read the documentation for Duration.between
and notice that it mentions automatically converting one of the values if they're of different types. Why did it crash if it handles this automatically? In this case, pure luck (or bad luck, depending on your viewpoint).
The key here is that Duration.between
will convert the second value to be the same type as the first value. Your first value is the ZonedDateTime
and your second value is the LocalDateTime
, meaning that the latter will be converted to the former. If you had reversed them, the conversation would succeed since it's trivial to convert a ZonedDateTime
to a LocalDateTime
; just delete the time zone information. (This is however potentially dangerous since it implicitly assumes that the time zone information was completely irrelevant in the first place.)
The opposite isn't possible though; how do you automatically add the time zone information to convert a LocalDateTime
to a ZonedDateTime
? What value is the computer supposed to put there? Should it default to the UTC time zone? Should it default to the computer clock's time zone? Should it default to the time zone of the ZonedDateTime
value? At best, the computer would have to blindly guess and hope it does the right thing, which is a very dangerous behavior for a method to have. In this case, it's much better that it crashes to notify you, the developer, that you have asked it a vague question and should be more specific.