I am trying to use LocalDateTime to manipulate dates in my application but I have noticed that getting epoch seconds from it returns different values from what I expected
val now1 = Instant.now().epochSecond - 60
val now2 = Instant.now().minusSeconds(60).epochSecond
val now3 = LocalDateTime.now().minusSeconds(60).toEpochSecond(ZoneOffset.UTC)
val now4 = System.currentTimeMillis() / 1000 - 60
Output
Now1 = 1674501451
Now2 = 1674501451
Now3 = 1674512251
Now4 = 1674501451
Notice how Now3 has a different value. What is happening?
LocalDateTime
class.I am trying to use LocalDateTime to manipulate dates
Don't.
If you are representing a moment, a specific point on the timeline, 👉 do not use LocalDateTime
. That class holds a date with a time-of-day but lacks the context of a time zone or offset-from-UTC. To represent a moment use:
Instant
— A moment as seen in UTC (an offset from UTC of zero hours-minutes-seconds).OffsetDateTime
- A moment as seen with a particular offset.ZonedDateTime
- A moment as seen through a particular time zone.I cannot imagine any scenario where calling LocalDateTime.now
is the right thing to do.
Example code:
Instant instant = Instant.now() ;
ZoneOffset offset = ZoneId.of( "Africa/Dar_es_Salaam" ).getRules().getOffset( instant ) ;
OffsetDateTime odt = OffsetDateTime.now( offset ) ;
ZoneId zone = ZoneId.of( "Africa/Dar_es_Salaam" ) ;
ZonedDateTime zonedDateTime = ZonedDateTime.now( zone )
Dump to console.
System.out.println( instant + " | " + instant.getEpochSecond() ) ;
System.out.println( odt + " | " + odt.toEpochSecond() ) ;
System.out.println( zonedDateTime + " | " + zonedDateTime.toEpochSecond() ) ;
See this code run at Ideone.com. Note that all three are within the same second, give or take a second as the clock may rollover to the next second by the second or third call to now
.
2023-01-24T14:27:06.416597Z | 1674570426
2023-01-24T17:27:06.478680+03:00 | 1674570426
2023-01-24T17:27:06.487289+03:00[Africa/Dar_es_Salaam] | 1674570426
LocalDateTime#toEpochSecond
Calling the toEpochSecond
method on LocalDateTime
is tricky conceptually. As a concept, LocalDateTime
has no epoch reference point. It is just a date with a time. For example, “noon on January 23 of 2023” by itself has no specific meaning, cannot be nailed down to a point on the timeline. So it is meaningless to compare “noon on January 23 of 2023” to a reference point such as the first moment of 1970 as seen in UTC, 1970-01-01T00:00Z.
Practically, the LocalDateTime
class is implemented as if it were in UTC. So internally, the class counts a number of whole seconds since 1970-01-01T00:00Z plus a count of nanoseconds for a fractional second. But you should not be thinking of this implementation detail when writing code for business logic. For business logic, calling LocalDateTime#toEpochSecond
makes no sense.
So the technical answer to your Question is that your delta of 3 hours (1_674_501_451L - 1_674_512_251L) came from the fact that your JVM’s current default time zone at that moment used an offset of +03:00, three hours ahead of UTC. So your call to LocalDateTime.now
captured a date and time three hours ahead of UTC. But then you asked for toEpochSecond
which treats that very same date and time as if it were in UTC.
Of course that date and time were not meant to represent a moment in UTC. So logically speaking, your code is nonsensical. You should not be comparing a count from epoch for a LocalDateTime
(which is a non-moment) to other classes such as Instant
(which is a moment).
In other words, you are comparing apples and oranges.
So, if LocalDateTime#toEpochSecond
is inappropriate for business logic, why does that class offer such a method?
That method is useful for serializing a LocalDateTime
value for storage or data-exchange. Some other systems may present date-with-time values in this manner.
However, using a count-from-reference is a poor way to communicate date-time values. The values are ambiguous, as their granularity is implicit, and their particular epoch reference point is also implicit. And, such values make validation and debugging difficult for human readers.
I strongly recommend using standard ISO 8601 text rather than a count when storing or exchanging date-time values outside Java. Regarding your example standard textual format would be:
2023-01-23T22:17:31
General advice: Avoid LocalDateTime
class unless you are very clear on its appropriate use.
For business apps, we are generally tracking moments. For example, "when does this contract come into effect", "when was this employee hired", "when does the purchase of this house close". So LocalDateTime
is not called for.
The two business scenarios for LocalDateTime
are (a) making appointments where we want the moment to float if time zone rules change, such as clinic, salon, and studio bookings, and (b) where we mean several moments, each with the same wall-clock time but occurring at different points on the timeline, such as "Announce this corporate message at noon in each of our company factories in Delhi, Düsseldorf, and Detroit.". Search Stack Overflow to learn more on this, as I and others have posted multiple times already.
On Ideone.com, see some code I noodled around with in writing this Answer.