When I try to convert a ZonedDateTime
to a Timestamp
everything is fine until I call Timestamp.from()
in the following code:
ZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);
currentTimeUTC = currentTimeUTC.minusSeconds(currentTimeUTC.getSecond());
currentTimeUTC = currentTimeUTC.minusNanos(currentTimeUTC.getNano());
return Timestamp.from(currentTimeUTC.toInstant());
ZonedDateTime.now(ZoneOffset.UTC); -> 2018-04-26T12:31Z
currentTimeUTC.toInstant() -> 2018-04-26T12:31:00Z
Timestamp.from(currentTimeUTC.toInstant()) -> 2018-04-26 14:31:00.0
// (with Timezone of Europe/Berlin, which is currently +2)
Why is Timestamp.from()
not heeding the timezone set in the instant?
The Instant
class doesn't have a timezone, it just has the values of seconds and nanoseconds since unix epoch. A Timestamp
also represents that (a count from epoch).
why is the debugger displaying this with a Z behind it?
The problem is in the toString
methods:
Instant.toString()
converts the seconds and nanoseconds values to the corresponding date/time in UTC - hence the "Z" in the end - and I believe it was made like that for convenience (to make the API more "developer-friendly").
The javadoc for toString
says:
A string representation of this instant using ISO-8601 representation.
The format used is the same asDateTimeFormatter.ISO_INSTANT
.
And if we take a look at DateTimeFormatter.ISO_INSTANT
javadoc:
The ISO instant formatter that formats or parses an instant in UTC, such as '2011-12-03T10:15:30Z'
As debuggers usually uses the toString
method to display variables values, that explains why you see the Instant
with "Z" in the end, instead of the seconds/nanoseconds values.
On the other hand, Timestamp.toString
uses the JVM default timezone to convert the seconds/nanos values to a date/time string.
But the values of both Instant
and Timestamp
are the same. You can check that by calling the methods Instant.toEpochMilli
and Timestamp.getTime
, both will return the same value.
Note: instead of calling minusSeconds
and minusNanos
, you could use the truncatedTo
method:
ZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);
currentTimeUTC = currentTimeUTC.truncatedTo(ChronoUnit.MINUTES);
This will set all fields smaller than ChronoUnit.MINUTES
(in this case, the seconds and nanoseconds) to zero.
You could also use withSecond(0)
and withNano(0)
, but in this case, I think truncatedTo
is better and more straight to the point.
Note2: the java.time API's creator also made a backport for Java 6 and 7, and in the project's github issues you can see a comment about the behaviour of Instant.toString
. The relevant part to this question:
If we were really hard line, the toString of an Instant would simply be the number of seconds from 1970-01-01Z. We chose not to do that, and output a more friendly toString to aid developers
That reinforces my view that the toString
method was designed like this for convenience and ease to use.