java.util.Date::toString
applies zone
You are likely being confused by the well-intentioned but unfortunate design decision of the java.util.Date::toString
method to dynamically applying the JVM’s current default time zone while generating a String to represent the value of the Date
which is actually always in UTC by definition. One of many reasons to avoid this terribly troublesome old class.
Wrong data type in database
I have a field in postgres whose type is "timestamp without timezone". It is UTC time in database which I want to show in API response.
This is a contradiction in terms. A SQL-standard TIMESTAMP WITHOUT TIME ZONE
column does not hold a UTC value. That data type lacks any concept of time zone or offset-from-UTC. (Such UTC values should have instead been stored in a column of type TIMESTAMP WITH TIME ZONE
.)
The values you inserted may have been intended for UTC, but you lost that fact once stored to the database.
java.time
Avoid java.sql.Timestamp
There is no need to be using java.sql.Timestamp
. That legacy class should be avoided. As of JDBC 4.2 and later, we can directly exchange java.time types with the database. Hibernate supports java.time.
LocalDateTime
Lacking any zone/offset, you should retrieve those values using LocalDateTime
class. That class also lacks any concept of time zone or offset-from-UTC.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
OffsetDateTime
If you want to assume that retrieved value represents a moment in UTC, apply a ZoneOffset
to get a OffsetDateTime
object. For UTC specifically, we can use the predefined constant ZoneOffset.UTC
.
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;
Not ZonedDateTime
While the ZonedDateTime
class will technically work here in place of the OffsetDateTime
class, its use is inappropriate.
A time zone is much more than an offset. A zone is a history of the past, present, and future changes to the offset used by the people of a specific region. In contrast, an offset-from-UTC is merely a number of hours and minutes, nothing more, nothing less.
So using a ZonedDateTime
in this context of UTC is misleading and potentially confusing.
Instant
Now, in your OffsetDateTime
object, you have a moment in UTC.
If you know you want to work only in UTC from there, extract a Instant
. An Instant
is always in UTC by definition. When representing moments in UTC, generally use Instant
. If you need more flexibility such as in generating strings in various formats, switch to using OffsetDateTime
.
Instant instant = odt.toInstant() ;
About java.time
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.