如何从JDBC + postgreSql时间戳获取UTC时间戳?

我在PostgreSQL中创建了一个这样的表:

create table myTable ( dateAdded timestamp(0) without time zone null default (current_timestamp at time zone 'UTC'); ) 

我选择“没有时区”,因为我知道我的应用程序使用的所有时间戳都是 UTC。 据我所知,“带时间戳”的唯一区别是我可以在其他时区提供值,然后转换为UTC。 但是我想避免这种自动转换,因为如果我知道我的值是UTC,它们几乎不会有任何好处。

当我在测试表中添加新记录并使用pgAdmin查看表的内容时,我可以看到插入日期已经以UTC格式正确保存。

但是,当我尝试使用JDBC选择值时,该值减去2小时。 我位于UTC + 2,所以看起来JDBC假定表中的日期不是UTC时间戳,而是UTC + 2时间戳而是尝试转换为UTC。

一些谷歌搜索透露,JDBC标准规定了与当前时区的转换,但这可以通过向getTimestamp / setTimestamp调用提供Calander来防止。 然而,提供日历并没有任何差别。 这是我的MyBatis / Jodatime转换器:

 @MappedTypes(DateTime.class) public class DateTimeTypeHandler extends BaseTypeHandler { 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); } } 

从JDBC + PostgreSQL时间戳源获取UTC时间戳的正确方法是什么?

将UTC设置为JVM -Duser.timezone=UTC默认时区,或将整个操作系统设置为UTC。

背景

在Postgres中, TIMESTAMPTIMESTAMP WITH TIMEZONE以相同的方式存储 – 自Postgres纪元(2000-01-01)以来的秒数。 主要区别在于Postgres在保存时间戳值时所做的事情,如2004-10-19 10:23:54+02

  • 没有TZ,+ 02就被剥夺了
  • 使用TZ执行-02校正以使其成为UTC

现在有趣的是JDBC驱动程序加载值时:

  • 如果没有TZ,存储的值将由用户(JVM / OS)TZ移位
  • 使用TZ,该值被视为UTC

在这两种情况下,您将使用用户的默认TZ结束java.sql.Timestamp对象。

时区

没有TZ的时间戳非常有限。 如果有两个系统连接到数据库,两个系统都有不同的TZ,它们将以不同的方式解释时间戳。 因此,我强烈建议您使用TIMESTAMP WITH TIMEZONE


更新

您可以告诉JDBC在通过ResultSet#getTimestamp(String, Calendar)读取时间戳时应该使用哪种TZ。 摘自JavaDoc:

如果基础数据库不存储时区信息,则此方法使用给定日历为时间戳构造适当的毫秒值。

Pavel提出的解决方案(添加jvm参数’-Duser.timezone = UTC’)肯定是最佳选择,如果您没有系统访问权限,这并不总是可行。

我发现的唯一方法是将时间戳转换为查询中的纪元 ,并将其读取为long

 SELECT extract(epoch from my_timestamp_colum at time zone 'utc')::bigint * 1000 AS my_timestamp_as_epoc FROM my_table 

然后用普通的JDBC读取它

 ResultSet rs = ... long myTimestampAsEpoc = rs.getLong("my_timestamp_as_epoc"); Date myTimestamp = new Date(myTimestampAsEpoc); 

使用iBatis / MyBatis,您需要将列作为Long处理,然后手动将其转换为Date / Timestamp。 不方便和丑陋。

PostgreSQL中的反向操作可以通过以下方式完成:

 SELECT TIMESTAMP WITHOUT TIME ZONE 'epoch' + 1421855729000 * INTERVAL '1 millisecond' 

也就是说,JDBC规范并未说明返回的时间戳应该或不应该将时间戳转移到用户时区; 但是,由于您将表列定义为“没有时区的时间戳”,我希望没有时间转移,但记录的时期只包含在java.sql.Timestamp 。 我认为,这是PostgreSQL JDBC驱动程序中的一个错误。 因此,如果没有系统访问权限,那么在这个级别上可能没有更多可以做的事情。