Reputation: 6732
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?
Upvotes: 1
Views: 3105
Reputation: 338566
Timestamp::toString
to apply the JVM’s current default time zone to the objects internal UTC value.Instant
, never Timestamp
.2018-04-26T12:31Z
is in standard ISO 8601 format, with the Z
being short for Zulu
and meaning UTC.Your entire block of code can be replaced with:
Instant.now()
…such as:
myPreparedStatement.setObject( … , Instant.now() ) ;
The Answer by wowxts is correct. Instant
is always in UTC, as is Timestamp
, yet Timestamp::toString
applies a time zone. This behavior is one of many poor design choices in those troubled legacy classes.
I'll add some other thoughts.
Instant
for UTCZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);
While technically correct, this line is semantically wrong. If you want to represent a moment in UTC, use Instant
class. The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = Instant.now() ; // Capture the current moment in UTC.
Timestamp
classTimestamp.from(currentTimeUTC.toInstant());
While technically correct, using my suggest above, that would be:
Timestamp.from( instant ); // Convert from modern *java.time* class to troublesome legacy date-time class using new method added to the old class.
Nothing is lost going between Instant
and Timestamp
, as both represent a moment in UTC with a resolution of nanoseconds. However…
No need to be using java.sql.Timestamp
at all! That class is part of the troublesome old date-time classes that are now legacy. They were supplanted entirely by the java.time classes defined by JSR 310. Timestamp
is replaced by Instant
.
As of JDBC 4.2 and later, you can directly exchange java.time objects with your database.
Insert/Update.
myPreparedStatement.setObject( … , instant ) ;
Retrieval.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
Upvotes: 3
Reputation: 56
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.
Upvotes: 4
Reputation: 614
Instant does not hold the Timezone information. It only holds the seconds and nanos. To when you convert your ZonedDateTime into an Instant the information is lost. When converting into Timestamp then the Timestamp will hold the default Timezone, which is, in your case, Europe/Berlin.
Upvotes: 2