Search code examples
javatimestampjava-timezoneddatetimejava.time.instant

Timestamp.from not heeding timezone from Instant


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?


Solution

  • 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 as DateTimeFormatter.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.