tl;dr
Postgres always stores a moment in UTC in a column of type TIMESTAMP WITH TIME ZONE
.
- Going into the database, adjusted to UTC.
- Going out, UTC.
Beware of your database access tool: It may alter the retrieved UTC value, applying a zone/offset adjustment. This creates the illusion of the moment having been stored in that zone/offset when in fact it was stored in UTC.
Use objects, not strings, to exchange date-time values with your database.
Use only java.time classes, never the legacy Date
/Calendar
. For UTC moments, use java.time.Instant
class.
Instant instant = Instant.now() ;
myPreparedStatement.setObject( … , instant ) ;
Retrieval.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Smart objects, not dumb strings
The other Answers are correct in advising you to use objects rather than strings when exchanging date-time values between Java and your database. That is the whole point to JDBC and your JDBC driver, to marshal data back-and-forth while mediating the differences & details.
However those other Answers incorrectly advise the wrong classes to use in Java.
UTC
Postgres saves moments only in UTC. Any time zone or offset-from-UTC information accompanying an input to a database column of type TIMESTAMP WITH TIME ZONE
is used to adjust the value into UTC. Then the UTC value is saved to the database, and the original zone/offset forgotten.
Likewise, when retrieving a value from a database column of type TIMESTAMP WITH TIME ZONE
, you always get a value in UTC.
However, your middleware or tool being used between you and the database may be choose in alter the UTC value in transit, to apply a time zone or offset, thereby adjusting the value. I consider this to be a poor design choice, though I understand the good intentions behind it. But such behavior creates the illusion that a zone/offset was in the stored data when it was not.
Be aware that I am describing Postgres behavior here. The SQL standard defines the data types I discuss here, but not their behavior. The SQL standard barely touches on the issues of date-time, unfortunately. So various database implementations differ in their behavior. The adjust-to-UTC behavior of Postgres makes sense to me. But note that if you care about a date-time value’s original zone/offset, you must store that yourself in a separate column.
On the Java side, just use Instant
for sending and retrieving a moment from a database column of type TIMESTAMP WITH TIME ZONE
.
Sending.
Instant instant = Instant.now() ; // Capture the current moment in UTC.
myPreparedStatement.setObject( … , instant ) ;
Retrieving.
Instant instant = myResultSet.getObject( … , Instant.class );
You can adjust from UTC to some particular time zone.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
If you were working in your business logic with an OffsetDateTime
or ZonedDateTime
object, extract an Instant
by calling their toInstant
method.
Instant instant = myZonedDateTime.toInstant() ; // Extract an `Instant` object, effectively adjusting from some particular time zone to UTC.
Unzoned
An entirely different kind of date-time is one intentionally lacking any concept of time zone or offset-from-UTC. As such, this kind does not represent a specific moment, is not a point on the timeline. This type represents potential moments along a range of about 26-27 hours (the range of various time zones).
For this kind of date-time:
- In Java, use
LocalDateTime
class.
- In Postgres, use
TIMESTAMP WITHOUT TIME ZONE
type.
When an input is sent to a column of this type in Postgres, any accompanying zone/offset information is ignored. The date and time-of-day are taken as-is and stored directly in the database. When retrieved you get the same date and time-of-day, with no adjustment to any particular time zone.
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.