I created a table like like this in PostgreSQL:
create table myTable (
dateAdded timestamp(0) without time zone null default (current_timestamp at time zone 'UTC');
)
I choose "without time zone" because I know that all timestamps that my application works with are always UTC. As far as I got the documentation the only difference to "with timestamp" is that I can supply values in other time zones which will then be converted to UTC. However I want to avoid such automatic conversions because they could hardly do any good if I know that my values are UTC.
When I add a new record in my test table and view the table's content with pgAdmin I can see that the insertion date has been correctly saved in UTC format.
However when I try to select values using JDBC the value gets 2 hours subtracted. I am located at UTC+2, so it looks like that JDBC assumes that the date in the table is not a UTC timestamp, but a UTC+2 timestamp instead and tries to convert to UTC.
Some googling revealed that the JDBC standard dictates something about conversion to/from the current time zone, but that this could be prevented by supplying a Calander to getTimestamp/setTimestamp calls. However supplying a calendar did not make any difference at all. Here is my MyBatis/Jodatime converter:
@MappedTypes(DateTime.class)
public class DateTimeTypeHandler extends BaseTypeHandler<DateTime> {
private static final Calendar UTC_CALENDAR = Calendar.getInstance(DateTimeZone.UTC.toTimeZone());
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
DateTime parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, new Timestamp(parameter.getMillis()), UTC_CALENDAR);
}
@Override
public DateTime getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return fromSQLTimestamp(rs.getTimestamp(columnName, UTC_CALENDAR));
}
/* further get methods with pretty much same content as above */
private static DateTime fromSQLTimestamp(final Timestamp ts) {
if (ts == null) {
return null;
}
return new DateTime(ts.getTime(), DateTimeZone.UTC);
}
}
What's the correct way to get UTC timestamps from JDBC+PostgreSQL timestamp source?
Introduction to PostgreSQL timestamp The timestamp datatype allows you to store both date and time. However, it does not have any time zone data. It means that when you change the timezone of your database server, the timestamp value stored in the database will not change automatically.
PostgreSQL assumes your local time zone for any type containing only date or time. All timezone-aware dates and times are stored internally in UTC .
sql. Timestamp classes represent a point on the timeline in UTC. In other words, they represent the number of nanoseconds since the Java epoch.
In PostgreSQL, the next data type is TIMESTAMP, which can store both TIME and DATE values. But it does not support any time zone data. And it implies that when we convert the time zone of our database server, the timestamp value will be stored in the database and cannot be modified repeatedly.
Set UTC as default timezone of your JVM -Duser.timezone=UTC
or set your whole OS to UTC.
In Postgres both TIMESTAMP
and TIMESTAMP WITH TIMEZONE
are stored the same way - number of seconds since Postgres epoch (2000-01-01). The main difference is what Postgres do when it saves timestamp value such as 2004-10-19 10:23:54+02
:
+02
is just stripped away -02
correction is performed to make it UTC Now the interesting thing is when JDBC driver loads the value:
In both cases you will end up with java.sql.Timestamp
object with user's default TZ.
Timestamps without TZ are pretty limited. If you have two systems connected to your database, both with different TZ, they will interpret timestamps differently. Therefore, I strongly advice you to use TIMESTAMP WITH TIMEZONE
.
You can tell JDBC what kind of TZ it should use when reading timestamp via ResultSet#getTimestamp(String, Calendar)
. Excerpt from JavaDoc:
This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does not store timezone information.
There are a few tricks specific for the Postgres JDBC driver
See https://jdbc.postgresql.org/documentation/head/java8-date-time.html
So when reading you can do
Instant utc =resultSet.getObject("dateAdded",LocalDateTime.class).toInstant(ZoneOffset.UTC);
If you use a connection pool such as Hikari, you can also specify the time time-zone used by each connection by setting connectionInitSql=set time zone 'UTC'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With